]> git.lizzy.rs Git - micro.git/blobdiff - internal/action/bufpane.go
Filename completion for all non-command prompts
[micro.git] / internal / action / bufpane.go
index 39b6c145148770e930884d6034ae289f439f2a5d..d63f0a2056a8a6c06c303121cc1dfc35a27adcb0 100644 (file)
@@ -6,6 +6,7 @@ import (
 
        luar "layeh.com/gopher-luar"
 
+       lua "github.com/yuin/gopher-lua"
        "github.com/zyedidia/micro/internal/buffer"
        "github.com/zyedidia/micro/internal/config"
        "github.com/zyedidia/micro/internal/display"
@@ -27,21 +28,104 @@ func init() {
        BufMouseBindings = make(map[MouseEvent]BufMouseAction)
 }
 
+func LuaAction(fn string) func(*BufPane) bool {
+       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
+       }
+       return 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)
+               }
+       }
+}
+
 // BufMapKey maps a key event to an action
 func BufMapKey(k Event, action string) {
-       if strings.HasPrefix(action, "command:") {
-               action = strings.SplitN(action, ":", 2)[1]
-               BufKeyStrings[k] = action
-               BufKeyBindings[k] = CommandAction(action)
-       } else if strings.HasPrefix(action, "command-edit:") {
-               action = strings.SplitN(action, ":", 2)[1]
-               BufKeyStrings[k] = action
-               BufKeyBindings[k] = CommandEditAction(action)
-       } else if f, ok := BufKeyActions[action]; ok {
-               BufKeyStrings[k] = action
-               BufKeyBindings[k] = f
-       } else {
-               screen.TermMessage("Error:", action, "does not exist")
+       BufKeyStrings[k] = action
+       var actionfns []func(*BufPane) bool
+       var names []string
+       var types []byte
+       for i := 0; ; i++ {
+               if action == "" {
+                       break
+               }
+
+               // TODO: fix problem when complex bindings have these
+               // characters (escape them?)
+               idx := strings.IndexAny(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 func(*BufPane) bool
+               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)
+                       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 {
+                       screen.TermMessage("Error:", a, "does not exist")
+                       continue
+               }
+               actionfns = append(actionfns, afn)
+       }
+       BufKeyBindings[k] = func(h *BufPane) bool {
+               cursors := h.Buf.GetCursors()
+               success := true
+               for i, a := range actionfns {
+                       for j, c := range cursors {
+                               if c == nil {
+                                       continue
+                               }
+                               h.Buf.SetCurCursor(c.Num)
+                               h.Cursor = c
+                               if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
+                                       success = h.execAction(a, names[i], j)
+                               } else {
+                                       break
+                               }
+                       }
+               }
+               return true
        }
 }
 
@@ -101,15 +185,17 @@ type BufPane struct {
        multiWord bool
 
        splitID uint64
+       tab     *Tab
 
        // remember original location of a search in case the search is canceled
        searchOrig buffer.Loc
 }
 
-func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
+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.mouseReleased = true
@@ -119,9 +205,44 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
        return h
 }
 
-func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
+func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
        w := display.NewBufWindow(0, 0, 0, 0, buf)
-       return NewBufPane(buf, w)
+       return NewBufPane(buf, w, tab)
+}
+
+func (h *BufPane) SetTab(t *Tab) {
+       h.tab = t
+}
+
+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 aggregrate
+// boolean response
+func (h *BufPane) PluginCB(cb string) bool {
+       b, err := config.RunPluginFnBool(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(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
+       if err != nil {
+               screen.TermMessage(err)
+       }
+       return b
 }
 
 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
@@ -130,8 +251,6 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
        h.BWindow.SetBuffer(b)
        h.Cursor = b.GetActiveCursor()
        h.Resize(h.GetView().Width, h.GetView().Height)
-       v := new(display.View)
-       h.SetView(v)
        h.Relocate()
        // Set mouseReleased to true because we assume the mouse is not being pressed when
        // the editor is opened
@@ -155,14 +274,30 @@ func (h *BufPane) Name() string {
 }
 
 // HandleEvent executes the tcell event properly
-// TODO: multiple actions bound to one key
 func (h *BufPane) HandleEvent(event tcell.Event) {
+       if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
+               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.Buf.ReOpen()
+                       }
+               })
+
+       }
+
        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{
                        code: e.Key(),
@@ -175,14 +310,27 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
                        h.DoRuneInsert(e.Rune())
                }
        case *tcell.EventMouse:
