7 luar "layeh.com/gopher-luar"
9 lua "github.com/yuin/gopher-lua"
10 "github.com/zyedidia/micro/v2/internal/buffer"
11 "github.com/zyedidia/micro/v2/internal/clipboard"
12 "github.com/zyedidia/micro/v2/internal/config"
13 "github.com/zyedidia/micro/v2/internal/display"
14 ulua "github.com/zyedidia/micro/v2/internal/lua"
15 "github.com/zyedidia/micro/v2/internal/screen"
16 "github.com/zyedidia/tcell/v2"
19 // BufKeyAction represents an action bound to a key.
20 type BufKeyAction func(*BufPane) bool
22 // BufMouseAction is an action that must be bound to a mouse event.
23 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
25 // BufBindings stores the bindings for the buffer pane type.
26 var BufBindings *KeyTree
28 // BufKeyActionGeneral makes a general pane action from a BufKeyAction.
29 func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
30 return func(p Pane) bool {
31 return a(p.(*BufPane))
35 // BufMouseActionGeneral makes a general pane mouse action from a BufKeyAction.
36 func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
37 return func(p Pane, me *tcell.EventMouse) bool {
38 return a(p.(*BufPane), me)
43 BufBindings = NewKeyTree()
46 // LuaAction makes a BufKeyAction from a lua function.
47 func LuaAction(fn string) func(*BufPane) bool {
48 luaFn := strings.Split(fn, ".")
52 plName, plFn := luaFn[0], luaFn[1]
53 pl := config.FindPlugin(plName)
57 return func(h *BufPane) bool {
58 val, err := pl.Call(plFn, luar.New(ulua.L, h))
60 screen.TermMessage(err)
62 if v, ok := val.(lua.LBool); !ok {
70 // BufMapKey maps an event to an action
71 func BufMapEvent(k Event, action string) {
72 config.Bindings["buffer"][k.Name()] = action
74 switch e := k.(type) {
75 case KeyEvent, KeySequenceEvent, RawEvent:
78 bufMapMouse(e, action)
82 func bufMapKey(k Event, action string) {
83 var actionfns []func(*BufPane) bool
91 // TODO: fix problem when complex bindings have these
92 // characters (escape them?)
93 idx := strings.IndexAny(action, "&|,")
97 types = append(types, action[idx])
98 action = action[idx+1:]
100 types = append(types, ' ')
104 var afn func(*BufPane) bool
105 if strings.HasPrefix(a, "command:") {
106 a = strings.SplitN(a, ":", 2)[1]
107 afn = CommandAction(a)
108 names = append(names, "")
109 } else if strings.HasPrefix(a, "command-edit:") {
110 a = strings.SplitN(a, ":", 2)[1]
111 afn = CommandEditAction(a)
112 names = append(names, "")
113 } else if strings.HasPrefix(a, "lua:") {
114 a = strings.SplitN(a, ":", 2)[1]
117 screen.TermMessage("Lua Error:", a, "does not exist")
120 split := strings.SplitN(a, ".", 2)
122 a = strings.Title(split[0]) + strings.Title(split[1])
127 names = append(names, a)
128 } else if f, ok := BufKeyActions[a]; ok {
130 names = append(names, a)
132 screen.TermMessage("Error in bindings: action", a, "does not exist")
135 actionfns = append(actionfns, afn)
137 bufAction := func(h *BufPane) bool {
138 cursors := h.Buf.GetCursors()
140 for i, a := range actionfns {
142 for j, c := range cursors {
146 h.Buf.SetCurCursor(c.Num)
148 if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
149 innerSuccess = innerSuccess && h.execAction(a, names[i], j)
154 // if the action changed the current pane, update the reference
155 h = MainTab().CurPane()
156 success = innerSuccess
161 BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
164 // BufMapMouse maps a mouse event to an action
165 func bufMapMouse(k MouseEvent, action string) {
166 if f, ok := BufMouseActions[action]; ok {
167 BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
170 // delete(BufMouseBindings, k)
175 // BufUnmap unmaps a key or mouse event from any action
176 func BufUnmap(k Event) {
178 // delete(BufKeyBindings, k)
180 // switch e := k.(type) {
182 // delete(BufMouseBindings, e)
186 // The BufPane connects the buffer and the window
187 // It provides a cursor (or multiple) and defines a set of actions
188 // that can be taken on the buffer
189 // The ActionHandler can access the window for necessary info about
190 // visual positions for mouse clicks and scrolling
191 type BufPane struct {
194 // Buf is the buffer this BufPane views
196 // Bindings stores the association of key events and actions
199 // Cursor is the currently active buffer cursor
200 Cursor *buffer.Cursor
202 // Since tcell doesn't differentiate between a mouse release event
203 // and a mouse move event with no keys pressed, we need to keep
204 // track of whether or not the mouse was pressed (or not released) last event to determine
205 // mouse release events
208 // We need to keep track of insert key press toggle
210 // This stores when the last click was
211 // This is useful for detecting double and triple clicks
212 lastClickTime time.Time
215 // lastCutTime stores when the last ctrl+k was issued.
216 // It is used for clearing the clipboard to replace it with fresh cut lines.
217 lastCutTime time.Time
219 // freshClip returns true if the clipboard has never been pasted.
222 // Was the last mouse event actually a double click?
223 // Useful for detecting triple clicks -- if a double click is detected
224 // but the last mouse event was actually a double click, it's a triple click
226 // Same here, just to keep track for mouse move events
229 // Should the current multiple cursor selection search based on word or
230 // based on selection (false for selection, true for word)
236 // remember original location of a search in case the search is canceled
237 searchOrig buffer.Loc
240 // NewBufPane creates a new buffer pane with the given window.
241 func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
247 h.Cursor = h.Buf.GetActiveCursor()
248 h.mouseReleased = true
250 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
255 // NewBufPaneFromBuf constructs a new pane from the given buffer and automatically
256 // creates a buf window.
257 func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
258 w := display.NewBufWindow(0, 0, 0, 0, buf)
259 return NewBufPane(buf, w, tab)
262 // SetTab sets this pane's tab.
263 func (h *BufPane) SetTab(t *Tab) {
267 // Tab returns this pane's tab.
268 func (h *BufPane) Tab() *Tab {
272 func (h *BufPane) ResizePane(size int) {
273 n := h.tab.GetNode(h.splitID)
278 // PluginCB calls all plugin callbacks with a certain name and displays an
279 // error if there is one and returns the aggregrate boolean response
280 func (h *BufPane) PluginCB(cb string) bool {
281 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
283 screen.TermMessage(err)
288 // PluginCBRune is the same as PluginCB but also passes a rune to the plugins
289 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
290 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
292 screen.TermMessage(err)
297 // OpenBuffer opens the given buffer in this pane.
298 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
301 h.BWindow.SetBuffer(b)
302 h.Cursor = b.GetActiveCursor()
303 h.Resize(h.GetView().Width, h.GetView().Height)
305 // Set mouseReleased to true because we assume the mouse is not being
306 // pressed when the editor is opened
307 h.mouseReleased = true
308 // Set isOverwriteMode to false, because we assume we are in the default
309 // mode when editor is opened
310 h.isOverwriteMode = false
311 h.lastClickTime = time.Time{}
314 // ID returns this pane's split id.
315 func (h *BufPane) ID() uint64 {
319 // SetID sets the split ID of this pane.
320 func (h *BufPane) SetID(i uint64) {
324 // Name returns the BufPane's name.
325 func (h *BufPane) Name() string {
327 if h.Buf.Modified() {
333 // HandleEvent executes the tcell event properly
334 func (h *BufPane) HandleEvent(event tcell.Event) {
335 if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
336 InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
338 h.Buf.DisableReload()
340 if !yes || canceled {
341 h.Buf.UpdateModTime()
349 switch e := event.(type) {
350 case *tcell.EventRaw:
355 case *tcell.EventPaste:
358 case *tcell.EventKey:
361 mod: metaToAlt(e.Modifiers()),
365 done := h.DoKeyEvent(ke)
366 if !done && e.Key() == tcell.KeyRune {
367 h.DoRuneInsert(e.Rune())
369 case *tcell.EventMouse:
373 _, my := e.Position()
374 if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
377 case tcell.ButtonNone:
378 // Mouse event with no click
379 if !h.mouseReleased {
380 // Mouse was just released
382 // mx, my := e.Position()
383 // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
385 // we could finish the selection based on the release location as described
386 // below but when the mouse click is within the scroll margin this will
387 // cause a scroll and selection even for a simple mouse click which is
389 // for terminals that don't support mouse motion events, selection via
390 // the mouse won't work but this is ok
392 // Relocating here isn't really necessary because the cursor will
393 // be in the right place from the last mouse event
394 // However, if we are running in a terminal that doesn't support mouse motion
395 // events, this still allows the user to make selections, except only after they
398 // if !h.doubleClick && !h.tripleClick {
399 // h.Cursor.SetSelectionEnd(h.Cursor.Loc)
401 if h.Cursor.HasSelection() {
402 h.Cursor.CopySelection(clipboard.PrimaryReg)
404 h.mouseReleased = true
411 mod: metaToAlt(e.Modifiers()),
413 h.DoMouseEvent(me, e)
419 // Display any gutter messages for this line
420 c := h.Buf.GetActiveCursor()
422 for _, m := range h.Buf.Messages {
423 if c.Y == m.Start.Y || c.Y == m.End.Y {
424 InfoBar.GutterMessage(m.Msg)
429 if none && InfoBar.HasGutter {
430 InfoBar.ClearGutter()
435 // Bindings returns the current bindings tree for this buffer.
436 func (h *BufPane) Bindings() *KeyTree {
437 if h.bindings != nil {
443 // DoKeyEvent executes a key event by finding the action it is bound
444 // to and executing it (possibly multiple times for multiple cursors)
445 func (h *BufPane) DoKeyEvent(e Event) bool {
446 binds := h.Bindings()
447 action, more := binds.NextEvent(e, nil)
448 if action != nil && !more {
452 } else if action == nil && !more {
458 func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
459 if name != "Autocomplete" && name != "CycleAutocompleteBack" {
460 h.Buf.HasSuggestions = false
463 _, isMulti := MultiActions[name]
464 if (!isMulti && cursor == 0) || isMulti {
465 if h.PluginCB("pre" + name) {
467 success = success && h.PluginCB("on"+name)
471 if name != "ToggleMacro" && name != "PlayMacro" {
472 curmacro = append(curmacro, action)
484 func (h *BufPane) completeAction(action string) {
485 h.PluginCB("on" + action)
488 func (h *BufPane) HasKeyEvent(e Event) bool {
491 // _, ok := BufKeyBindings[e]
495 // DoMouseEvent executes a mouse event by finding the action it is bound
496 // to and executing it
497 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
498 binds := h.Bindings()
499 action, _ := binds.NextEvent(e, te)
508 // if action, ok := BufMouseBindings[e]; ok {
509 // if action(h, te) {
513 // } else if h.HasKeyEvent(e) {
514 // return h.DoKeyEvent(e)
519 // DoRuneInsert inserts a given rune into the current buffer
520 // (possibly multiple times for multiple cursors)
521 func (h *BufPane) DoRuneInsert(r rune) {
522 cursors := h.Buf.GetCursors()
523 for _, c := range cursors {
524 // Insert a character
525 h.Buf.SetCurCursor(c.Num)
527 if !h.PluginCBRune("preRune", r) {
530 if c.HasSelection() {
535 if h.isOverwriteMode {
538 h.Buf.Replace(c.Loc, next, string(r))
540 h.Buf.Insert(c.Loc, string(r))
543 curmacro = append(curmacro, r)
546 h.PluginCBRune("onRune", r)
550 // VSplitIndex opens the given buffer in a vertical split on the given side.
551 func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
552 e := NewBufPaneFromBuf(buf, h.tab)
553 e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
554 MainTab().Panes = append(MainTab().Panes, e)
556 MainTab().SetActive(len(MainTab().Panes) - 1)
560 // HSplitIndex opens the given buffer in a horizontal split on the given side.
561 func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
562 e := NewBufPaneFromBuf(buf, h.tab)
563 e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
564 MainTab().Panes = append(MainTab().Panes, e)
566 MainTab().SetActive(len(MainTab().Panes) - 1)
570 // VSplitBuf opens the given buffer in a new vertical split.
571 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
572 return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
575 // HSplitBuf opens the given buffer in a new horizontal split.
576 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
577 return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
581 func (h *BufPane) Close() {
585 // SetActive marks this pane as active.
586 func (h *BufPane) SetActive(b bool) {
587 h.BWindow.SetActive(b)
589 // Display any gutter messages for this line
590 c := h.Buf.GetActiveCursor()
592 for _, m := range h.Buf.Messages {
593 if c.Y == m.Start.Y || c.Y == m.End.Y {
594 InfoBar.GutterMessage(m.Msg)
599 if none && InfoBar.HasGutter {
600 InfoBar.ClearGutter()
606 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
607 var BufKeyActions = map[string]BufKeyAction{
608 "CursorUp": (*BufPane).CursorUp,
609 "CursorDown": (*BufPane).CursorDown,
610 "CursorPageUp": (*BufPane).CursorPageUp,
611 "CursorPageDown": (*BufPane).CursorPageDown,
612 "CursorLeft": (*BufPane).CursorLeft,
613 "CursorRight": (*BufPane).CursorRight,
614 "CursorStart": (*BufPane).CursorStart,
615 "CursorEnd": (*BufPane).CursorEnd,
616 "SelectToStart": (*BufPane).SelectToStart,
617 "SelectToEnd": (*BufPane).SelectToEnd,
618 "SelectUp": (*BufPane).SelectUp,
619 "SelectDown": (*BufPane).SelectDown,
620 "SelectLeft": (*BufPane).SelectLeft,
621 "SelectRight": (*BufPane).SelectRight,
622 "WordRight": (*BufPane).WordRight,
623 "WordLeft": (*BufPane).WordLeft,
624 "SelectWordRight": (*BufPane).SelectWordRight,
625 "SelectWordLeft": (*BufPane).SelectWordLeft,
626 "DeleteWordRight": (*BufPane).DeleteWordRight,
627 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
628 "SelectLine": (*BufPane).SelectLine,
629 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
630 "SelectToStartOfText": (*BufPane).SelectToStartOfText,
631 "SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
632 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
633 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
634 "ParagraphNext": (*BufPane).ParagraphNext,
635 "InsertNewline": (*BufPane).InsertNewline,
636 "Backspace": (*BufPane).Backspace,
637 "Delete": (*BufPane).Delete,
638 "InsertTab": (*BufPane).InsertTab,
639 "Save": (*BufPane).Save,
640 "SaveAll": (*BufPane).SaveAll,
641 "SaveAs": (*BufPane).SaveAs,
642 "Find": (*BufPane).Find,
643 "FindLiteral": (*BufPane).FindLiteral,
644 "FindNext": (*BufPane).FindNext,
645 "FindPrevious": (*BufPane).FindPrevious,
646 "Center": (*BufPane).Center,
647 "Undo": (*BufPane).Undo,
648 "Redo": (*BufPane).Redo,
649 "Copy": (*BufPane).Copy,
650 "CopyLine": (*BufPane).CopyLine,
651 "Cut": (*BufPane).Cut,
652 "CutLine": (*BufPane).CutLine,
653 "DuplicateLine": (*BufPane).DuplicateLine,
654 "DeleteLine": (*BufPane).DeleteLine,
655 "MoveLinesUp": (*BufPane).MoveLinesUp,
656 "MoveLinesDown": (*BufPane).MoveLinesDown,
657 "IndentSelection": (*BufPane).IndentSelection,
658 "OutdentSelection": (*BufPane).OutdentSelection,
659 "Autocomplete": (*BufPane).Autocomplete,
660 "CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
661 "OutdentLine": (*BufPane).OutdentLine,
662 "IndentLine": (*BufPane).IndentLine,
663 "Paste": (*BufPane).Paste,
664 "PastePrimary": (*BufPane).PastePrimary,
665 "SelectAll": (*BufPane).SelectAll,
666 "OpenFile": (*BufPane).OpenFile,
667 "Start": (*BufPane).Start,
668 "End": (*BufPane).End,
669 "PageUp": (*BufPane).PageUp,
670 "PageDown": (*BufPane).PageDown,
671 "SelectPageUp": (*BufPane).SelectPageUp,
672 "SelectPageDown": (*BufPane).SelectPageDown,
673 "HalfPageUp": (*BufPane).HalfPageUp,
674 "HalfPageDown": (*BufPane).HalfPageDown,
675 "StartOfText": (*BufPane).StartOfText,
676 "StartOfTextToggle": (*BufPane).StartOfTextToggle,
677 "StartOfLine": (*BufPane).StartOfLine,
678 "EndOfLine": (*BufPane).EndOfLine,
679 "ToggleHelp": (*BufPane).ToggleHelp,
680 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
681 "ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
682 "ToggleRuler": (*BufPane).ToggleRuler,
683 "ToggleHighlightSearch": (*BufPane).ToggleHighlightSearch,
684 "UnhighlightSearch": (*BufPane).UnhighlightSearch,
685 "ClearStatus": (*BufPane).ClearStatus,
686 "ShellMode": (*BufPane).ShellMode,
687 "CommandMode": (*BufPane).CommandMode,
688 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
689 "Escape": (*BufPane).Escape,
690 "Quit": (*BufPane).Quit,
691 "QuitAll": (*BufPane).QuitAll,
692 "ForceQuit": (*BufPane).ForceQuit,
693 "AddTab": (*BufPane).AddTab,
694 "PreviousTab": (*BufPane).PreviousTab,
695 "NextTab": (*BufPane).NextTab,
696 "NextSplit": (*BufPane).NextSplit,
697 "PreviousSplit": (*BufPane).PreviousSplit,
698 "Unsplit": (*BufPane).Unsplit,
699 "VSplit": (*BufPane).VSplitAction,
700 "HSplit": (*BufPane).HSplitAction,
701 "ToggleMacro": (*BufPane).ToggleMacro,
702 "PlayMacro": (*BufPane).PlayMacro,
703 "Suspend": (*BufPane).Suspend,
704 "ScrollUp": (*BufPane).ScrollUpAction,
705 "ScrollDown": (*BufPane).ScrollDownAction,
706 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
707 "SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
708 "SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
709 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
710 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
711 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
712 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
713 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
714 "JumpLine": (*BufPane).JumpLine,
715 "Deselect": (*BufPane).Deselect,
716 "ClearInfo": (*BufPane).ClearInfo,
717 "None": (*BufPane).None,
719 // This was changed to InsertNewline but I don't want to break backwards compatibility
720 "InsertEnter": (*BufPane).InsertNewline,
723 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
724 var BufMouseActions = map[string]BufMouseAction{
725 "MousePress": (*BufPane).MousePress,
726 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
729 // MultiActions is a list of actions that should be executed multiple
730 // times if there are multiple cursors (one per cursor)
731 // Generally actions that modify global editor state like quitting or
732 // saving should not be included in this list
733 var MultiActions = map[string]bool{
736 "CursorPageUp": true,
737 "CursorPageDown": true,
742 "SelectToStart": true,
750 "SelectWordRight": true,
751 "SelectWordLeft": true,
752 "DeleteWordRight": true,
753 "DeleteWordLeft": true,
755 "SelectToStartOfLine": true,
756 "SelectToStartOfText": true,
757 "SelectToStartOfTextToggle": true,
758 "SelectToEndOfLine": true,
759 "ParagraphPrevious": true,
760 "ParagraphNext": true,
761 "InsertNewline": true,
766 "FindPrevious": true,
771 "DuplicateLine": true,
774 "MoveLinesDown": true,
775 "IndentSelection": true,
776 "OutdentSelection": true,
780 "PastePrimary": true,
781 "SelectPageUp": true,
782 "SelectPageDown": true,
785 "StartOfTextToggle": true,
787 "JumpToMatchingBrace": true,