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