]> git.lizzy.rs Git - micro.git/blob - internal/action/bufpane.go
Fix test dependencies and travis build
[micro.git] / internal / action / bufpane.go
1 package action
2
3 import (
4         "strings"
5         "time"
6
7         "github.com/zyedidia/micro/internal/buffer"
8         "github.com/zyedidia/micro/internal/display"
9         "github.com/zyedidia/micro/internal/screen"
10         "github.com/zyedidia/tcell"
11 )
12
13 type BufKeyAction func(*BufPane) bool
14 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
15
16 var BufKeyBindings map[Event]BufKeyAction
17 var BufKeyStrings map[Event]string
18 var BufMouseBindings map[MouseEvent]BufMouseAction
19
20 func init() {
21         BufKeyBindings = make(map[Event]BufKeyAction)
22         BufKeyStrings = make(map[Event]string)
23         BufMouseBindings = make(map[MouseEvent]BufMouseAction)
24 }
25
26 // BufMapKey maps a key event to an action
27 func BufMapKey(k Event, action string) {
28         if strings.HasPrefix(action, "command:") {
29                 action = strings.SplitN(action, ":", 2)[1]
30                 BufKeyStrings[k] = action
31                 BufKeyBindings[k] = CommandAction(action)
32         } else if strings.HasPrefix(action, "command-edit:") {
33                 action = strings.SplitN(action, ":", 2)[1]
34                 BufKeyStrings[k] = action
35                 BufKeyBindings[k] = CommandEditAction(action)
36         } else if f, ok := BufKeyActions[action]; ok {
37                 BufKeyStrings[k] = action
38                 BufKeyBindings[k] = f
39         } else {
40                 screen.TermMessage("Error:", action, "does not exist")
41         }
42 }
43
44 // BufMapMouse maps a mouse event to an action
45 func BufMapMouse(k MouseEvent, action string) {
46         if f, ok := BufMouseActions[action]; ok {
47                 BufMouseBindings[k] = f
48         } else {
49                 delete(BufMouseBindings, k)
50                 BufMapKey(k, action)
51         }
52 }
53
54 // The BufPane connects the buffer and the window
55 // It provides a cursor (or multiple) and defines a set of actions
56 // that can be taken on the buffer
57 // The ActionHandler can access the window for necessary info about
58 // visual positions for mouse clicks and scrolling
59 type BufPane struct {
60         display.BWindow
61
62         Buf *buffer.Buffer
63
64         Cursor *buffer.Cursor // the active cursor
65
66         StartLine int // Vertical scrolling
67         StartCol  int // Horizontal scrolling
68
69         // Since tcell doesn't differentiate between a mouse release event
70         // and a mouse move event with no keys pressed, we need to keep
71         // track of whether or not the mouse was pressed (or not released) last event to determine
72         // mouse release events
73         mouseReleased bool
74
75         // We need to keep track of insert key press toggle
76         isOverwriteMode bool
77         // This stores when the last click was
78         // This is useful for detecting double and triple clicks
79         lastClickTime time.Time
80         lastLoc       buffer.Loc
81
82         // lastCutTime stores when the last ctrl+k was issued.
83         // It is used for clearing the clipboard to replace it with fresh cut lines.
84         lastCutTime time.Time
85
86         // freshClip returns true if the clipboard has never been pasted.
87         freshClip bool
88
89         // Was the last mouse event actually a double click?
90         // Useful for detecting triple clicks -- if a double click is detected
91         // but the last mouse event was actually a double click, it's a triple click
92         doubleClick bool
93         // Same here, just to keep track for mouse move events
94         tripleClick bool
95
96         // Last search stores the last successful search for FindNext and FindPrev
97         lastSearch string
98         // Should the current multiple cursor selection search based on word or
99         // based on selection (false for selection, true for word)
100         multiWord bool
101
102         splitID uint64
103 }
104
105 func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
106         h := new(BufPane)
107         h.Buf = buf
108         h.BWindow = win
109
110         h.Cursor = h.Buf.GetActiveCursor()
111         h.mouseReleased = true
112
113         return h
114 }
115
116 func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
117         w := display.NewBufWindow(0, 0, 0, 0, buf)
118         return NewBufPane(buf, w)
119 }
120
121 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
122         h.Buf.Close()
123         h.Buf = b
124         h.BWindow.SetBuffer(b)
125         h.Cursor = b.GetActiveCursor()
126         v := new(display.View)
127         h.SetView(v)
128         h.Relocate()
129         // Set mouseReleased to true because we assume the mouse is not being pressed when
130         // the editor is opened
131         h.mouseReleased = true
132         // Set isOverwriteMode to false, because we assume we are in the default mode when editor
133         // is opened
134         h.isOverwriteMode = false
135         h.lastClickTime = time.Time{}
136 }
137
138 func (h *BufPane) ID() uint64 {
139         return h.splitID
140 }
141
142 func (h *BufPane) SetID(i uint64) {
143         h.splitID = i
144 }
145
146 func (h *BufPane) Name() string {
147         return h.Buf.GetName()
148 }
149
150 // HandleEvent executes the tcell event properly
151 // TODO: multiple actions bound to one key
152 func (h *BufPane) HandleEvent(event tcell.Event) {
153         switch e := event.(type) {
154         case *tcell.EventRaw:
155                 re := RawEvent{
156                         esc: e.EscSeq(),
157                 }
158                 h.DoKeyEvent(re)
159         case *tcell.EventKey:
160                 ke := KeyEvent{
161                         code: e.Key(),
162                         mod:  e.Modifiers(),
163                         r:    e.Rune(),
164                 }
165
166                 done := h.DoKeyEvent(ke)
167                 if !done && e.Key() == tcell.KeyRune {
168                         h.DoRuneInsert(e.Rune())
169                 }
170         case *tcell.EventMouse:
171                 switch e.Buttons() {
172                 case tcell.ButtonNone:
173                         // Mouse event with no click
174                         if !h.mouseReleased {
175                                 // Mouse was just released
176
177                                 mx, my := e.Position()
178                                 mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
179
180                                 // Relocating here isn't really necessary because the cursor will
181                                 // be in the right place from the last mouse event
182                                 // However, if we are running in a terminal that doesn't support mouse motion
183                                 // events, this still allows the user to make selections, except only after they
184                                 // release the mouse
185
186                                 if !h.doubleClick && !h.tripleClick {
187                                         h.Cursor.Loc = mouseLoc
188                                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
189                                         h.Cursor.CopySelection("primary")
190                                 }
191                                 h.mouseReleased = true
192                         }
193                 }
194
195                 me := MouseEvent{
196                         btn: e.Buttons(),
197                         mod: e.Modifiers(),
198                 }
199                 h.DoMouseEvent(me, e)
200         }
201         h.Buf.MergeCursors()
202
203         // Display any gutter messages for this line
204         c := h.Buf.GetActiveCursor()
205         none := true
206         for _, m := range h.Buf.Messages {
207                 if c.Y == m.Start.Y || c.Y == m.End.Y {
208                         InfoBar.GutterMessage(m.Msg)
209                         none = false
210                         break
211                 }
212         }
213         if none && InfoBar.HasGutter {
214                 InfoBar.ClearGutter()
215         }
216 }
217
218 // DoKeyEvent executes a key event by finding the action it is bound
219 // to and executing it (possibly multiple times for multiple cursors)
220 func (h *BufPane) DoKeyEvent(e Event) bool {
221         if action, ok := BufKeyBindings[e]; ok {
222                 estr := BufKeyStrings[e]
223                 for _, s := range MultiActions {
224                         if s == estr {
225                                 cursors := h.Buf.GetCursors()
226                                 for _, c := range cursors {
227                                         h.Buf.SetCurCursor(c.Num)
228                                         h.Cursor = c
229                                         if action(h) {
230                                                 h.Relocate()
231                                         }
232                                 }
233                                 return true
234                         }
235                 }
236                 if action(h) {
237                         h.Relocate()
238                 }
239                 return true
240         }
241         return false
242 }
243
244 func (h *BufPane) HasKeyEvent(e Event) bool {
245         _, ok := BufKeyBindings[e]
246         return ok
247 }
248
249 // DoMouseEvent executes a mouse event by finding the action it is bound
250 // to and executing it
251 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
252         if action, ok := BufMouseBindings[e]; ok {
253                 if action(h, te) {
254                         h.Relocate()
255                 }
256                 return true
257         } else if h.HasKeyEvent(e) {
258                 return h.DoKeyEvent(e)
259         }
260         return false
261 }
262
263 // DoRuneInsert inserts a given rune into the current buffer
264 // (possibly multiple times for multiple cursors)
265 func (h *BufPane) DoRuneInsert(r rune) {
266         cursors := h.Buf.GetCursors()
267         for _, c := range cursors {
268                 // Insert a character
269                 h.Buf.SetCurCursor(c.Num)
270                 if c.HasSelection() {
271                         c.DeleteSelection()
272                         c.ResetSelection()
273                 }
274
275                 if h.isOverwriteMode {
276                         next := c.Loc
277                         next.X++
278                         h.Buf.Replace(c.Loc, next, string(r))
279                 } else {
280                         h.Buf.Insert(c.Loc, string(r))
281                 }
282         }
283 }
284
285 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) {
286         e := NewBufPaneFromBuf(buf)
287         e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
288         MainTab().Panes = append(MainTab().Panes, e)
289         MainTab().Resize()
290         MainTab().SetActive(len(MainTab().Panes) - 1)
291 }
292 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) {
293         e := NewBufPaneFromBuf(buf)
294         e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
295         MainTab().Panes = append(MainTab().Panes, e)
296         MainTab().Resize()
297         MainTab().SetActive(len(MainTab().Panes) - 1)
298 }
299 func (h *BufPane) Close() {
300         h.Buf.Close()
301 }
302
303 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
304 var BufKeyActions = map[string]BufKeyAction{
305         "CursorUp":               (*BufPane).CursorUp,
306         "CursorDown":             (*BufPane).CursorDown,
307         "CursorPageUp":           (*BufPane).CursorPageUp,
308         "CursorPageDown":         (*BufPane).CursorPageDown,
309         "CursorLeft":             (*BufPane).CursorLeft,
310         "CursorRight":            (*BufPane).CursorRight,
311         "CursorStart":            (*BufPane).CursorStart,
312         "CursorEnd":              (*BufPane).CursorEnd,
313         "SelectToStart":          (*BufPane).SelectToStart,
314         "SelectToEnd":            (*BufPane).SelectToEnd,
315         "SelectUp":               (*BufPane).SelectUp,
316         "SelectDown":             (*BufPane).SelectDown,
317         "SelectLeft":             (*BufPane).SelectLeft,
318         "SelectRight":            (*BufPane).SelectRight,
319         "WordRight":              (*BufPane).WordRight,
320         "WordLeft":               (*BufPane).WordLeft,
321         "SelectWordRight":        (*BufPane).SelectWordRight,
322         "SelectWordLeft":         (*BufPane).SelectWordLeft,
323         "DeleteWordRight":        (*BufPane).DeleteWordRight,
324         "DeleteWordLeft":         (*BufPane).DeleteWordLeft,
325         "SelectLine":             (*BufPane).SelectLine,
326         "SelectToStartOfLine":    (*BufPane).SelectToStartOfLine,
327         "SelectToEndOfLine":      (*BufPane).SelectToEndOfLine,
328         "ParagraphPrevious":      (*BufPane).ParagraphPrevious,
329         "ParagraphNext":          (*BufPane).ParagraphNext,
330         "InsertNewline":          (*BufPane).InsertNewline,
331         "Backspace":              (*BufPane).Backspace,
332         "Delete":                 (*BufPane).Delete,
333         "InsertTab":              (*BufPane).InsertTab,
334         "Save":                   (*BufPane).Save,
335         "SaveAll":                (*BufPane).SaveAll,
336         "SaveAs":                 (*BufPane).SaveAs,
337         "Find":                   (*BufPane).Find,
338         "FindNext":               (*BufPane).FindNext,
339         "FindPrevious":           (*BufPane).FindPrevious,
340         "Center":                 (*BufPane).Center,
341         "Undo":                   (*BufPane).Undo,
342         "Redo":                   (*BufPane).Redo,
343         "Copy":                   (*BufPane).Copy,
344         "Cut":                    (*BufPane).Cut,
345         "CutLine":                (*BufPane).CutLine,
346         "DuplicateLine":          (*BufPane).DuplicateLine,
347         "DeleteLine":             (*BufPane).DeleteLine,
348         "MoveLinesUp":            (*BufPane).MoveLinesUp,
349         "MoveLinesDown":          (*BufPane).MoveLinesDown,
350         "IndentSelection":        (*BufPane).IndentSelection,
351         "OutdentSelection":       (*BufPane).OutdentSelection,
352         "OutdentLine":            (*BufPane).OutdentLine,
353         "Paste":                  (*BufPane).Paste,
354         "PastePrimary":           (*BufPane).PastePrimary,
355         "SelectAll":              (*BufPane).SelectAll,
356         "OpenFile":               (*BufPane).OpenFile,
357         "Start":                  (*BufPane).Start,
358         "End":                    (*BufPane).End,
359         "PageUp":                 (*BufPane).PageUp,
360         "PageDown":               (*BufPane).PageDown,
361         "SelectPageUp":           (*BufPane).SelectPageUp,
362         "SelectPageDown":         (*BufPane).SelectPageDown,
363         "HalfPageUp":             (*BufPane).HalfPageUp,
364         "HalfPageDown":           (*BufPane).HalfPageDown,
365         "StartOfLine":            (*BufPane).StartOfLine,
366         "EndOfLine":              (*BufPane).EndOfLine,
367         "ToggleHelp":             (*BufPane).ToggleHelp,
368         "ToggleKeyMenu":          (*BufPane).ToggleKeyMenu,
369         "ToggleRuler":            (*BufPane).ToggleRuler,
370         "JumpLine":               (*BufPane).JumpLine,
371         "ClearStatus":            (*BufPane).ClearStatus,
372         "ShellMode":              (*BufPane).ShellMode,
373         "CommandMode":            (*BufPane).CommandMode,
374         "ToggleOverwriteMode":    (*BufPane).ToggleOverwriteMode,
375         "Escape":                 (*BufPane).Escape,
376         "Quit":                   (*BufPane).Quit,
377         "QuitAll":                (*BufPane).QuitAll,
378         "AddTab":                 (*BufPane).AddTab,
379         "PreviousTab":            (*BufPane).PreviousTab,
380         "NextTab":                (*BufPane).NextTab,
381         "NextSplit":              (*BufPane).NextSplit,
382         "PreviousSplit":          (*BufPane).PreviousSplit,
383         "Unsplit":                (*BufPane).Unsplit,
384         "VSplit":                 (*BufPane).VSplitAction,
385         "HSplit":                 (*BufPane).HSplitAction,
386         "ToggleMacro":            (*BufPane).ToggleMacro,
387         "PlayMacro":              (*BufPane).PlayMacro,
388         "Suspend":                (*BufPane).Suspend,
389         "ScrollUp":               (*BufPane).ScrollUpAction,
390         "ScrollDown":             (*BufPane).ScrollDownAction,
391         "SpawnMultiCursor":       (*BufPane).SpawnMultiCursor,
392         "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
393         "RemoveMultiCursor":      (*BufPane).RemoveMultiCursor,
394         "RemoveAllMultiCursors":  (*BufPane).RemoveAllMultiCursors,
395         "SkipMultiCursor":        (*BufPane).SkipMultiCursor,
396         "JumpToMatchingBrace":    (*BufPane).JumpToMatchingBrace,
397
398         // This was changed to InsertNewline but I don't want to break backwards compatibility
399         "InsertEnter": (*BufPane).InsertNewline,
400 }
401
402 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
403 var BufMouseActions = map[string]BufMouseAction{
404         "MousePress":       (*BufPane).MousePress,
405         "MouseMultiCursor": (*BufPane).MouseMultiCursor,
406 }
407
408 // MultiActions is a list of actions that should be executed multiple
409 // times if there are multiple cursors (one per cursor)
410 // Generally actions that modify global editor state like quitting or
411 // saving should not be included in this list
412 var MultiActions = []string{
413         "CursorUp",
414         "CursorDown",
415         "CursorPageUp",
416         "CursorPageDown",
417         "CursorLeft",
418         "CursorRight",
419         "CursorStart",
420         "CursorEnd",
421         "SelectToStart",
422         "SelectToEnd",
423         "SelectUp",
424         "SelectDown",
425         "SelectLeft",
426         "SelectRight",
427         "WordRight",
428         "WordLeft",
429         "SelectWordRight",
430         "SelectWordLeft",
431         "DeleteWordRight",
432         "DeleteWordLeft",
433         "SelectLine",
434         "SelectToStartOfLine",
435         "SelectToEndOfLine",
436         "ParagraphPrevious",
437         "ParagraphNext",
438         "InsertNewline",
439         "Backspace",
440         "Delete",
441         "InsertTab",
442         "FindNext",
443         "FindPrevious",
444         "Cut",
445         "CutLine",
446         "DuplicateLine",
447         "DeleteLine",
448         "MoveLinesUp",
449         "MoveLinesDown",
450         "IndentSelection",
451         "OutdentSelection",
452         "OutdentLine",
453         "Paste",
454         "PastePrimary",
455         "SelectPageUp",
456         "SelectPageDown",
457         "StartOfLine",
458         "EndOfLine",
459         "JumpToMatchingBrace",
460 }