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