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"
19 type BufKeyAction func(*BufPane) bool
20 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
22 var BufKeyBindings map[Event]BufKeyAction
23 var BufKeyStrings map[Event]string
24 var BufMouseBindings map[MouseEvent]BufMouseAction
27 BufKeyBindings = make(map[Event]BufKeyAction)
28 BufKeyStrings = make(map[Event]string)
29 BufMouseBindings = make(map[MouseEvent]BufMouseAction)
32 func LuaAction(fn string) func(*BufPane) bool {
33 luaFn := strings.Split(fn, ".")
37 plName, plFn := luaFn[0], luaFn[1]
38 pl := config.FindPlugin(plName)
42 return func(h *BufPane) bool {
43 val, err := pl.Call(plFn, luar.New(ulua.L, h))
45 screen.TermMessage(err)
47 if v, ok := val.(lua.LBool); !ok {
55 // BufMapKey maps a key event to an action
56 func BufMapKey(k Event, action string) {
57 BufKeyStrings[k] = action
58 var actionfns []func(*BufPane) bool
66 // TODO: fix problem when complex bindings have these
67 // characters (escape them?)
68 idx := strings.IndexAny(action, "&|,")
72 types = append(types, action[idx])
73 action = action[idx+1:]
75 types = append(types, ' ')
79 var afn func(*BufPane) bool
80 if strings.HasPrefix(a, "command:") {
81 a = strings.SplitN(a, ":", 2)[1]
82 afn = CommandAction(a)
83 names = append(names, "")
84 } else if strings.HasPrefix(a, "command-edit:") {
85 a = strings.SplitN(a, ":", 2)[1]
86 afn = CommandEditAction(a)
87 names = append(names, "")
88 } else if strings.HasPrefix(a, "lua:") {
89 a = strings.SplitN(a, ":", 2)[1]
92 screen.TermMessage("Lua Error:", a, "does not exist")
95 split := strings.SplitN(a, ".", 2)
97 a = strings.Title(split[0]) + strings.Title(split[1])
102 names = append(names, a)
103 } else if f, ok := BufKeyActions[a]; ok {
105 names = append(names, a)
107 screen.TermMessage("Error in bindings: action", a, "does not exist")
110 actionfns = append(actionfns, afn)
112 BufKeyBindings[k] = func(h *BufPane) bool {
113 cursors := h.Buf.GetCursors()
115 for i, a := range actionfns {
117 for j, c := range cursors {
121 h.Buf.SetCurCursor(c.Num)
123 if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
124 innerSuccess = innerSuccess && h.execAction(a, names[i], j)
129 // if the action changed the current pane, update the reference
130 h = MainTab().CurPane()
131 success = innerSuccess
137 // BufMapMouse maps a mouse event to an action
138 func BufMapMouse(k MouseEvent, action string) {
139 if f, ok := BufMouseActions[action]; ok {
140 BufMouseBindings[k] = f
142 delete(BufMouseBindings, k)
147 // BufUnmap unmaps a key or mouse event from any action
148 func BufUnmap(k Event) {
149 delete(BufKeyBindings, k)
150 delete(BufKeyStrings, k)
152 switch e := k.(type) {
154 delete(BufMouseBindings, e)
158 // The BufPane connects the buffer and the window
159 // It provides a cursor (or multiple) and defines a set of actions
160 // that can be taken on the buffer
161 // The ActionHandler can access the window for necessary info about
162 // visual positions for mouse clicks and scrolling
163 type BufPane struct {
168 Cursor *buffer.Cursor // the active cursor
170 // Since tcell doesn't differentiate between a mouse release event
171 // and a mouse move event with no keys pressed, we need to keep
172 // track of whether or not the mouse was pressed (or not released) last event to determine
173 // mouse release events
176 // We need to keep track of insert key press toggle
178 // This stores when the last click was
179 // This is useful for detecting double and triple clicks
180 lastClickTime time.Time
183 // lastCutTime stores when the last ctrl+k was issued.
184 // It is used for clearing the clipboard to replace it with fresh cut lines.
185 lastCutTime time.Time
187 // freshClip returns true if the clipboard has never been pasted.
190 // Was the last mouse event actually a double click?
191 // Useful for detecting triple clicks -- if a double click is detected
192 // but the last mouse event was actually a double click, it's a triple click
194 // Same here, just to keep track for mouse move events
197 // Last search stores the last successful search for FindNext and FindPrev
200 // Should the current multiple cursor selection search based on word or
201 // based on selection (false for selection, true for word)
207 // remember original location of a search in case the search is canceled
208 searchOrig buffer.Loc
211 func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
217 h.Cursor = h.Buf.GetActiveCursor()
218 h.mouseReleased = true
220 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
225 func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
226 w := display.NewBufWindow(0, 0, 0, 0, buf)
227 return NewBufPane(buf, w, tab)
230 func (h *BufPane) SetTab(t *Tab) {
234 func (h *BufPane) Tab() *Tab {
238 func (h *BufPane) ResizePane(size int) {
239 n := h.tab.GetNode(h.splitID)
244 // PluginCB calls all plugin callbacks with a certain name and
245 // displays an error if there is one and returns the aggregrate
247 func (h *BufPane) PluginCB(cb string) bool {
248 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
250 screen.TermMessage(err)
255 // PluginCBRune is the same as PluginCB but also passes a rune to
257 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
258 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
260 screen.TermMessage(err)
265 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
268 h.BWindow.SetBuffer(b)
269 h.Cursor = b.GetActiveCursor()
270 h.Resize(h.GetView().Width, h.GetView().Height)
272 // Set mouseReleased to true because we assume the mouse is not being pressed when
273 // the editor is opened
274 h.mouseReleased = true
275 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
277 h.isOverwriteMode = false
278 h.lastClickTime = time.Time{}
281 func (h *BufPane) ID() uint64 {
285 func (h *BufPane) SetID(i uint64) {
289 func (h *BufPane) Name() string {
291 if h.Buf.Modified() {
297 // HandleEvent executes the tcell event properly
298 func (h *BufPane) HandleEvent(event tcell.Event) {
299 if h.Buf.ExternallyModified() && !h.Buf.ReloadDisabled {
300 InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n,esc)", func(yes, canceled bool) {
302 h.Buf.DisableReload()
304 if !yes || canceled {
305 h.Buf.UpdateModTime()
313 switch e := event.(type) {
314 case *tcell.EventRaw:
319 case *tcell.EventPaste:
322 case *tcell.EventKey:
329 done := h.DoKeyEvent(ke)
330 if !done && e.Key() == tcell.KeyRune {
331 h.DoRuneInsert(e.Rune())
333 case *tcell.EventMouse:
337 _, my := e.Position()
338 if h.Buf.Type.Kind != buffer.BTInfo.Kind && h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
341 case tcell.ButtonNone:
342 // Mouse event with no click
343 if !h.mouseReleased {
344 // Mouse was just released
346 // mx, my := e.Position()
347 // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
349 // we could finish the selection based on the release location as described
350 // below but when the mouse click is within the scroll margin this will
351 // cause a scroll and selection even for a simple mouse click which is
353 // for terminals that don't support mouse motion events, selection via
354 // the mouse won't work but this is ok
356 // Relocating here isn't really necessary because the cursor will
357 // be in the right place from the last mouse event
358 // However, if we are running in a terminal that doesn't support mouse motion
359 // events, this still allows the user to make selections, except only after they
362 // if !h.doubleClick && !h.tripleClick {
363 // h.Cursor.SetSelectionEnd(h.Cursor.Loc)
365 if h.Cursor.HasSelection() {
366 h.Cursor.CopySelection(clipboard.PrimaryReg)
368 h.mouseReleased = true
377 h.DoMouseEvent(me, e)
383 // Display any gutter messages for this line
384 c := h.Buf.GetActiveCursor()
386 for _, m := range h.Buf.Messages {
387 if c.Y == m.Start.Y || c.Y == m.End.Y {
388 InfoBar.GutterMessage(m.Msg)
393 if none && InfoBar.HasGutter {
394 InfoBar.ClearGutter()
399 // DoKeyEvent executes a key event by finding the action it is bound
400 // to and executing it (possibly multiple times for multiple cursors)
401 func (h *BufPane) DoKeyEvent(e Event) bool {
402 if action, ok := BufKeyBindings[e]; ok {
408 func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
409 if name != "Autocomplete" && name != "CycleAutocompleteBack" {
410 h.Buf.HasSuggestions = false
413 _, isMulti := MultiActions[name]
414 if (!isMulti && cursor == 0) || isMulti {
415 if h.PluginCB("pre" + name) {
417 success = success && h.PluginCB("on"+name)
421 if name != "ToggleMacro" && name != "PlayMacro" {
422 curmacro = append(curmacro, action)
434 func (h *BufPane) completeAction(action string) {
435 h.PluginCB("on" + action)
438 func (h *BufPane) HasKeyEvent(e Event) bool {
439 _, ok := BufKeyBindings[e]
443 // DoMouseEvent executes a mouse event by finding the action it is bound
444 // to and executing it
445 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
446 if action, ok := BufMouseBindings[e]; ok {
451 } else if h.HasKeyEvent(e) {
452 return h.DoKeyEvent(e)
457 // DoRuneInsert inserts a given rune into the current buffer
458 // (possibly multiple times for multiple cursors)
459 func (h *BufPane) DoRuneInsert(r rune) {
460 cursors := h.Buf.GetCursors()
461 for _, c := range cursors {
462 // Insert a character
463 h.Buf.SetCurCursor(c.Num)
465 if !h.PluginCBRune("preRune", r) {
468 if c.HasSelection() {
473 if h.isOverwriteMode {
476 h.Buf.Replace(c.Loc, next, string(r))
478 h.Buf.Insert(c.Loc, string(r))
481 curmacro = append(curmacro, r)
484 h.PluginCBRune("onRune", r)
488 func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
489 e := NewBufPaneFromBuf(buf, h.tab)
490 e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
491 MainTab().Panes = append(MainTab().Panes, e)
493 MainTab().SetActive(len(MainTab().Panes) - 1)
496 func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
497 e := NewBufPaneFromBuf(buf, h.tab)
498 e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
499 MainTab().Panes = append(MainTab().Panes, e)
501 MainTab().SetActive(len(MainTab().Panes) - 1)
505 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
506 return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
508 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
509 return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
511 func (h *BufPane) Close() {
515 func (h *BufPane) SetActive(b bool) {
516 h.BWindow.SetActive(b)
518 // Display any gutter messages for this line
519 c := h.Buf.GetActiveCursor()
521 for _, m := range h.Buf.Messages {
522 if c.Y == m.Start.Y || c.Y == m.End.Y {
523 InfoBar.GutterMessage(m.Msg)
528 if none && InfoBar.HasGutter {
529 InfoBar.ClearGutter()
535 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
536 var BufKeyActions = map[string]BufKeyAction{
537 "CursorUp": (*BufPane).CursorUp,
538 "CursorDown": (*BufPane).CursorDown,
539 "CursorPageUp": (*BufPane).CursorPageUp,
540 "CursorPageDown": (*BufPane).CursorPageDown,
541 "CursorLeft": (*BufPane).CursorLeft,
542 "CursorRight": (*BufPane).CursorRight,
543 "CursorStart": (*BufPane).CursorStart,
544 "CursorEnd": (*BufPane).CursorEnd,
545 "SelectToStart": (*BufPane).SelectToStart,
546 "SelectToEnd": (*BufPane).SelectToEnd,
547 "SelectUp": (*BufPane).SelectUp,
548 "SelectDown": (*BufPane).SelectDown,
549 "SelectLeft": (*BufPane).SelectLeft,
550 "SelectRight": (*BufPane).SelectRight,
551 "WordRight": (*BufPane).WordRight,
552 "WordLeft": (*BufPane).WordLeft,
553 "SelectWordRight": (*BufPane).SelectWordRight,
554 "SelectWordLeft": (*BufPane).SelectWordLeft,
555 "DeleteWordRight": (*BufPane).DeleteWordRight,
556 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
557 "SelectLine": (*BufPane).SelectLine,
558 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
559 "SelectToStartOfText": (*BufPane).SelectToStartOfText,
560 "SelectToStartOfTextToggle": (*BufPane).SelectToStartOfTextToggle,
561 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
562 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
563 "ParagraphNext": (*BufPane).ParagraphNext,
564 "InsertNewline": (*BufPane).InsertNewline,
565 "Backspace": (*BufPane).Backspace,
566 "Delete": (*BufPane).Delete,
567 "InsertTab": (*BufPane).InsertTab,
568 "Save": (*BufPane).Save,
569 "SaveAll": (*BufPane).SaveAll,
570 "SaveAs": (*BufPane).SaveAs,
571 "Find": (*BufPane).Find,
572 "FindLiteral": (*BufPane).FindLiteral,
573 "FindNext": (*BufPane).FindNext,
574 "FindPrevious": (*BufPane).FindPrevious,
575 "Center": (*BufPane).Center,
576 "Undo": (*BufPane).Undo,
577 "Redo": (*BufPane).Redo,
578 "Copy": (*BufPane).Copy,
579 "CopyLine": (*BufPane).CopyLine,
580 "Cut": (*BufPane).Cut,
581 "CutLine": (*BufPane).CutLine,
582 "DuplicateLine": (*BufPane).DuplicateLine,
583 "DeleteLine": (*BufPane).DeleteLine,
584 "MoveLinesUp": (*BufPane).MoveLinesUp,
585 "MoveLinesDown": (*BufPane).MoveLinesDown,
586 "IndentSelection": (*BufPane).IndentSelection,
587 "OutdentSelection": (*BufPane).OutdentSelection,
588 "Autocomplete": (*BufPane).Autocomplete,
589 "CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
590 "OutdentLine": (*BufPane).OutdentLine,
591 "IndentLine": (*BufPane).IndentLine,
592 "Paste": (*BufPane).Paste,
593 "PastePrimary": (*BufPane).PastePrimary,
594 "SelectAll": (*BufPane).SelectAll,
595 "OpenFile": (*BufPane).OpenFile,
596 "Start": (*BufPane).Start,
597 "End": (*BufPane).End,
598 "PageUp": (*BufPane).PageUp,
599 "PageDown": (*BufPane).PageDown,
600 "SelectPageUp": (*BufPane).SelectPageUp,
601 "SelectPageDown": (*BufPane).SelectPageDown,
602 "HalfPageUp": (*BufPane).HalfPageUp,
603 "HalfPageDown": (*BufPane).HalfPageDown,
604 "StartOfText": (*BufPane).StartOfText,
605 "StartOfTextToggle": (*BufPane).StartOfTextToggle,
606 "StartOfLine": (*BufPane).StartOfLine,
607 "EndOfLine": (*BufPane).EndOfLine,
608 "ToggleHelp": (*BufPane).ToggleHelp,
609 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
610 "ToggleDiffGutter": (*BufPane).ToggleDiffGutter,
611 "ToggleRuler": (*BufPane).ToggleRuler,
612 "ClearStatus": (*BufPane).ClearStatus,
613 "ShellMode": (*BufPane).ShellMode,
614 "CommandMode": (*BufPane).CommandMode,
615 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
616 "Escape": (*BufPane).Escape,
617 "Quit": (*BufPane).Quit,
618 "QuitAll": (*BufPane).QuitAll,
619 "AddTab": (*BufPane).AddTab,
620 "PreviousTab": (*BufPane).PreviousTab,
621 "NextTab": (*BufPane).NextTab,
622 "NextSplit": (*BufPane).NextSplit,
623 "PreviousSplit": (*BufPane).PreviousSplit,
624 "Unsplit": (*BufPane).Unsplit,
625 "VSplit": (*BufPane).VSplitAction,
626 "HSplit": (*BufPane).HSplitAction,
627 "ToggleMacro": (*BufPane).ToggleMacro,
628 "PlayMacro": (*BufPane).PlayMacro,
629 "Suspend": (*BufPane).Suspend,
630 "ScrollUp": (*BufPane).ScrollUpAction,
631 "ScrollDown": (*BufPane).ScrollDownAction,
632 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
633 "SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
634 "SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
635 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
636 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
637 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
638 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
639 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
640 "JumpLine": (*BufPane).JumpLine,
641 "Deselect": (*BufPane).Deselect,
642 "ClearInfo": (*BufPane).ClearInfo,
643 "None": (*BufPane).None,
645 // This was changed to InsertNewline but I don't want to break backwards compatibility
646 "InsertEnter": (*BufPane).InsertNewline,
649 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
650 var BufMouseActions = map[string]BufMouseAction{
651 "MousePress": (*BufPane).MousePress,
652 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
655 // MultiActions is a list of actions that should be executed multiple
656 // times if there are multiple cursors (one per cursor)
657 // Generally actions that modify global editor state like quitting or
658 // saving should not be included in this list
659 var MultiActions = map[string]bool{
662 "CursorPageUp": true,
663 "CursorPageDown": true,
668 "SelectToStart": true,
676 "SelectWordRight": true,
677 "SelectWordLeft": true,
678 "DeleteWordRight": true,
679 "DeleteWordLeft": true,
681 "SelectToStartOfLine": true,
682 "SelectToStartOfText": true,
683 "SelectToStartOfTextToggle": true,
684 "SelectToEndOfLine": true,
685 "ParagraphPrevious": true,
686 "ParagraphNext": true,
687 "InsertNewline": true,
692 "FindPrevious": true,
697 "DuplicateLine": true,
700 "MoveLinesDown": true,
701 "IndentSelection": true,
702 "OutdentSelection": true,
706 "PastePrimary": true,
707 "SelectPageUp": true,
708 "SelectPageDown": true,
711 "StartOfTextToggle": true,
713 "JumpToMatchingBrace": true,