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