8 luar "layeh.com/gopher-luar"
10 lua "github.com/yuin/gopher-lua"
11 "github.com/zyedidia/micro/v2/internal/buffer"
12 "github.com/zyedidia/micro/v2/internal/clipboard"
13 "github.com/zyedidia/micro/v2/internal/config"
14 "github.com/zyedidia/micro/v2/internal/display"
15 ulua "github.com/zyedidia/micro/v2/internal/lua"
16 "github.com/zyedidia/micro/v2/internal/screen"
17 "github.com/zyedidia/tcell"
20 type BufKeyAction func(*BufPane) bool
21 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
23 var BufBindings *KeyTree
24 var BufKeyBindings map[Event]BufKeyAction
25 var BufKeyStrings map[Event]string
26 var BufMouseBindings map[MouseEvent]BufMouseAction
28 func BufKeyActionGeneral(a BufKeyAction) PaneKeyAction {
29 return func(p Pane) bool {
30 return a(p.(*BufPane))
34 func BufMouseActionGeneral(a BufMouseAction) PaneMouseAction {
35 return func(p Pane, me *tcell.EventMouse) bool {
36 return a(p.(*BufPane), me)
41 BufKeyBindings = make(map[Event]BufKeyAction)
42 BufKeyStrings = make(map[Event]string)
43 BufMouseBindings = make(map[MouseEvent]BufMouseAction)
45 BufBindings = NewKeyTree()
48 func LuaAction(fn string) func(*BufPane) bool {
49 luaFn := strings.Split(fn, ".")
53 plName, plFn := luaFn[0], luaFn[1]
54 pl := config.FindPlugin(plName)
58 return func(h *BufPane) bool {
59 val, err := pl.Call(plFn, luar.New(ulua.L, h))
61 screen.TermMessage(err)
63 if v, ok := val.(lua.LBool); !ok {
71 // BufMapKey maps a key event to an action
72 func BufMapKey(k Event, action string) {
73 // BufKeyStrings[k] = action
74 var actionfns []func(*BufPane) bool
82 // TODO: fix problem when complex bindings have these
83 // characters (escape them?)
84 idx := strings.IndexAny(action, "&|,")
88 types = append(types, action[idx])
89 action = action[idx+1:]
91 types = append(types, ' ')
95 var afn func(*BufPane) bool
96 if strings.HasPrefix(a, "command:") {
97 a = strings.SplitN(a, ":", 2)[1]
98 afn = CommandAction(a)
99 names = append(names, "")
100 } else if strings.HasPrefix(a, "command-edit:") {
101 a = strings.SplitN(a, ":", 2)[1]
102 afn = CommandEditAction(a)
103 names = append(names, "")
104 } else if strings.HasPrefix(a, "lua:") {
105 a = strings.SplitN(a, ":", 2)[1]
108 screen.TermMessage("Lua Error:", a, "does not exist")
111 split := strings.SplitN(a, ".", 2)
113 a = strings.Title(split[0]) + strings.Title(split[1])
118 names = append(names, a)
119 } else if f, ok := BufKeyActions[a]; ok {
121 names = append(names, a)
123 screen.TermMessage("Error in bindings: action", a, "does not exist")
126 actionfns = append(actionfns, afn)
128 bufAction := func(h *BufPane) bool {
129 cursors := h.Buf.GetCursors()
131 for i, a := range actionfns {
133 for j, c := range cursors {
137 h.Buf.SetCurCursor(c.Num)
139 if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
140 innerSuccess = innerSuccess && h.execAction(a, names[i], j)
145 // if the action changed the current pane, update the reference
146 h = MainTab().CurPane()
147 success = innerSuccess
152 BufBindings.RegisterKeyBinding(k, BufKeyActionGeneral(bufAction))
155 // BufMapMouse maps a mouse event to an action
156 func BufMapMouse(k MouseEvent, action string) {
157 if f, ok := BufMouseActions[action]; ok {
158 BufBindings.RegisterMouseBinding(k, BufMouseActionGeneral(f))
159 // BufMouseBindings[k] = f
162 // delete(BufMouseBindings, k)
163 // BufMapKey(k, action)
168 // BufUnmap unmaps a key or mouse event from any action
169 func BufUnmap(k Event) {
171 // delete(BufKeyBindings, k)
172 // delete(BufKeyStrings, k)
174 // switch e := k.(type) {
176 // delete(BufMouseBindings, e)
180 // The BufPane connects the buffer and the window
181 // It provides a cursor (or multiple) and defines a set of actions
182 // that can be taken on the buffer
183 // The ActionHandler can access the window for necessary info about
184 // visual positions for mouse clicks and scrolling
185 type BufPane struct {
188 // Buf is the buffer this BufPane views
190 // Bindings stores the association of key events and actions
193 // Cursor is the currently active buffer cursor
194 Cursor *buffer.Cursor
196 // Since tcell doesn't differentiate between a mouse release event
197 // and a mouse move event with no keys pressed, we need to keep
198 // track of whether or not the mouse was pressed (or not released) last event to determine
199 // mouse release events
202 // We need to keep track of insert key press toggle
204 // This stores when the last click was
205 // This is useful for detecting double and triple clicks
206 lastClickTime time.Time
209 // lastCutTime stores when the last ctrl+k was issued.
210 // It is used for clearing the clipboard to replace it with fresh cut lines.
211 lastCutTime time.Time
213 // freshClip returns true if the clipboard has never been pasted.
216 // Was the last mouse event actually a double click?
217 // Useful for detecting triple clicks -- if a double click is detected
218 // but the last mouse event was actually a double click, it's a triple click
220 // Same here, just to keep track for mouse move events
223 // Last search stores the last successful search for FindNext and FindPrev
226 // Should the current multiple cursor selection search based on word or
227 // based on selection (false for selection, true for word)
233 // remember original location of a search in case the search is canceled
234 searchOrig buffer.Loc
237 func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
243 h.Cursor = h.Buf.GetActiveCursor()
244 h.mouseReleased = true
246 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
251 func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
252 w := display.NewBufWindow(0, 0, 0, 0, buf)
253 return NewBufPane(buf, w, tab)
256 func (h *BufPane) SetTab(t *Tab) {
260 func (h *BufPane) Tab() *Tab {
264 func (h *BufPane) ResizePane(size int) {
265 n := h.tab.GetNode(h.splitID)
270 // PluginCB calls all plugin callbacks with a certain name and
271 // displays an error if there is one and returns the aggregrate
273 func (h *BufPane) PluginCB(cb string) bool {
274 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
276 screen.TermMessage(err)
281 // PluginCBRune is the same as PluginCB but also passes a rune to
283 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
284 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
286 screen.TermMessage(err)
291 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
294 h.BWindow.SetBuffer(b)
295 h.Cursor = b.GetActiveCursor()
296 h.Resize(h.GetView().Width, h.GetView().Height)
298 // Set mouseReleased to true because we assume the mouse is not being pressed when
299 // the editor is opened
300 h.mouseReleased = true
301 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
303 h.isOverwriteMode = false
304 h.lastClickTime = time.Time{}
307 func (h *BufPane) ID() uint64 {
311 func (h *BufPane) SetID(i uint64) {
315 func (h *BufPane) Name() string {
317 if h.Buf.Modified() {
323 // HandleEvent executes the tcell event properly
324 func (h *BufPane) HandleEvent(event tcell.Event) {
325 if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
326 InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
328 h.Buf.DisableReload()
330 if !yes || canceled {
331 h.Buf.UpdateModTime()
339 switch e := event.(type) {
340 case *tcell.EventRaw:
345 case *tcell.EventPaste:
348 case *tcell.EventKey:
355 done := h.DoKeyEvent(ke)
356 if !done && e.Key() == tcell.KeyRune {
357 h.DoRuneInsert(e.Rune())
359 case *tcell.EventMouse:
363 _, my := e.Position()
364 if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
367 case tcell.ButtonNone:
368 // Mouse event with no click
369 if !h.mouseReleased {
370 // Mouse was just released
372 // mx, my := e.Position()
373 // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
375 // we could finish the selection based on the release location as described
376 // below but when the mouse click is within the scroll margin this will
377 // cause a scroll and selection even for a simple mouse click which is
379 // for terminals that don't support mouse motion events, selection via
380 // the mouse won't work but this is ok
382 // Relocating here isn't really necessary because the cursor will
383 // be in the right place from the last mouse event
384 // However, if we are running in a terminal that doesn't support mouse motion
385 // events, this still allows the user to make selections, except only after they
388 // if !h.doubleClick && !h.tripleClick {
389 // h.Cursor.SetSelectionEnd(h.Cursor.Loc)
391 if h.Cursor.HasSelection() {
392 h.Cursor.CopySelection(clipboard.PrimaryReg)
394 h.mouseReleased = true
403 h.DoMouseEvent(me, e)
409 // Display any gutter messages for this line
410 c := h.Buf.GetActiveCursor()
412 for _, m := range h.Buf.Messages {
413 if c.Y == m.Start.Y || c.Y == m.End.Y {
414 InfoBar.GutterMessage(m.Msg)
419 if none && InfoBar.HasGutter {
420 InfoBar.ClearGutter()
425 // DoKeyEvent executes a key event by finding the action it is bound
426 // to and executing it (possibly multiple times for multiple cursors)
427 func (h *BufPane) DoKeyEvent(e Event) bool {
428 action, more := BufBindings.NextEvent(e, nil)
429 log.Println("Next event", e, more)
430 if action != nil && !more {
432 BufBindings.ResetEvents()
433 } else if action == nil && !more {
434 BufBindings.ResetEvents()
436 // if action, ok := BufKeyBindings[e]; ok {
442 func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
443 if name != "Autocomplete" && name != "CycleAutocompleteBack" {
444 h.Buf.HasSuggestions = false
447 _, isMulti := MultiActions[name]
448 if (!isMulti && cursor == 0) || isMulti {
449 if h.PluginCB("pre" + name) {
451 success = success && h.PluginCB("on"+name)
455 if name != "ToggleMacro" && name != "PlayMacro" {
456 curmacro = append(curmacro, action)
468 func (h *BufPane) completeAction(action string) {
469 h.PluginCB("on" + action)
472 func (h *BufPane) HasKeyEvent(e Event) bool {
475 // _, ok := BufKeyBindings[e]
479 // DoMouseEvent executes a mouse event by finding the action it is bound
480 // to and executing it
481 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
482 log.Println("DOMOUSEEVENT")
483 action, _ := BufBindings.NextEvent(e, te)
488 BufBindings.ResetEvents()
494 // if action, ok := BufMouseBindings[e]; ok {
495 // if action(h, te) {
499 // } else if h.HasKeyEvent(e) {
500 // return h.DoKeyEvent(e)
505 // DoRuneInsert inserts a given rune into the current buffer
506 // (possibly multiple times for multiple cursors)
507 func (h *BufPane) DoRuneInsert(r rune) {
508 cursors := h.Buf.GetCursors()
509 for _, c := range cursors {
510 // Insert a character
511 h.Buf.SetCurCursor(c.Num)
513 if !h.PluginCBRune("preRune", r) {
516 if c.HasSelection() {
521 if h.isOverwriteMode {
524 h.Buf.Replace(c.Loc, next, string(r))
526 h.Buf.Insert(c.Loc, string(r))
529 curmacro = append(curmacro, r)
532 h.PluginCBRune("onRune", r)
536 func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
537 e := NewBufPaneFromBuf(buf, h.tab)
538 e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
539 MainTab().Panes = append(MainTab().Panes, e)
541 MainTab().SetActive(len(MainTab().Panes) - 1)
544 func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
545 e := NewBufPaneFromBuf(buf, h.tab)
546 e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
547 MainTab().Panes = append(MainTab().Panes, e)
549 MainTab().SetActive(len(MainTab().Panes) - 1)
553 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
554 return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
556 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
557 return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
559 func (h *BufPane) Close() {
563 func (h *BufPane) SetActive(b bool) {
564 h.BWindow.SetActive(b)
566 // Display any gutter messages for this line
567 c := h.Buf.GetActiveCursor()
569 for _, m := range h.Buf.Messages {
570 if c.Y == m.Start.Y || c.Y == m.End.Y {
571 InfoBar.GutterMessage(m.Msg)
576 if none && InfoBar.HasGutter {
577 InfoBar.ClearGutter()
583 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
584 var BufKeyActions = map[string]BufKeyAction{
585 "CursorUp": (*BufPane).CursorUp,
586 "CursorDown": (*BufPane).CursorDown,
587 "CursorPageUp": (*BufPane).CursorPageUp,
588 "CursorPageDown": (*BufPane).CursorPageDown,
589 "CursorLeft": (*BufPane).CursorLeft,
590 "CursorRight": (*BufPane).CursorRight,
591 "CursorStart": (*BufPane).CursorStart,
592 "CursorEnd": (*BufPane).CursorEnd,
593 "SelectToStart": (*BufPane).SelectToStart,
594 "SelectToEnd": (*BufPane).SelectToEnd,
595 "SelectUp": (*BufPane).SelectUp,
596 "SelectDown": (*BufPane).SelectDown,
597 "SelectLeft": (*BufPane).SelectLeft,
598 "SelectRight": (*BufPane).SelectRight,
599 "WordRight": (*BufPane).WordRight,
600 "WordLeft": (*BufPane).WordLeft,
601 "SelectWordRight": (*BufPane).SelectWordRight,
602 "SelectWordLeft": (*BufPane).SelectWordLeft,
603 "DeleteWordRight": (*BufPane).DeleteWordRight,
604 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
605 "SelectLine": (*BufPane).SelectLine,
606 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
607 "SelectToStartOfText": (*BufPane).SelectToStartOfText,
608 "SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
609 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
610 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
611 "ParagraphNext": (*BufPane).ParagraphNext,
612 "InsertNewline": (*BufPane).InsertNewline,
613 "Backspace": (*BufPane).Backspace,
614 "Delete": (*BufPane).Delete,
615 "InsertTab": (*BufPane).InsertTab,
616 "Save": (*BufPane).Save,
617 "SaveAll": (*BufPane).SaveAll,
618 "SaveAs": (*BufPane).SaveAs,
619 "Find": (*BufPane).Find,
620 "FindLiteral": (*BufPane).FindLiteral,
621 "FindNext": (*BufPane).FindNext,
622 "FindPrevious": (*BufPane).FindPrevious,
623 "Center": (*BufPane).Center,
624 "Undo": (*BufPane).Undo,
625 "Redo": (*BufPane).Redo,
626 "Copy": (*BufPane).Copy,
627 "CopyLine": (*BufPane).CopyLine,
628 "Cut": (*BufPane).Cut,
629 "CutLine": (*BufPane).CutLine,
630 "DuplicateLine": (*BufPane).DuplicateLine,
631 "DeleteLine": (*BufPane).DeleteLine,
632 "MoveLinesUp": (*BufPane).MoveLinesUp,
633 "MoveLinesDown": (*BufPane).MoveLinesDown,
634 "IndentSelection": (*BufPane).IndentSelection,
635 "OutdentSelection": (*BufPane).OutdentSelection,
636 "Autocomplete": (*BufPane).Autocomplete,
637 "CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
638 "OutdentLine": (*BufPane).OutdentLine,
639 "IndentLine": (*BufPane).IndentLine,
640 "Paste": (*BufPane).Paste,
641 "PastePrimary": (*BufPane).PastePrimary,
642 "SelectAll": (*BufPane).SelectAll,
643 "OpenFile": (*BufPane).OpenFile,
644 "Start": (*BufPane).Start,
645 "End": (*BufPane).End,
646 "PageUp": (*BufPane).PageUp,
647 "PageDown": (*BufPane).PageDown,
648 "SelectPageUp": (*BufPane).SelectPageUp,
649 "SelectPageDown": (*BufPane).SelectPageDown,
650 "HalfPageUp": (*BufPane).HalfPageUp,
651 "HalfPageDown": (*BufPane).HalfPageDown,
652 "StartOfText": (*BufPane).StartOfText,
653 "StartOfTextToggle": (*BufPane).StartOfTextToggle,
654 "StartOfLine": (*BufPane).StartOfLine,
655 "EndOfLine": (*BufPane).EndOfLine,
656 "ToggleHelp": (*BufPane).ToggleHelp,
657 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
658 "ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
659 "ToggleRuler": (*BufPane).ToggleRuler,
660 "ClearStatus": (*BufPane).ClearStatus,
661 "ShellMode": (*BufPane).ShellMode,
662 "CommandMode": (*BufPane).CommandMode,
663 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
664 "Escape": (*BufPane).Escape,
665 "Quit": (*BufPane).Quit,
666 "QuitAll": (*BufPane).QuitAll,
667 "AddTab": (*BufPane).AddTab,
668 "PreviousTab": (*BufPane).PreviousTab,
669 "NextTab": (*BufPane).NextTab,
670 "NextSplit": (*BufPane).NextSplit,
671 "PreviousSplit": (*BufPane).PreviousSplit,
672 "Unsplit": (*BufPane).Unsplit,
673 "VSplit": (*BufPane).VSplitAction,
674 "HSplit": (*BufPane).HSplitAction,
675 "ToggleMacro": (*BufPane).ToggleMacro,
676 "PlayMacro": (*BufPane).PlayMacro,
677 "Suspend": (*BufPane).Suspend,
678 "ScrollUp": (*BufPane).ScrollUpAction,
679 "ScrollDown": (*BufPane).ScrollDownAction,
680 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
681 "SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
682 "SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
683 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
684 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
685 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
686 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
687 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
688 "JumpLine": (*BufPane).JumpLine,
689 "Deselect": (*BufPane).Deselect,
690 "ClearInfo": (*BufPane).ClearInfo,
691 "None": (*BufPane).None,
693 // This was changed to InsertNewline but I don't want to break backwards compatibility
694 "InsertEnter": (*BufPane).InsertNewline,
697 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
698 var BufMouseActions = map[string]BufMouseAction{
699 "MousePress": (*BufPane).MousePress,
700 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
703 // MultiActions is a list of actions that should be executed multiple
704 // times if there are multiple cursors (one per cursor)
705 // Generally actions that modify global editor state like quitting or
706 // saving should not be included in this list
707 var MultiActions = map[string]bool{
710 "CursorPageUp": true,
711 "CursorPageDown": true,
716 "SelectToStart": true,
724 "SelectWordRight": true,
725 "SelectWordLeft": true,
726 "DeleteWordRight": true,
727 "DeleteWordLeft": true,
729 "SelectToStartOfLine": true,
730 "SelectToStartOfText": true,
731 "SelectToStartOfTextToggle": true,
732 "SelectToEndOfLine": true,
733 "ParagraphPrevious": true,
734 "ParagraphNext": true,
735 "InsertNewline": true,
740 "FindPrevious": true,
745 "DuplicateLine": true,
748 "MoveLinesDown": true,
749 "IndentSelection": true,
750 "OutdentSelection": true,
754 "PastePrimary": true,
755 "SelectPageUp": true,
756 "SelectPageDown": true,
759 "StartOfTextToggle": true,
761 "JumpToMatchingBrace": true,