+               cancel := false
                switch e.Buttons() {
+               case tcell.Button1:
+                       _, my := e.Position()
+                       if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
+                               cancel = true
+                       }
                case tcell.ButtonNone:
                        // Mouse event with no click
                        if !h.mouseReleased {
                                // Mouse was just released
 
-                               mx, my := e.Position()
-                               mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
+                               // mx, my := e.Position()
+                               // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
+
+                               // we could finish the selection based on the release location as described
+                               // below but when the mouse click is within the scroll margin this will
+                               // cause a scroll and selection even for a simple mouse click which is
+                               // not good
+                               // for terminals that don't support mouse motion events, selection via
+                               // the mouse won't work but this is ok
 
                                // Relocating here isn't really necessary because the cursor will
                                // be in the right place from the last mouse event
@@ -190,35 +338,39 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
                                // events, this still allows the user to make selections, except only after they
                                // release the mouse
 
-                               if !h.doubleClick && !h.tripleClick {
-                                       h.Cursor.Loc = mouseLoc
-                                       h.Cursor.SetSelectionEnd(h.Cursor.Loc)
-                                       h.Cursor.CopySelection("primary")
-                               }
+                               // if !h.doubleClick && !h.tripleClick {
+                               //      h.Cursor.Loc = mouseLoc
+                               //      h.Cursor.SetSelectionEnd(h.Cursor.Loc)
+                               //      h.Cursor.CopySelection("primary")
+                               // }
                                h.mouseReleased = true
                        }
                }
 
-               me := MouseEvent{
-                       btn: e.Buttons(),
-                       mod: e.Modifiers(),
+               if !cancel {
+                       me := MouseEvent{
+                               btn: e.Buttons(),
+                               mod: e.Modifiers(),
+                       }
+                       h.DoMouseEvent(me, e)
                }
-               h.DoMouseEvent(me, e)
        }
        h.Buf.MergeCursors()
 
-       // 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 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()
                }
-       }
-       if none && InfoBar.HasGutter {
-               InfoBar.ClearGutter()
        }
 }
 
