]> git.lizzy.rs Git - micro.git/blob - cmd/micro/terminal.go
Clean up terminal emulator a bit
[micro.git] / cmd / micro / terminal.go
1 package main
2
3 import (
4         "fmt"
5         "os"
6         "os/exec"
7         "strconv"
8
9         "github.com/zyedidia/clipboard"
10         "github.com/zyedidia/tcell"
11         "github.com/zyedidia/terminal"
12 )
13
14 const (
15         VTIdle    = iota // Waiting for a new command
16         VTRunning        // Currently running a command
17         VTDone           // Finished running a command
18 )
19
20 // A Terminal holds information for the terminal emulator
21 type Terminal struct {
22         state     terminal.State
23         view      *View
24         vtOld     ViewType
25         term      *terminal.VT
26         title     string
27         status    int
28         selection [2]Loc
29         wait      bool
30 }
31
32 // HasSelection returns whether this terminal has a valid selection
33 func (t *Terminal) HasSelection() bool {
34         return t.selection[0] != t.selection[1]
35 }
36
37 // GetSelection returns the selected text
38 func (t *Terminal) GetSelection(width int) string {
39         start := t.selection[0]
40         end := t.selection[1]
41         if start.GreaterThan(end) {
42                 start, end = end, start
43         }
44         var ret string
45         var l Loc
46         for y := start.Y; y <= end.Y; y++ {
47                 for x := 0; x < width; x++ {
48                         l.X, l.Y = x, y
49                         if l.GreaterEqual(start) && l.LessThan(end) {
50                                 c, _, _ := t.state.Cell(x, y)
51                                 ret += string(c)
52                         }
53                 }
54         }
55         return ret
56 }
57
58 // Start begins a new command in this terminal with a given view
59 func (t *Terminal) Start(execCmd []string, view *View) error {
60         if len(execCmd) <= 0 {
61                 return nil
62         }
63
64         cmd := exec.Command(execCmd[0], execCmd[1:]...)
65         term, _, err := terminal.Start(&t.state, cmd)
66         if err != nil {
67                 return err
68         }
69         t.term = term
70         t.view = view
71         t.vtOld = view.Type
72         t.status = VTRunning
73         t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid)
74
75         go func() {
76                 for {
77                         err := term.Parse()
78                         if err != nil {
79                                 fmt.Fprintln(os.Stderr, "[Press enter to close]")
80                                 break
81                         }
82                         updateterm <- true
83                 }
84                 closeterm <- view.Num
85         }()
86
87         return nil
88 }
89
90 // Resize informs the terminal of a resize event
91 func (t *Terminal) Resize(width, height int) {
92         t.term.Resize(width, height)
93 }
94
95 // HandleEvent handles a tcell event by forwarding it to the terminal emulator
96 // If the event is a mouse event and the program running in the emulator
97 // does not have mouse support, the emulator will support selections and
98 // copy-paste
99 func (t *Terminal) HandleEvent(event tcell.Event) {
100         if e, ok := event.(*tcell.EventKey); ok {
101                 if t.status == VTDone {
102                         switch e.Key() {
103                         case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter:
104                                 t.Close()
105                                 t.view.Type = vtDefault
106                         default:
107                         }
108                 }
109                 if e.Key() == tcell.KeyCtrlC && t.HasSelection() {
110                         clipboard.WriteAll(t.GetSelection(t.view.Width), "clipboard")
111                         messenger.Message("Copied selection to clipboard")
112                 } else if t.status != VTDone {
113                         t.WriteString(event.EscSeq())
114                 }
115         } else if e, ok := event.(*tcell.EventMouse); !ok || t.state.Mode(terminal.ModeMouseMask) {
116                 t.WriteString(event.EscSeq())
117         } else {
118                 x, y := e.Position()
119                 x -= t.view.x
120                 y += t.view.y
121
122                 if e.Buttons() == tcell.Button1 {
123                         if !t.view.mouseReleased {
124                                 // drag
125                                 t.selection[1].X = x
126                                 t.selection[1].Y = y
127                         } else {
128                                 t.selection[0].X = x
129                                 t.selection[0].Y = y
130                                 t.selection[1].X = x
131                                 t.selection[1].Y = y
132                         }
133
134                         t.view.mouseReleased = false
135                 } else if e.Buttons() == tcell.ButtonNone {
136                         if !t.view.mouseReleased {
137                                 t.selection[1].X = x
138                                 t.selection[1].Y = y
139                         }
140                         t.view.mouseReleased = true
141                 }
142         }
143 }
144
145 // Stop stops execution of the terminal and sets the status
146 // to VTDone
147 func (t *Terminal) Stop() {
148         t.term.File().Close()
149         t.term.Close()
150         if t.wait {
151                 t.status = VTDone
152         } else {
153                 t.status = VTIdle
154                 t.view.Type = t.vtOld
155         }
156 }
157
158 // Close sets the status to VTIdle indicating that the terminal
159 // is ready for a new command to execute
160 func (t *Terminal) Close() {
161         t.status = VTIdle
162 }
163
164 // WriteString writes a given string to this terminal's pty
165 func (t *Terminal) WriteString(str string) {
166         t.term.File().WriteString(str)
167 }
168
169 // Display displays this terminal in a view
170 func (t *Terminal) Display() {
171         divider := 0
172         if t.view.x != 0 {
173                 divider = 1
174                 dividerStyle := defStyle
175                 if style, ok := colorscheme["divider"]; ok {
176                         dividerStyle = style
177                 }
178                 for i := 0; i < t.view.Height; i++ {
179                         screen.SetContent(t.view.x, t.view.y+i, '|', nil, dividerStyle.Reverse(true))
180                 }
181         }
182         t.state.Lock()
183         defer t.state.Unlock()
184
185         var l Loc
186         for y := 0; y < t.view.Height; y++ {
187                 for x := 0; x < t.view.Width; x++ {
188                         l.X, l.Y = x, y
189                         c, f, b := t.state.Cell(x, y)
190
191                         fg, bg := int(f), int(b)
192                         if f == terminal.DefaultFG {
193                                 fg = int(tcell.ColorDefault)
194                         }
195                         if b == terminal.DefaultBG {
196                                 bg = int(tcell.ColorDefault)
197                         }
198                         st := tcell.StyleDefault.Foreground(GetColor256(int(fg))).Background(GetColor256(int(bg)))
199
200                         if l.LessThan(t.selection[1]) && l.GreaterEqual(t.selection[0]) || l.LessThan(t.selection[0]) && l.GreaterEqual(t.selection[1]) {
201                                 st = st.Reverse(true)
202                         }
203
204                         screen.SetContent(t.view.x+x+divider, t.view.y+y, c, nil, st)
205                 }
206         }
207         if t.state.CursorVisible() && tabs[curTab].CurView == t.view.Num {
208                 curx, cury := t.state.Cursor()
209                 screen.ShowCursor(curx+t.view.x+divider, cury+t.view.y)
210         }
211 }