]> git.lizzy.rs Git - micro.git/blob - internal/action/bufpane.go
203a5b3a240acd76d70f70c2435d045711cb1b0e
[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/v2/internal/buffer"
11         "github.com/zyedidia/micro/v2/internal/clipboard"
12         "github.com/zyedidia/micro/v2/internal/config"
13         "github.com/zyedidia/micro/v2/internal/display"
14         ulua "github.com/zyedidia/micro/v2/internal/lua"
15         "github.com/zyedidia/micro/v2/internal/screen"
16         "github.com/zyedidia/tcell"
17 )
18
19 type BufKeyAction func(*BufPane) bool
20 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
21
22 var BufKeyBindings map[Event]BufKeyAction
23 var BufKeyStrings map[Event]string
24 var BufMouseBindings map[MouseEvent]BufMouseAction
25
26 func init() {
27         BufKeyBindings = make(map[Event]BufKeyAction)
28         BufKeyStrings = make(map[Event]string)
29         BufMouseBindings = make(map[MouseEvent]BufMouseAction)
30 }
31
32 func LuaAction(fn string) func(*BufPane) bool {
33         luaFn := strings.Split(fn, ".")
34         if len(luaFn) <= 1 {
35                 return nil
36         }
37         plName, plFn := luaFn[0], luaFn[1]
38         pl := config.FindPlugin(plName)
39         if pl == nil {
40                 return nil
41         }
42         return func(h *BufPane) bool {
43                 val, err := pl.Call(plFn, luar.New(ulua.L, h))
44                 if err != nil {
45                         screen.TermMessage(err)
46                 }
47                 if v, ok := val.(lua.LBool); !ok {
48                         return false
49                 } else {
50                         return bool(v)
51                 }
52         }
53 }
54
55 // BufMapKey maps a key event to an action
56 func BufMapKey(k Event, action string) {
57         BufKeyStrings[k] = action
58         var actionfns []func(*BufPane) bool
59         var names []string
60         var types []byte
61         for i := 0; ; i++ {
62                 if action == "" {
63                         break
64                 }
65
66                 // TODO: fix problem when complex bindings have these
67                 // characters (escape them?)
68                 idx := strings.IndexAny(action, "&|,")
69                 a := action
70                 if idx >= 0 {
71                         a = action[:idx]
72                         types = append(types, action[idx])
73                         action = action[idx+1:]
74                 } else {
75                         types = append(types, ' ')
76                         action = ""
77                 }
78
79                 var afn func(*BufPane) bool
80                 if strings.HasPrefix(a, "command:") {
81                         a = strings.SplitN(a, ":", 2)[1]
82                         afn = CommandAction(a)
83                         names = append(names, "")
84                 } else if strings.HasPrefix(a, "command-edit:") {
85                         a = strings.SplitN(a, ":", 2)[1]
86                         afn = CommandEditAction(a)
87                         names = append(names, "")
88                 } else if strings.HasPrefix(a, "lua:") {
89                         a = strings.SplitN(a, ":", 2)[1]
90                         afn = LuaAction(a)
91                         if afn == nil {
92                                 screen.TermMessage("Lua Error:", a, "does not exist")
93                                 continue
94                         }
95                         split := strings.SplitN(a, ".", 2)
96                         if len(split) > 1 {
97                                 a = strings.Title(split[0]) + strings.Title(split[1])
98                         } else {
99                                 a = strings.Title(a)
100                         }
101
102                         names = append(names, a)
103                 } else if f, ok := BufKeyActions[a]; ok {
104                         afn = f
105                         names = append(names, a)
106                 } else {
107                         screen.TermMessage("Error in bindings: action", a, "does not exist")
108                         continue
109                 }
110                 actionfns = append(actionfns, afn)
111         }
112         BufKeyBindings[k] = func(h *BufPane) bool {
113                 cursors := h.Buf.GetCursors()
114                 success := true
115                 for i, a := range actionfns {
116                         innerSuccess := true
117                         for j, c := range cursors {
118                                 if c == nil {
119                                         continue
120                                 }
121                                 h.Buf.SetCurCursor(c.Num)
122                                 h.Cursor = c
123                                 if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
124                                         innerSuccess = innerSuccess && h.execAction(a, names[i], j)
125                                 } else {
126                                         break
127                                 }
128                         }
129                         // if the action changed the current pane, update the reference
130                         h = MainTab().CurPane()
131                         success = innerSuccess
132                 }
133                 return true
134         }
135 }
136
137 // BufMapMouse maps a mouse event to an action
138 func BufMapMouse(k MouseEvent, action string) {
139         if f, ok := BufMouseActions[action]; ok {
140                 BufMouseBindings[k] = f
141         } else {
142                 delete(BufMouseBindings, k)
143                 BufMapKey(k, action)
144         }
145 }
146
147 // BufUnmap unmaps a key or mouse event from any action
148 func BufUnmap(k Event) {
149         delete(BufKeyBindings, k)
150         delete(BufKeyStrings, k)
151
152         switch e := k.(type) {
153         case MouseEvent:
154                 delete(BufMouseBindings, e)
155         }
156 }
157
158 // The BufPane connects the buffer and the window
159 // It provides a cursor (or multiple) and defines a set of actions
160 // that can be taken on the buffer
161 // The ActionHandler can access the window for necessary info about
162 // visual positions for mouse clicks and scrolling
163 type BufPane struct {
164         display.BWindow
165
166         Buf *buffer.Buffer
167
168         Cursor *buffer.Cursor // the active cursor
169
170         // Since tcell doesn't differentiate between a mouse release event
171         // and a mouse move event with no keys pressed, we need to keep
172         // track of whether or not the mouse was pressed (or not released) last event to determine
173         // mouse release events
174         mouseReleased bool
175
176         // We need to keep track of insert key press toggle
177         isOverwriteMode bool
178         // This stores when the last click was
179         // This is useful for detecting double and triple clicks
180         lastClickTime time.Time
181         lastLoc       buffer.Loc
182
183         // lastCutTime stores when the last ctrl+k was issued.
184         // It is used for clearing the clipboard to replace it with fresh cut lines.
185         lastCutTime time.Time
186
187         // freshClip returns true if the clipboard has never been pasted.
188         freshClip bool
189
190         // Was the last mouse event actually a double click?
191         // Useful for detecting triple clicks -- if a double click is detected
192         // but the last mouse event was actually a double click, it's a triple click
193         doubleClick bool
194         // Same here, just to keep track for mouse move events
195         tripleClick bool
196
197         // Last search stores the last successful search for FindNext and FindPrev
198         lastSearch      string
199         lastSearchRegex bool
200         // Should the current multiple cursor selection search based on word or
201         // based on selection (false for selection, true for word)
202         multiWord bool
203
204         splitID uint64
205         tab     *Tab
206
207         // remember original location of a search in case the search is canceled
208         searchOrig buffer.Loc
209 }
210
211 func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
212         h := new(BufPane)
213         h.Buf = buf
214         h.BWindow = win
215         h.tab = tab
216
217         h.Cursor = h.Buf.GetActiveCursor()
218         h.mouseReleased = true
219
220         config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
221
222         return h
223 }
224
225 func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
226         w := display.NewBufWindow(0, 0, 0, 0, buf)
227         return NewBufPane(buf, w, tab)
228 }
229
230 func (h *BufPane) SetTab(t *Tab) {
231         h.tab = t
232 }
233
234 func (h *BufPane) Tab() *Tab {
235         return h.tab
236 }
237
238 func (h *BufPane) ResizePane(size int) {
239         n := h.tab.GetNode(h.splitID)
240         n.ResizeSplit(size)
241         h.tab.Resize()
242 }
243
244 // PluginCB calls all plugin callbacks with a certain name and
245 // displays an error if there is one and returns the aggregrate
246 // boolean response
247 func (h *BufPane) PluginCB(cb string) bool {
248         b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
249         if err != nil {
250                 screen.TermMessage(err)
251         }
252         return b
253 }
254
255 // PluginCBRune is the same as PluginCB but also passes a rune to
256 // the plugins
257 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
258         b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
259         if err != nil {
260                 screen.TermMessage(err)
261         }
262         return b
263 }
264
265 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
266         h.Buf.Close()
267         h.Buf = b
268         h.BWindow.SetBuffer(b)
269         h.Cursor = b.GetActiveCursor()
270         h.Resize(h.GetView().Width, h.GetView().Height)
271         h.Relocate()
272         // Set mouseReleased to true because we assume the mouse is not being pressed when
273         // the editor is opened
274         h.mouseReleased = true
275         // Set isOverwriteMode to false, because we assume we are in the default mode when editor
276         // is opened
277         h.isOverwriteMode = false
278         h.lastClickTime = time.Time{}
279 }
280
281 func (h *BufPane) ID() uint64 {
282         return h.splitID
283 }
284
285 func (h *BufPane) SetID(i uint64) {
286         h.splitID = i
287 }
288
289 func (h *BufPane) Name() string {
290         n := h.Buf.GetName()
291         if h.Buf.Modified() {
292                 n += " +"
293         }
294         return n
295 }
296
297 // HandleEvent executes the tcell event properly
298 func (h *BufPane) HandleEvent(event tcell.Event) {
299         if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
300                 InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
301                         if canceled {
302                                 h.Buf.DisableReload()
303                         }
304                         if !yes || canceled {
305                                 h.Buf.UpdateModTime()
306                         } else {
307                                 h.Buf.ReOpen()
308                         }
309                 })
310
311         }
312
313         switch e := event.(type) {
314         case *tcell.EventRaw:
315                 re := RawEvent{
316                         esc: e.EscSeq(),
317                 }
318                 h.DoKeyEvent(re)
319         case *tcell.EventPaste:
320                 h.paste(e.Text())
321                 h.Relocate()
322         case *tcell.EventKey:
323                 ke := KeyEvent{
324                         code: e.Key(),
325                         mod:  e.Modifiers(),
326                         r:    e.Rune(),
327                 }
328
329                 done := h.DoKeyEvent(ke)
330                 if !done && e.Key() == tcell.KeyRune {
331                         h.DoRuneInsert(e.Rune())
332                 }
333         case *tcell.EventMouse:
334                 cancel := false
335                 switch e.Buttons() {
336                 case tcell.Button1:
337                         _, my := e.Position()
338                         if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
339                                 cancel = true
340                         }
341                 case tcell.ButtonNone:
342                         // Mouse event with no click
343                         if !h.mouseReleased {
344                                 // Mouse was just released
345
346                                 // mx, my := e.Position()
347                                 // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
348
349                                 // we could finish the selection based on the release location as described
350                                 // below but when the mouse click is within the scroll margin this will
351                                 // cause a scroll and selection even for a simple mouse click which is
352                                 // not good
353                                 // for terminals that don't support mouse motion events, selection via
354                                 // the mouse won't work but this is ok
355
356                                 // Relocating here isn't really necessary because the cursor will
357                                 // be in the right place from the last mouse event
358                                 // However, if we are running in a terminal that doesn't support mouse motion
359                                 // events, this still allows the user to make selections, except only after they
360                                 // release the mouse
361
362                                 // if !h.doubleClick && !h.tripleClick {
363                                 //      h.Cursor.SetSelectionEnd(h.Cursor.Loc)
364                                 // }
365                                 if h.Cursor.HasSelection() {
366                                         h.Cursor.CopySelection(clipboard.PrimaryReg)
367                                 }
368                                 h.mouseReleased = true
369                         }
370                 }
371
372                 if !cancel {
373                         me := MouseEvent{
374                                 btn: e.Buttons(),
375                                 mod: e.Modifiers(),
376                         }
377                         h.DoMouseEvent(me, e)
378                 }
379         }
380         h.Buf.MergeCursors()
381
382         if h.IsActive() {
383                 // Display any gutter messages for this line
384                 c := h.Buf.GetActiveCursor()
385                 none := true
386                 for _, m := range h.Buf.Messages {
387                         if c.Y == m.Start.Y || c.Y == m.End.Y {
388                                 InfoBar.GutterMessage(m.Msg)
389                                 none = false
390                                 break
391                         }
392                 }
393                 if none && InfoBar.HasGutter {
394                         InfoBar.ClearGutter()
395                 }
396         }
397 }
398
399 // DoKeyEvent executes a key event by finding the action it is bound
400 // to and executing it (possibly multiple times for multiple cursors)
401 func (h *BufPane) DoKeyEvent(e Event) bool {
402         if action, ok := BufKeyBindings[e]; ok {
403                 return action(h)
404         }
405         return false
406 }
407
408 func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
409         if name != "Autocomplete" && name != "CycleAutocompleteBack" {
410                 h.Buf.HasSuggestions = false
411         }
412
413         _, isMulti := MultiActions[name]
414         if (!isMulti && cursor == 0) || isMulti {
415                 if h.PluginCB("pre" + name) {
416                         success := action(h)
417                         success = success && h.PluginCB("on"+name)
418
419                         if isMulti {
420                                 if recording_macro {
421                                         if name != "ToggleMacro" && name != "PlayMacro" {
422                                                 curmacro = append(curmacro, action)
423                                         }
424                                 }
425                         }
426
427                         return success
428                 }
429         }
430
431         return false
432 }
433
434 func (h *BufPane) completeAction(action string) {
435         h.PluginCB("on" + action)
436 }
437
438 func (h *BufPane) HasKeyEvent(e Event) bool {
439         _, ok := BufKeyBindings[e]
440         return ok
441 }
442
443 // DoMouseEvent executes a mouse event by finding the action it is bound
444 // to and executing it
445 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
446         if action, ok := BufMouseBindings[e]; ok {
447                 if action(h, te) {
448                         h.Relocate()
449                 }
450                 return true
451         } else if h.HasKeyEvent(e) {
452                 return h.DoKeyEvent(e)
453         }
454         return false
455 }
456
457 // DoRuneInsert inserts a given rune into the current buffer
458 // (possibly multiple times for multiple cursors)
459 func (h *BufPane) DoRuneInsert(r rune) {
460         cursors := h.Buf.GetCursors()
461         for _, c := range cursors {
462                 // Insert a character
463                 h.Buf.SetCurCursor(c.Num)
464                 h.Cursor = c
465                 if !h.PluginCBRune("preRune", r) {
466                         continue
467                 }
468                 if c.HasSelection() {
469                         c.DeleteSelection()
470                         c.ResetSelection()
471                 }
472
473                 if h.isOverwriteMode {
474                         next := c.Loc
475                         next.X++
476                         h.Buf.Replace(c.Loc, next, string(r))
477                 } else {
478                         h.Buf.Insert(c.Loc, string(r))
479                 }
480                 if recording_macro {
481                         curmacro = append(curmacro, r)
482                 }
483                 h.Relocate()
484                 h.PluginCBRune("onRune", r)
485         }
486 }
487
488 func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
489         e := NewBufPaneFromBuf(buf, h.tab)
490         e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
491         MainTab().Panes = append(MainTab().Panes, e)
492         MainTab().Resize()
493         MainTab().SetActive(len(MainTab().Panes) - 1)
494         return e
495 }
496 func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
497         e := NewBufPaneFromBuf(buf, h.tab)
498         e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
499         MainTab().Panes = append(MainTab().Panes, e)
500         MainTab().Resize()
501         MainTab().SetActive(len(MainTab().Panes) - 1)
502         return e
503 }
504
505 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
506         return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
507 }
508 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
509         return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
510 }
511 func (h *BufPane) Close() {
512         h.Buf.Close()
513 }
514
515 func (h *BufPane) SetActive(b bool) {
516         h.BWindow.SetActive(b)
517         if b {
518                 // Display any gutter messages for this line
519                 c := h.Buf.GetActiveCursor()
520                 none := true
521                 for _, m := range h.Buf.Messages {
522                         if c.Y == m.Start.Y || c.Y == m.End.Y {
523                                 InfoBar.GutterMessage(m.Msg)
524                                 none = false
525                                 break
526                         }
527                 }
528                 if none && InfoBar.HasGutter {
529                         InfoBar.ClearGutter()
530                 }
531         }
532
533 }
534
535 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
536 var BufKeyActions = map[string]BufKeyAction{
537         "CursorUp":                  (*BufPane).CursorUp,
538         "CursorDown":                (*BufPane).CursorDown,
539         "CursorPageUp":              (*BufPane).CursorPageUp,
540         "CursorPageDown":            (*BufPane).CursorPageDown,
541         "CursorLeft":                (*BufPane).CursorLeft,
542         "CursorRight":               (*BufPane).CursorRight,
543         "CursorStart":               (*BufPane).CursorStart,
544         "CursorEnd":                 (*BufPane).CursorEnd,
545         "SelectToStart":             (*BufPane).SelectToStart,
546         "SelectToEnd":               (*BufPane).SelectToEnd,
547         "SelectUp":                  (*BufPane).SelectUp,
548         "SelectDown":                (*BufPane).SelectDown,
549         "SelectLeft":                (*BufPane).SelectLeft,
550         "SelectRight":               (*BufPane).SelectRight,
551         "WordRight":                 (*BufPane).WordRight,
552         "WordLeft":                  (*BufPane).WordLeft,
553         "SelectWordRight":           (*BufPane).SelectWordRight,
554         "SelectWordLeft":            (*BufPane).SelectWordLeft,
555         "DeleteWordRight":           (*BufPane).DeleteWordRight,
556         "DeleteWordLeft":            (*BufPane).DeleteWordLeft,
557         "SelectLine":                (*BufPane).SelectLine,
558         "SelectToStartOfLine":       (*BufPane).SelectToStartOfLine,
559         "SelectToStartOfText":       (*BufPane).SelectToStartOfText,
560         "SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
561         "SelectToEndOfLine":         (*BufPane).SelectToEndOfLine,
562         "ParagraphPrevious":         (*BufPane).ParagraphPrevious,
563         "ParagraphNext":             (*BufPane).ParagraphNext,
564         "InsertNewline":             (*BufPane).InsertNewline,
565         "Backspace":                 (*BufPane).Backspace,
566         "Delete":                    (*BufPane).Delete,
567         "InsertTab":                 (*BufPane).InsertTab,
568         "Save":                      (*BufPane).Save,
569         "SaveAll":                   (*BufPane).SaveAll,
570         "SaveAs":                    (*BufPane).SaveAs,
571         "Find":                      (*BufPane).Find,
572         "FindLiteral":               (*BufPane).FindLiteral,
573         "FindNext":                  (*BufPane).FindNext,
574         "FindPrevious":              (*BufPane).FindPrevious,
575         "Center":                    (*BufPane).Center,
576         "Undo":                      (*BufPane).Undo,
577         "Redo":                      (*BufPane).Redo,
578         "Copy":                      (*BufPane).Copy,
579         "CopyLine":                  (*BufPane).CopyLine,
580         "Cut":                       (*BufPane).Cut,
581         "CutLine":                   (*BufPane).CutLine,
582         "DuplicateLine":             (*BufPane).DuplicateLine,
583         "DeleteLine":                (*BufPane).DeleteLine,
584         "MoveLinesUp":               (*BufPane).MoveLinesUp,
585         "MoveLinesDown":             (*BufPane).MoveLinesDown,
586         "IndentSelection":           (*BufPane).IndentSelection,
587         "OutdentSelection":          (*BufPane).OutdentSelection,
588         "Autocomplete":              (*BufPane).Autocomplete,
589         "CycleAutocompleteBack":     (*BufPane).CycleAutocompleteBack,
590         "OutdentLine":               (*BufPane).OutdentLine,
591         "IndentLine":                (*BufPane).IndentLine,
592         "Paste":                     (*BufPane).Paste,
593         "PastePrimary":              (*BufPane).PastePrimary,
594         "SelectAll":                 (*BufPane).SelectAll,
595         "OpenFile":                  (*BufPane).OpenFile,
596         "Start":                     (*BufPane).Start,
597         "End":                       (*BufPane).End,
598         "PageUp":                    (*BufPane).PageUp,
599         "PageDown":                  (*BufPane).PageDown,
600         "SelectPageUp":              (*BufPane).SelectPageUp,
601         "SelectPageDown":            (*BufPane).SelectPageDown,
602         "HalfPageUp":                (*BufPane).HalfPageUp,
603         "HalfPageDown":              (*BufPane).HalfPageDown,
604         "StartOfText":               (*BufPane).StartOfText,
605         "StartOfTextToggle":         (*BufPane).StartOfTextToggle,
606         "StartOfLine":               (*BufPane).StartOfLine,
607         "EndOfLine":                 (*BufPane).EndOfLine,
608         "ToggleHelp":                (*BufPane).ToggleHelp,
609         "ToggleKeyMenu":             (*BufPane).ToggleKeyMenu,
610         "ToggleDiffGutter":          (*BufPane).ToggleDiffGutter,
611         "ToggleRuler":               (*BufPane).ToggleRuler,
612         "ClearStatus":               (*BufPane).ClearStatus,
613         "ShellMode":                 (*BufPane).ShellMode,
614         "CommandMode":               (*BufPane).CommandMode,
615         "ToggleOverwriteMode":       (*BufPane).ToggleOverwriteMode,
616         "Escape":                    (*BufPane).Escape,
617         "Quit":                      (*BufPane).Quit,
618         "QuitAll":                   (*BufPane).QuitAll,
619         "AddTab":                    (*BufPane).AddTab,
620         "PreviousTab":               (*BufPane).PreviousTab,
621         "NextTab":                   (*BufPane).NextTab,
622         "NextSplit":                 (*BufPane).NextSplit,
623         "PreviousSplit":             (*BufPane).PreviousSplit,
624         "Unsplit":                   (*BufPane).Unsplit,
625         "VSplit":                    (*BufPane).VSplitAction,
626         "HSplit":                    (*BufPane).HSplitAction,
627         "ToggleMacro":               (*BufPane).ToggleMacro,
628         "PlayMacro":                 (*BufPane).PlayMacro,
629         "Suspend":                   (*BufPane).Suspend,
630         "ScrollUp":                  (*BufPane).ScrollUpAction,
631         "ScrollDown":                (*BufPane).ScrollDownAction,
632         "SpawnMultiCursor":          (*BufPane).SpawnMultiCursor,
633         "SpawnMultiCursorUp":        (*BufPane).SpawnMultiCursorUp,
634         "SpawnMultiCursorDown":      (*BufPane).SpawnMultiCursorDown,
635         "SpawnMultiCursorSelect":    (*BufPane).SpawnMultiCursorSelect,
636         "RemoveMultiCursor":         (*BufPane).RemoveMultiCursor,
637         "RemoveAllMultiCursors":     (*BufPane).RemoveAllMultiCursors,
638         "SkipMultiCursor":           (*BufPane).SkipMultiCursor,
639         "JumpToMatchingBrace":       (*BufPane).JumpToMatchingBrace,
640         "JumpLine":                  (*BufPane).JumpLine,
641         "Deselect":                  (*BufPane).Deselect,
642         "ClearInfo":                 (*BufPane).ClearInfo,
643         "None":                      (*BufPane).None,
644
645         // This was changed to InsertNewline but I don't want to break backwards compatibility
646         "InsertEnter": (*BufPane).InsertNewline,
647 }
648
649 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
650 var BufMouseActions = map[string]BufMouseAction{
651         "MousePress":       (*BufPane).MousePress,
652         "MouseMultiCursor": (*BufPane).MouseMultiCursor,
653 }
654
655 // MultiActions is a list of actions that should be executed multiple
656 // times if there are multiple cursors (one per cursor)
657 // Generally actions that modify global editor state like quitting or
658 // saving should not be included in this list
659 var MultiActions = map[string]bool{
660         "CursorUp":                  true,
661         "CursorDown":                true,
662         "CursorPageUp":              true,
663         "CursorPageDown":            true,
664         "CursorLeft":                true,
665         "CursorRight":               true,
666         "CursorStart":               true,
667         "CursorEnd":                 true,
668         "SelectToStart":             true,
669         "SelectToEnd":               true,
670         "SelectUp":                  true,
671         "SelectDown":                true,
672         "SelectLeft":                true,
673         "SelectRight":               true,
674         "WordRight":                 true,
675         "WordLeft":                  true,
676         "SelectWordRight":           true,
677         "SelectWordLeft":            true,
678         "DeleteWordRight":           true,
679         "DeleteWordLeft":            true,
680         "SelectLine":                true,
681         "SelectToStartOfLine":       true,
682         "SelectToStartOfText":       true,
683         "SelectToStartOfTextToggle": true,
684         "SelectToEndOfLine":         true,
685         "ParagraphPrevious":         true,
686         "ParagraphNext":             true,
687         "InsertNewline":             true,
688         "Backspace":                 true,
689         "Delete":                    true,
690         "InsertTab":                 true,
691         "FindNext":                  true,
692         "FindPrevious":              true,
693         "CopyLine":                  true,
694         "Copy":                      true,
695         "Cut":                       true,
696         "CutLine":                   true,
697         "DuplicateLine":             true,
698         "DeleteLine":                true,
699         "MoveLinesUp":               true,
700         "MoveLinesDown":             true,
701         "IndentSelection":           true,
702         "OutdentSelection":          true,
703         "OutdentLine":               true,
704         "IndentLine":                true,
705         "Paste":                     true,
706         "PastePrimary":              true,
707         "SelectPageUp":              true,
708         "SelectPageDown":            true,
709         "StartOfLine":               true,
710         "StartOfText":               true,
711         "StartOfTextToggle":         true,
712         "EndOfLine":                 true,
713         "JumpToMatchingBrace":       true,
714 }