@@ -226,28 +378,41 @@ func (h *BufPane) HandleEvent(event tcell.Event) {
 // to and executing it (possibly multiple times for multiple cursors)
 func (h *BufPane) DoKeyEvent(e Event) bool {
        if action, ok := BufKeyBindings[e]; ok {
-               estr := BufKeyStrings[e]
-               for _, s := range MultiActions {
-                       if s == estr {
-                               cursors := h.Buf.GetCursors()
-                               for _, c := range cursors {
-                                       h.Buf.SetCurCursor(c.Num)
-                                       h.Cursor = c
-                                       if action(h) {
-                                               h.Relocate()
+               return action(h)
+       }
+       return false
+}
+
+func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
+       if name != "Autocomplete" && name != "CycleAutocompleteBack" {
+               h.Buf.HasSuggestions = false
+       }
+
+       _, isMulti := MultiActions[name]
+       if (!isMulti && cursor == 0) || isMulti {
+               if h.PluginCB("pre" + name) {
+                       success := action(h)
+                       success = success && h.PluginCB("on"+name)
+
+                       if isMulti {
+                               if recording_macro {
+                                       if name != "ToggleMacro" && name != "PlayMacro" {
+                                               curmacro = append(curmacro, action)
                                        }
                                }
-                               return true
                        }
+
+                       return success
                }
-               if action(h) {
-                       h.Relocate()
-               }
-               return true
        }
+
        return false
 }
 
+func (h *BufPane) completeAction(action string) {
+       h.PluginCB("on" + action)
+}
+
 func (h *BufPane) HasKeyEvent(e Event) bool {
        _, ok := BufKeyBindings[e]
        return ok
@@ -274,6 +439,10 @@ func (h *BufPane) DoRuneInsert(r rune) {
        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()
@@ -286,27 +455,61 @@ func (h *BufPane) DoRuneInsert(r rune) {
                } else {
                        h.Buf.Insert(c.Loc, string(r))
                }
+               if recording_macro {
+                       curmacro = append(curmacro, r)
+               }
+               h.Relocate()
+               h.PluginCBRune("onRune", r)
        }
 }
 
-func (h *BufPane) VSplitBuf(buf *buffer.Buffer) {
-       e := NewBufPaneFromBuf(buf)
-       e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
+func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
+       e := NewBufPaneFromBuf(buf, h.tab)
+       e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
        MainTab().Panes = append(MainTab().Panes, e)
        MainTab().Resize()
        MainTab().SetActive(len(MainTab().Panes) - 1)
+       return e
 }
-func (h *BufPane) HSplitBuf(buf *buffer.Buffer) {
-       e := NewBufPaneFromBuf(buf)
-       e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
+func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
+       e := NewBufPaneFromBuf(buf, h.tab)
+       e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
        MainTab().Panes = append(MainTab().Panes, e)
        MainTab().Resize()
        MainTab().SetActive(len(MainTab().Panes) - 1)
+       return e
+}
+
+func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
+       return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
+}
+func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
+       return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
 }
 func (h *BufPane) Close() {
        h.Buf.Close()
 }
 
+func (h *BufPane) SetActive(b bool) {
+       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()
+               }
+       }
+
+}
+
 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
 var BufKeyActions = map[string]BufKeyAction{
        "CursorUp":               (*BufPane).CursorUp,
@@ -331,6 +534,7 @@ var BufKeyActions = map[string]BufKeyAction{
        "DeleteWordLeft":         (*BufPane).DeleteWordLeft,
        "SelectLine":             (*BufPane).SelectLine,
        "SelectToStartOfLine":    (*BufPane).SelectToStartOfLine,
+       "SelectToStartOfText":    (*BufPane).SelectToStartOfText,
        "SelectToEndOfLine":      (*BufPane).SelectToEndOfLine,
        "ParagraphPrevious":      (*BufPane).ParagraphPrevious,
        "ParagraphNext":          (*BufPane).ParagraphNext,
@@ -356,7 +560,10 @@ var BufKeyActions = map[string]BufKeyAction{
        "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,
@@ -369,12 +576,13 @@ var BufKeyActions = map[string]BufKeyAction{
        "SelectPageDown":         (*BufPane).SelectPageDown,
        "HalfPageUp":             (*BufPane).HalfPageUp,
        "HalfPageDown":           (*BufPane).HalfPageDown,
+       "StartOfText":            (*BufPane).StartOfText,
        "StartOfLine":            (*BufPane).StartOfLine,
        "EndOfLine":              (*BufPane).EndOfLine,
        "ToggleHelp":             (*BufPane).ToggleHelp,
        "ToggleKeyMenu":          (*BufPane).ToggleKeyMenu,
+       "ToggleDiffGutter":       (*BufPane).ToggleDiffGutter,
        "ToggleRuler":            (*BufPane).ToggleRuler,
-       "JumpLine":               (*BufPane).JumpLine,
        "ClearStatus":            (*BufPane).ClearStatus,
        "ShellMode":              (*BufPane).ShellMode,
        "CommandMode":            (*BufPane).CommandMode,
@@ -396,11 +604,14 @@ var BufKeyActions = map[string]BufKeyAction{
        "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,
        "JumpToMatchingBrace":    (*BufPane).JumpToMatchingBrace,
+       "None":                   (*BufPane).None,
 
        // This was changed to InsertNewline but I don't want to break backwards compatibility
        "InsertEnter": (*BufPane).InsertNewline,
@@ -416,52 +627,55 @@ var BufMouseActions = map[string]BufMouseAction{
 // 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 = []string{
-       "CursorUp",
-       "CursorDown",
-       "CursorPageUp",
-       "CursorPageDown",
-       "CursorLeft",
-       "CursorRight",
-       "CursorStart",
-       "CursorEnd",
-       "SelectToStart",
-       "SelectToEnd",
-       "SelectUp",
-       "SelectDown",
-       "SelectLeft",
-       "SelectRight",
-       "WordRight",
-       "WordLeft",
-       "SelectWordRight",
-       "SelectWordLeft",
-       "DeleteWordRight",
-       "DeleteWordLeft",
-       "SelectLine",
-       "SelectToStartOfLine",
-       "SelectToEndOfLine",
-       "ParagraphPrevious",
-       "ParagraphNext",
-       "InsertNewline",
-       "Backspace",
-       "Delete",
-       "InsertTab",
-       "FindNext",
-       "FindPrevious",
-       "Cut",
-       "CutLine",
-       "DuplicateLine",
-       "DeleteLine",
-       "MoveLinesUp",
-       "MoveLinesDown",
-       "IndentSelection",
-       "OutdentSelection",
-       "OutdentLine",
-       "Paste",
-       "PastePrimary",
-       "SelectPageUp",
-       "SelectPageDown",
-       "StartOfLine",
-       "EndOfLine",
-       "JumpToMatchingBrace",
+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,
+       "SelectWordRight":     true,
+       "SelectWordLeft":      true,
+       "DeleteWordRight":     true,
+       "DeleteWordLeft":      true,
+       "SelectLine":          true,
+       "SelectToStartOfLine": true,
+       "SelectToStartOfText": true,
+       "SelectToEndOfLine":   true,
+       "ParagraphPrevious":   true,
+       "ParagraphNext":       true,
+       "InsertNewline":       true,
+       "Backspace":           true,
+       "Delete":              true,
+       "InsertTab":           true,
+       "FindNext":            true,
+       "FindPrevious":        true,
+       "Cut":                 true,
+       "CutLine":             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,
+       "EndOfLine":           true,
+       "JumpToMatchingBrace": true,
 }