package action import ( "strings" "time" luar "layeh.com/gopher-luar" "github.com/micro-editor/tcell/v2" lua "github.com/yuin/gopher-lua" "github.com/zyedidia/micro/v2/internal/buffer" "github.com/zyedidia/micro/v2/internal/config" "github.com/zyedidia/micro/v2/internal/display" ulua "github.com/zyedidia/micro/v2/internal/lua" "github.com/zyedidia/micro/v2/internal/screen" "github.com/zyedidia/micro/v2/internal/util" ) type BufAction interface{} // BufKeyAction represents an action bound to a key. type BufKeyAction func(*BufPane) bool // BufMouseAction is an action that must be bound to a mouse event. type BufMouseAction func(*BufPane, *tcell.EventMouse) bool // BufBindings stores the bindings for the buffer pane type. var BufBindings *KeyTree // BufKeyActionGeneral makes a general pane action from a BufKeyAction. func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction { return func(p Pane) bool { return a(p.(*BufPane)) } } // BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction. func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction { return func(p Pane, me *tcell.EventMouse) bool { return a(p.(*BufPane), me) } } func init() { BufBindings = NewKeyTree() } // LuaAction makes an action from a lua function. It returns either a BufKeyAction // or a BufMouseAction depending on the event type. func LuaAction(fn string, k Event) BufAction { luaFn := strings.Split(fn, ".") if len(luaFn) <= 1 { return nil } plName, plFn := luaFn[0], luaFn[1] pl := config.FindPlugin(plName) if pl == nil { return nil } var action BufAction switch k.(type) { case KeyEvent, KeySequenceEvent, RawEvent: action = BufKeyAction(func(h *BufPane) bool { val, err := pl.Call(plFn, luar.New(ulua.L, h)) if err != nil { screen.TermMessage(err) } if v, ok := val.(lua.LBool); !ok { return false } else { return bool(v) } }) case MouseEvent: action = BufMouseAction(func(h *BufPane, te *tcell.EventMouse) bool { val, err := pl.Call(plFn, luar.New(ulua.L, h), luar.New(ulua.L, te)) if err != nil { screen.TermMessage(err) } if v, ok := val.(lua.LBool); !ok { return false } else { return bool(v) } }) } return action } // BufMapEvent maps an event to an action func BufMapEvent(k Event, action string) { config.Bindings["buffer"][k.Name()] = action var actionfns []BufAction var names []string var types []byte for i := 0; ; i++ { if action == "" { break } idx := util.IndexAnyUnquoted(action, "&|,") a := action if idx >= 0 { a = action[:idx] types = append(types, action[idx]) action = action[idx+1:] } else { types = append(types, ' ') action = "" } var afn BufAction if strings.HasPrefix(a, "command:") { a = strings.SplitN(a, ":", 2)[1] afn = CommandAction(a) names = append(names, "") } else if strings.HasPrefix(a, "command-edit:") { a = strings.SplitN(a, ":", 2)[1] afn = CommandEditAction(a) names = append(names, "") } else if strings.HasPrefix(a, "lua:") { a = strings.SplitN(a, ":", 2)[1] afn = LuaAction(a, k) if afn == nil { screen.TermMessage("Lua Error:", a, "does not exist") continue } split := strings.SplitN(a, ".", 2) if len(split) > 1 { a = strings.Title(split[0]) + strings.Title(split[1]) } else { a = strings.Title(a) } names = append(names, a) } else if f, ok := BufKeyActions[a]; ok { afn = f names = append(names, a) } else if f, ok := BufMouseActions[a]; ok { afn = f names = append(names, a) } else { screen.TermMessage("Error in bindings: action", a, "does not exist") continue } actionfns = append(actionfns, afn) } bufAction := func(h *BufPane, te *tcell.EventMouse) bool { for i, a := range actionfns { var success bool if _, ok := MultiActions[names[i]]; ok { success = true for _, c := range h.Buf.GetCursors() { h.Buf.SetCurCursor(c.Num) h.Cursor = c success = success && h.execAction(a, names[i], te) } } else { h.Buf.SetCurCursor(0) h.Cursor = h.Buf.GetActiveCursor() success = h.execAction(a, names[i], te) } // if the action changed the current pane, update the reference h = MainTab().CurPane() if h == nil { // stop, in case the current pane is not a BufPane break } if (!success && types[i] == '&') || (success && types[i] == '|') { break } } return true } switch e := k.(type) { case KeyEvent, KeySequenceEvent, RawEvent: BufBindings.RegisterKeyBinding(e, BufKeyActionGeneral(func(h *BufPane) bool { return bufAction(h, nil) })) case MouseEvent: BufBindings.RegisterMouseBinding(e, BufMouseActionGeneral(bufAction)) } } // BufUnmap unmaps a key or mouse event from any action func BufUnmap(k Event) { // TODO // delete(BufKeyBindings, k) // // switch e := k.(type) { // case MouseEvent: // delete(BufMouseBindings, e) // } } // The BufPane connects the buffer and the window // It provides a cursor (or multiple) and defines a set of actions // that can be taken on the buffer // The ActionHandler can access the window for necessary info about // visual positions for mouse clicks and scrolling type BufPane struct { display.BWindow // Buf is the buffer this BufPane views Buf *buffer.Buffer // Bindings stores the association of key events and actions bindings *KeyTree // Cursor is the currently active buffer cursor Cursor *buffer.Cursor // Since tcell doesn't differentiate between a mouse press event // and a mouse move event with button pressed (nor between a mouse // release event and a mouse move event with no buttons pressed), // we need to keep track of whether or not the mouse was previously // pressed, to determine mouse release and mouse drag events. // Moreover, since in case of a release event tcell doesn't tell us // which button was released, we need to keep track of which // (possibly multiple) buttons were pressed previously. mousePressed map[MouseEvent]bool // This stores when the last click was // This is useful for detecting double and triple clicks lastClickTime time.Time lastLoc buffer.Loc // freshClip returns true if one or more lines have been cut to the clipboard // and have never been pasted yet. freshClip bool // Was the last mouse event actually a double click? // Useful for detecting triple clicks -- if a double click is detected // but the last mouse event was actually a double click, it's a triple click DoubleClick bool // Same here, just to keep track for mouse move events TripleClick bool // Should the current multiple cursor selection search based on word or // based on selection (false for selection, true for word) multiWord bool splitID uint64 tab *Tab // remember original location of a search in case the search is canceled searchOrig buffer.Loc // The pane may not yet be fully initialized after its creation // since we may not know the window geometry yet. In such case we finish // its initialization a bit later, after the initial resize. initialized bool } func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h := new(BufPane) h.Buf = buf h.BWindow = win h.tab = tab h.Cursor = h.Buf.GetActiveCursor() h.mousePressed = make(map[MouseEvent]bool) return h } // NewBufPane creates a new buffer pane with the given window. func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h := newBufPane(buf, win, tab) h.finishInitialize() return h } // NewBufPaneFromBuf constructs a new pane from the given buffer and automatically // creates a buf window. func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane { w := display.NewBufWindow(0, 0, 0, 0, buf) h := newBufPane(buf, w, tab) // Postpone finishing initializing the pane until we know the actual geometry // of the buf window. return h } // TODO: make sure splitID and tab are set before finishInitialize is called func (h *BufPane) finishInitialize() { h.initialRelocate() h.initialized = true err := config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h)) if err != nil { screen.TermMessage(err) } } // Resize resizes the pane func (h *BufPane) Resize(width, height int) { h.BWindow.Resize(width, height) if !h.initialized { h.finishInitialize() } } // SetTab sets this pane's tab. func (h *BufPane) SetTab(t *Tab) { h.tab = t } // Tab returns this pane's tab. func (h *BufPane) Tab() *Tab { return h.tab } func (h *BufPane) ResizePane(size int) { n := h.tab.GetNode(h.splitID) n.ResizeSplit(size) h.tab.Resize() } // PluginCB calls all plugin callbacks with a certain name and displays an // error if there is one and returns the aggregate boolean response func (h *BufPane) PluginCB(cb string) bool { b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h)) if err != nil { screen.TermMessage(err) } return b } // PluginCBRune is the same as PluginCB but also passes a rune to the plugins func (h *BufPane) PluginCBRune(cb string, r rune) bool { b, err := config.RunPluginFnBool(h.Buf.Settings, cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r))) if err != nil { screen.TermMessage(err) } return b } func (h *BufPane) resetMouse() { for me := range h.mousePressed { delete(h.mousePressed, me) } } // OpenBuffer opens the given buffer in this pane. func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.Buf.Close() h.Buf = b h.BWindow.SetBuffer(b) h.Cursor = b.GetActiveCursor() h.Resize(h.GetView().Width, h.GetView().Height) h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened h.resetMouse() h.lastClickTime = time.Time{} } // GotoLoc moves the cursor to a new location and adjusts the view accordingly. // Use GotoLoc when the new location may be far away from the current location. func (h *BufPane) GotoLoc(loc buffer.Loc) { sloc := h.SLocFromLoc(loc) d := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc) h.Cursor.GotoLoc(loc) // If the new location is far away from the previous one, // ensure the cursor is at 25% of the window height height := h.BufView().Height if util.Abs(d) >= height { v := h.GetView() v.StartLine = h.Scroll(sloc, -height/4) h.ScrollAdjust() v.StartCol = 0 } h.Relocate() } func (h *BufPane) initialRelocate() { sloc := h.SLocFromLoc(h.Cursor.Loc) height := h.BufView().Height // If the initial cursor location is far away from the beginning // of the buffer, ensure the cursor is at 25% of the window height v := h.GetView() if h.Diff(display.SLoc{0, 0}, sloc) < height { v.StartLine = display.SLoc{0, 0} } else { v.StartLine = h.Scroll(sloc, -height/4) h.ScrollAdjust() } v.StartCol = 0 h.Relocate() } // ID returns this pane's split id. func (h *BufPane) ID() uint64 { return h.splitID } // SetID sets the split ID of this pane. func (h *BufPane) SetID(i uint64) { h.splitID = i } // Name returns the BufPane's name. func (h *BufPane) Name() string { n := h.Buf.GetName() if h.Buf.Modified() { n += " +" } return n } // ReOpen reloads the file opened in the bufpane from disk func (h *BufPane) ReOpen() { h.Buf.ReOpen() h.Relocate() } func (h *BufPane) getReloadSetting() string { reloadSetting := h.Buf.Settings["reload"] return reloadSetting.(string) } // HandleEvent executes the tcell event properly func (h *BufPane) HandleEvent(event tcell.Event) { if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled { reload := h.getReloadSetting() if reload == "prompt" { InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) { if canceled { h.Buf.DisableReload() } if !yes || canceled { h.Buf.UpdateModTime() } else { h.ReOpen() } }) } else if reload == "auto" { h.ReOpen() } else if reload == "disabled" { h.Buf.DisableReload() } else { InfoBar.Message("Invalid reload setting") } } switch e := event.(type) { case *tcell.EventRaw: re := RawEvent{ esc: e.EscSeq(), } h.DoKeyEvent(re) case *tcell.EventPaste: h.paste(e.Text()) h.Relocate() case *tcell.EventKey: ke := keyEvent(e) done := h.DoKeyEvent(ke) if !done && e.Key() == tcell.KeyRune { h.DoRuneInsert(e.Rune()) } case *tcell.EventMouse: if e.Buttons() != tcell.ButtonNone { me := MouseEvent{ btn: e.Buttons(), mod: metaToAlt(e.Modifiers()), state: MousePress, } isDrag := len(h.mousePressed) > 0 if e.Buttons() & ^(tcell.WheelUp|tcell.WheelDown|tcell.WheelLeft|tcell.WheelRight) != tcell.ButtonNone { h.mousePressed[me] = true } if isDrag { me.state = MouseDrag } h.DoMouseEvent(me, e) } else { // Mouse event with no click - mouse was just released. // If there were multiple mouse buttons pressed, we don't know which one // was actually released, so we assume they all were released. pressed := len(h.mousePressed) > 0 for me := range h.mousePressed { delete(h.mousePressed, me) me.state = MouseRelease h.DoMouseEvent(me, e) } if !pressed { // Propagate the mouse release in case the press wasn't for this BufPane Tabs.ResetMouse() } } } h.Buf.MergeCursors() if h.IsActive() { // Display any gutter messages for this line c := h.Buf.GetActiveCursor() none := true for _, m := range h.Buf.Messages { if c.Y == m.Start.Y || c.Y == m.End.Y { InfoBar.GutterMessage(m.Msg) none = false break } } if none && InfoBar.HasGutter { InfoBar.ClearGutter() } } cursors := h.Buf.GetCursors() for _, c := range cursors { if c.NewTrailingWsY != c.Y && (!c.HasSelection() || (c.NewTrailingWsY != c.CurSelection[0].Y && c.NewTrailingWsY != c.CurSelection[1].Y)) { c.NewTrailingWsY = -1 } } } // Bindings returns the current bindings tree for this buffer. func (h *BufPane) Bindings() *KeyTree { if h.bindings != nil { return h.bindings } return BufBindings } // DoKeyEvent executes a key event by finding the action it is bound // to and executing it (possibly multiple times for multiple cursors). // Returns true if the action was executed OR if there are more keys // remaining to process before executing an action (if this is a key // sequence event). Returns false if no action found. func (h *BufPane) DoKeyEvent(e Event) bool { binds := h.Bindings() action, more := binds.NextEvent(e, nil) if action != nil && !more { action(h) binds.ResetEvents() return true } else if action == nil && !more { binds.ResetEvents() } return more } func (h *BufPane) execAction(action BufAction, name string, te *tcell.EventMouse) bool { if name != "Autocomplete" && name != "CycleAutocompleteBack" { h.Buf.HasSuggestions = false } if !h.PluginCB("pre" + name) { return false } var success bool switch a := action.(type) { case BufKeyAction: success = a(h) case BufMouseAction: success = a(h, te) } success = success && h.PluginCB("on"+name) if _, ok := MultiActions[name]; ok { if recordingMacro { if name != "ToggleMacro" && name != "PlayMacro" { curmacro = append(curmacro, action) } } } return success } func (h *BufPane) completeAction(action string) { h.PluginCB("on" + action) } func (h *BufPane) HasKeyEvent(e Event) bool { // TODO return true // _, ok := BufKeyBindings[e] // return ok } // DoMouseEvent executes a mouse event by finding the action it is bound // to and executing it func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool { binds := h.Bindings() action, _ := binds.NextEvent(e, te) if action != nil { action(h) binds.ResetEvents() return true } // TODO return false // if action, ok := BufMouseBindings[e]; ok { // if action(h, te) { // h.Relocate() // } // return true // } else if h.HasKeyEvent(e) { // return h.DoKeyEvent(e) // } // return false } // DoRuneInsert inserts a given rune into the current buffer // (possibly multiple times for multiple cursors) func (h *BufPane) DoRuneInsert(r rune) { cursors := h.Buf.GetCursors() for _, c := range cursors { // Insert a character h.Buf.SetCurCursor(c.Num) h.Cursor = c if !h.PluginCBRune("preRune", r) { continue } if c.HasSelection() { c.DeleteSelection() c.ResetSelection() } if h.Buf.OverwriteMode { next := c.Loc next.X++ h.Buf.Replace(c.Loc, next, string(r)) } else { h.Buf.Insert(c.Loc, string(r)) } if recordingMacro { curmacro = append(curmacro, r) } h.Relocate() h.PluginCBRune("onRune", r) } } // VSplitIndex opens the given buffer in a vertical split on the given side. func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane { e := NewBufPaneFromBuf(buf, h.tab) e.splitID = h.tab.GetNode(h.splitID).VSplit(right) currentPaneIdx := h.tab.GetPane(h.splitID) if right { currentPaneIdx++ } h.tab.AddPane(e, currentPaneIdx) h.tab.Resize() h.tab.SetActive(currentPaneIdx) return e } // HSplitIndex opens the given buffer in a horizontal split on the given side. func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane { e := NewBufPaneFromBuf(buf, h.tab) e.splitID = h.tab.GetNode(h.splitID).HSplit(bottom) currentPaneIdx := h.tab.GetPane(h.splitID) if bottom { currentPaneIdx++ } h.tab.AddPane(e, currentPaneIdx) h.tab.Resize() h.tab.SetActive(currentPaneIdx) return e } // VSplitBuf opens the given buffer in a new vertical split. func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane { return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool)) } // HSplitBuf opens the given buffer in a new horizontal split. func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane { return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool)) } // Close this pane. func (h *BufPane) Close() { h.Buf.Close() } // SetActive marks this pane as active. func (h *BufPane) SetActive(b bool) { if h.IsActive() == b { return } h.BWindow.SetActive(b) if b { // Display any gutter messages for this line c := h.Buf.GetActiveCursor() none := true for _, m := range h.Buf.Messages { if c.Y == m.Start.Y || c.Y == m.End.Y { InfoBar.GutterMessage(m.Msg) none = false break } } if none && InfoBar.HasGutter { InfoBar.ClearGutter() } err := config.RunPluginFn("onSetActive", luar.New(ulua.L, h)) if err != nil { screen.TermMessage(err) } } } // BufKeyActions contains the list of all possible key actions the bufhandler could execute var BufKeyActions = map[string]BufKeyAction{ "CursorUp": (*BufPane).CursorUp, "CursorDown": (*BufPane).CursorDown, "CursorPageUp": (*BufPane).CursorPageUp, "CursorPageDown": (*BufPane).CursorPageDown, "CursorLeft": (*BufPane).CursorLeft, "CursorRight": (*BufPane).CursorRight, "CursorStart": (*BufPane).CursorStart, "CursorEnd": (*BufPane).CursorEnd, "CursorToViewTop": (*BufPane).CursorToViewTop, "CursorToViewCenter": (*BufPane).CursorToViewCenter, "CursorToViewBottom": (*BufPane).CursorToViewBottom, "SelectToStart": (*BufPane).SelectToStart, "SelectToEnd": (*BufPane).SelectToEnd, "SelectUp": (*BufPane).SelectUp, "SelectDown": (*BufPane).SelectDown, "SelectLeft": (*BufPane).SelectLeft, "SelectRight": (*BufPane).SelectRight, "WordRight": (*BufPane).WordRight, "WordLeft": (*BufPane).WordLeft, "SubWordRight": (*BufPane).SubWordRight, "SubWordLeft": (*BufPane).SubWordLeft, "SelectWordRight": (*BufPane).SelectWordRight, "SelectWordLeft": (*BufPane).SelectWordLeft, "SelectSubWordRight": (*BufPane).SelectSubWordRight, "SelectSubWordLeft": (*BufPane).SelectSubWordLeft, "DeleteWordRight": (*BufPane).DeleteWordRight, "DeleteWordLeft": (*BufPane).DeleteWordLeft, "DeleteSubWordRight": (*BufPane).DeleteSubWordRight, "DeleteSubWordLeft": (*BufPane).DeleteSubWordLeft, "SelectLine": (*BufPane).SelectLine, "SelectToStartOfLine": (*BufPane).SelectToStartOfLine, "SelectToStartOfText": (*BufPane).SelectToStartOfText, "SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle, "SelectToEndOfLine": (*BufPane).SelectToEndOfLine, "ParagraphPrevious": (*BufPane).ParagraphPrevious, "ParagraphNext": (*BufPane).ParagraphNext, "SelectToParagraphPrevious": (*BufPane).SelectToParagraphPrevious, "SelectToParagraphNext": (*BufPane).SelectToParagraphNext, "InsertNewline": (*BufPane).InsertNewline, "Backspace": (*BufPane).Backspace, "Delete": (*BufPane).Delete, "InsertTab": (*BufPane).InsertTab, "Save": (*BufPane).Save, "SaveAll": (*BufPane).SaveAll, "SaveAs": (*BufPane).SaveAs, "Find": (*BufPane).Find, "FindLiteral": (*BufPane).FindLiteral, "FindNext": (*BufPane).FindNext, "FindPrevious": (*BufPane).FindPrevious, "DiffNext": (*BufPane).DiffNext, "DiffPrevious": (*BufPane).DiffPrevious, "Center": (*BufPane).Center, "Undo": (*BufPane).Undo, "Redo": (*BufPane).Redo, "Copy": (*BufPane).Copy, "CopyLine": (*BufPane).CopyLine, "Cut": (*BufPane).Cut, "CutLine": (*BufPane).CutLine, "Duplicate": (*BufPane).Duplicate, "DuplicateLine": (*BufPane).DuplicateLine, "DeleteLine": (*BufPane).DeleteLine, "MoveLinesUp": (*BufPane).MoveLinesUp, "MoveLinesDown": (*BufPane).MoveLinesDown, "IndentSelection": (*BufPane).IndentSelection, "OutdentSelection": (*BufPane).OutdentSelection, "Autocomplete": (*BufPane).Autocomplete, "CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack, "OutdentLine": (*BufPane).OutdentLine, "IndentLine": (*BufPane).IndentLine, "Paste": (*BufPane).Paste, "PastePrimary": (*BufPane).PastePrimary, "SelectAll": (*BufPane).SelectAll, "OpenFile": (*BufPane).OpenFile, "Start": (*BufPane).Start, "End": (*BufPane).End, "PageUp": (*BufPane).PageUp, "PageDown": (*BufPane).PageDown, "SelectPageUp": (*BufPane).SelectPageUp, "SelectPageDown": (*BufPane).SelectPageDown, "HalfPageUp": (*BufPane).HalfPageUp, "HalfPageDown": (*BufPane).HalfPageDown, "StartOfText": (*BufPane).StartOfText, "StartOfTextToggle": (*BufPane).StartOfTextToggle, "StartOfLine": (*BufPane).StartOfLine, "EndOfLine": (*BufPane).EndOfLine, "ToggleHelp": (*BufPane).ToggleHelp, "ToggleKeyMenu": (*BufPane).ToggleKeyMenu, "ToggleDiffGutter": (*BufPane).ToggleDiffGutter, "ToggleRuler": (*BufPane).ToggleRuler, "ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch, "UnhighlightSearch": (*BufPane).UnhighlightSearch, "ResetSearch": (*BufPane).ResetSearch, "ClearStatus": (*BufPane).ClearStatus, "ShellMode": (*BufPane).ShellMode, "CommandMode": (*BufPane).CommandMode, "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode, "Escape": (*BufPane).Escape, "Quit": (*BufPane).Quit, "QuitAll": (*BufPane).QuitAll, "ForceQuit": (*BufPane).ForceQuit, "AddTab": (*BufPane).AddTab, "PreviousTab": (*BufPane).PreviousTab, "NextTab": (*BufPane).NextTab, "FirstTab": (*BufPane).FirstTab, "LastTab": (*BufPane).LastTab, "NextSplit": (*BufPane).NextSplit, "PreviousSplit": (*BufPane).PreviousSplit, "FirstSplit": (*BufPane).FirstSplit, "LastSplit": (*BufPane).LastSplit, "Unsplit": (*BufPane).Unsplit, "VSplit": (*BufPane).VSplitAction, "HSplit": (*BufPane).HSplitAction, "ToggleMacro": (*BufPane).ToggleMacro, "PlayMacro": (*BufPane).PlayMacro, "Suspend": (*BufPane).Suspend, "ScrollUp": (*BufPane).ScrollUpAction, "ScrollDown": (*BufPane).ScrollDownAction, "SpawnMultiCursor": (*BufPane).SpawnMultiCursor, "SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp, "SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown, "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect, "RemoveMultiCursor": (*BufPane).RemoveMultiCursor, "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors, "SkipMultiCursor": (*BufPane).SkipMultiCursor, "SkipMultiCursorBack": (*BufPane).SkipMultiCursorBack, "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace, "JumpLine": (*BufPane).JumpLine, "Deselect": (*BufPane).Deselect, "ClearInfo": (*BufPane).ClearInfo, "None": (*BufPane).None, // This was changed to InsertNewline but I don't want to break backwards compatibility "InsertEnter": (*BufPane).InsertNewline, } // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute var BufMouseActions = map[string]BufMouseAction{ "MousePress": (*BufPane).MousePress, "MouseDrag": (*BufPane).MouseDrag, "MouseRelease": (*BufPane).MouseRelease, "MouseMultiCursor": (*BufPane).MouseMultiCursor, } // MultiActions is a list of actions that should be executed multiple // times if there are multiple cursors (one per cursor) // Generally actions that modify global editor state like quitting or // saving should not be included in this list var MultiActions = map[string]bool{ "CursorUp": true, "CursorDown": true, "CursorPageUp": true, "CursorPageDown": true, "CursorLeft": true, "CursorRight": true, "CursorStart": true, "CursorEnd": true, "SelectToStart": true, "SelectToEnd": true, "SelectUp": true, "SelectDown": true, "SelectLeft": true, "SelectRight": true, "WordRight": true, "WordLeft": true, "SubWordRight": true, "SubWordLeft": true, "SelectWordRight": true, "SelectWordLeft": true, "SelectSubWordRight": true, "SelectSubWordLeft": true, "DeleteWordRight": true, "DeleteWordLeft": true, "DeleteSubWordRight": true, "DeleteSubWordLeft": true, "SelectLine": true, "SelectToStartOfLine": true, "SelectToStartOfText": true, "SelectToStartOfTextToggle": true, "SelectToEndOfLine": true, "ParagraphPrevious": true, "ParagraphNext": true, "InsertNewline": true, "Backspace": true, "Delete": true, "InsertTab": true, "FindNext": true, "FindPrevious": true, "CopyLine": true, "Copy": true, "Cut": true, "CutLine": true, "Duplicate": true, "DuplicateLine": true, "DeleteLine": true, "MoveLinesUp": true, "MoveLinesDown": true, "IndentSelection": true, "OutdentSelection": true, "OutdentLine": true, "IndentLine": true, "Paste": true, "PastePrimary": true, "SelectPageUp": true, "SelectPageDown": true, "StartOfLine": true, "StartOfText": true, "StartOfTextToggle": true, "EndOfLine": true, "JumpToMatchingBrace": true, }