7 luar "layeh.com/gopher-luar"
9 lua "github.com/yuin/gopher-lua"
10 "github.com/zyedidia/micro/internal/buffer"
11 "github.com/zyedidia/micro/internal/config"
12 "github.com/zyedidia/micro/internal/display"
13 ulua "github.com/zyedidia/micro/internal/lua"
14 "github.com/zyedidia/micro/internal/screen"
15 "github.com/zyedidia/tcell"
18 type BufKeyAction func(*BufPane) bool
19 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
21 var BufKeyBindings map[Event]BufKeyAction
22 var BufKeyStrings map[Event]string
23 var BufMouseBindings map[MouseEvent]BufMouseAction
26 BufKeyBindings = make(map[Event]BufKeyAction)
27 BufKeyStrings = make(map[Event]string)
28 BufMouseBindings = make(map[MouseEvent]BufMouseAction)
31 func LuaAction(fn string) func(*BufPane) bool {
32 luaFn := strings.Split(fn, ".")
36 plName, plFn := luaFn[0], luaFn[1]
37 pl := config.FindPlugin(plName)
41 return func(h *BufPane) bool {
42 val, err := pl.Call(plFn, luar.New(ulua.L, h))
44 screen.TermMessage(err)
46 if v, ok := val.(lua.LBool); !ok {
54 // BufMapKey maps a key event to an action
55 func BufMapKey(k Event, action string) {
56 BufKeyStrings[k] = action
57 var actionfns []func(*BufPane) bool
65 // TODO: fix problem when complex bindings have these
66 // characters (escape them?)
67 idx := strings.IndexAny(action, "&|,")
71 types = append(types, action[idx])
72 action = action[idx+1:]
74 types = append(types, ' ')
78 var afn func(*BufPane) bool
79 if strings.HasPrefix(a, "command:") {
80 a = strings.SplitN(a, ":", 2)[1]
81 afn = CommandAction(a)
82 names = append(names, "")
83 } else if strings.HasPrefix(a, "command-edit:") {
84 a = strings.SplitN(a, ":", 2)[1]
85 afn = CommandEditAction(a)
86 names = append(names, "")
87 } else if strings.HasPrefix(a, "lua:") {
88 a = strings.SplitN(a, ":", 2)[1]
91 screen.TermMessage("Lua Error:", a, "does not exist")
94 split := strings.SplitN(a, ".", 2)
96 a = strings.Title(split[0]) + strings.Title(split[1])
101 names = append(names, a)
102 } else if f, ok := BufKeyActions[a]; ok {
104 names = append(names, a)
106 screen.TermMessage("Error:", a, "does not exist")
109 actionfns = append(actionfns, afn)
111 BufKeyBindings[k] = func(h *BufPane) bool {
112 cursors := h.Buf.GetCursors()
114 for i, a := range actionfns {
115 for j, c := range cursors {
116 h.Buf.SetCurCursor(c.Num)
118 if i == 0 || (success && types[i-1] == '&') || (!success && types[i-1] == '|') || (types[i-1] == ',') {
119 success = h.execAction(a, names[i], j)
129 // BufMapMouse maps a mouse event to an action
130 func BufMapMouse(k MouseEvent, action string) {
131 if f, ok := BufMouseActions[action]; ok {
132 BufMouseBindings[k] = f
134 delete(BufMouseBindings, k)
139 // The BufPane connects the buffer and the window
140 // It provides a cursor (or multiple) and defines a set of actions
141 // that can be taken on the buffer
142 // The ActionHandler can access the window for necessary info about
143 // visual positions for mouse clicks and scrolling
144 type BufPane struct {
149 Cursor *buffer.Cursor // the active cursor
151 // Since tcell doesn't differentiate between a mouse release event
152 // and a mouse move event with no keys pressed, we need to keep
153 // track of whether or not the mouse was pressed (or not released) last event to determine
154 // mouse release events
157 // We need to keep track of insert key press toggle
159 // This stores when the last click was
160 // This is useful for detecting double and triple clicks
161 lastClickTime time.Time
164 // lastCutTime stores when the last ctrl+k was issued.
165 // It is used for clearing the clipboard to replace it with fresh cut lines.
166 lastCutTime time.Time
168 // freshClip returns true if the clipboard has never been pasted.
171 // Was the last mouse event actually a double click?
172 // Useful for detecting triple clicks -- if a double click is detected
173 // but the last mouse event was actually a double click, it's a triple click
175 // Same here, just to keep track for mouse move events
178 // Last search stores the last successful search for FindNext and FindPrev
180 // Should the current multiple cursor selection search based on word or
181 // based on selection (false for selection, true for word)
187 // remember original location of a search in case the search is canceled
188 searchOrig buffer.Loc
191 func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane {
197 h.Cursor = h.Buf.GetActiveCursor()
198 h.mouseReleased = true
200 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
205 func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane {
206 w := display.NewBufWindow(0, 0, 0, 0, buf)
207 return NewBufPane(buf, w, tab)
210 func (h *BufPane) SetTab(t *Tab) {
214 func (h *BufPane) Tab() *Tab {
218 func (h *BufPane) ResizePane(size int) {
219 n := h.tab.GetNode(h.splitID)
224 // PluginCB calls all plugin callbacks with a certain name and
225 // displays an error if there is one and returns the aggregrate
227 func (h *BufPane) PluginCB(cb string) bool {
228 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
230 screen.TermMessage(err)
235 // PluginCBRune is the same as PluginCB but also passes a rune to
237 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
238 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
240 screen.TermMessage(err)
245 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
248 h.BWindow.SetBuffer(b)
249 h.Cursor = b.GetActiveCursor()
250 h.Resize(h.GetView().Width, h.GetView().Height)
252 // Set mouseReleased to true because we assume the mouse is not being pressed when
253 // the editor is opened
254 h.mouseReleased = true
255 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
257 h.isOverwriteMode = false
258 h.lastClickTime = time.Time{}
261 func (h *BufPane) ID() uint64 {
265 func (h *BufPane) SetID(i uint64) {
269 func (h *BufPane) Name() string {
270 return h.Buf.GetName()
273 // HandleEvent executes the tcell event properly
274 func (h *BufPane) HandleEvent(event tcell.Event) {
275 if h.Buf.ExternallyModified() {
276 InfoBar.YNPrompt("The file on disk has changed. Reload file? (y,n)", func(yes, canceled bool) {
277 if !yes || canceled {
278 h.Buf.UpdateModTime()
286 switch e := event.(type) {
287 case *tcell.EventRaw:
292 case *tcell.EventPaste:
295 case *tcell.EventKey:
302 done := h.DoKeyEvent(ke)
303 if !done && e.Key() == tcell.KeyRune {
304 h.DoRuneInsert(e.Rune())
306 case *tcell.EventMouse:
310 _, my := e.Position()
311 if h.Buf.Settings["statusline"].(bool) && my >= h.GetView().Y+h.GetView().Height-1 {
314 case tcell.ButtonNone:
315 // Mouse event with no click
316 if !h.mouseReleased {
317 // Mouse was just released
319 // mx, my := e.Position()
320 // mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
322 // we could finish the selection based on the release location as described
323 // below but when the mouse click is within the scroll margin this will
324 // cause a scroll and selection even for a simple mouse click which is
326 // for terminals that don't support mouse motion events, selection via
327 // the mouse won't work but this is ok
329 // Relocating here isn't really necessary because the cursor will
330 // be in the right place from the last mouse event
331 // However, if we are running in a terminal that doesn't support mouse motion
332 // events, this still allows the user to make selections, except only after they
335 // if !h.doubleClick && !h.tripleClick {
336 // h.Cursor.Loc = mouseLoc
337 // h.Cursor.SetSelectionEnd(h.Cursor.Loc)
338 // h.Cursor.CopySelection("primary")
340 h.mouseReleased = true
349 h.DoMouseEvent(me, e)
355 // Display any gutter messages for this line
356 c := h.Buf.GetActiveCursor()
358 for _, m := range h.Buf.Messages {
359 if c.Y == m.Start.Y || c.Y == m.End.Y {
360 InfoBar.GutterMessage(m.Msg)
365 if none && InfoBar.HasGutter {
366 InfoBar.ClearGutter()
371 // DoKeyEvent executes a key event by finding the action it is bound
372 // to and executing it (possibly multiple times for multiple cursors)
373 func (h *BufPane) DoKeyEvent(e Event) bool {
374 if action, ok := BufKeyBindings[e]; ok {
380 func (h *BufPane) execAction(action func(*BufPane) bool, name string, cursor int) bool {
381 if name != "Autocomplete" && name != "CycleAutocompleteBack" {
382 h.Buf.HasSuggestions = false
385 _, isMulti := MultiActions[name]
386 if (!isMulti && cursor == 0) || isMulti {
387 if h.PluginCB("pre" + name) {
389 success = success && h.PluginCB("on"+name)
393 if name != "ToggleMacro" && name != "PlayMacro" {
394 curmacro = append(curmacro, action)
406 func (h *BufPane) completeAction(action string) {
407 h.PluginCB("on" + action)
410 func (h *BufPane) HasKeyEvent(e Event) bool {
411 _, ok := BufKeyBindings[e]
415 // DoMouseEvent executes a mouse event by finding the action it is bound
416 // to and executing it
417 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
418 if action, ok := BufMouseBindings[e]; ok {
423 } else if h.HasKeyEvent(e) {
424 return h.DoKeyEvent(e)
429 // DoRuneInsert inserts a given rune into the current buffer
430 // (possibly multiple times for multiple cursors)
431 func (h *BufPane) DoRuneInsert(r rune) {
432 cursors := h.Buf.GetCursors()
433 for _, c := range cursors {
434 // Insert a character
435 h.Buf.SetCurCursor(c.Num)
437 if !h.PluginCBRune("preRune", r) {
440 if c.HasSelection() {
445 if h.isOverwriteMode {
448 h.Buf.Replace(c.Loc, next, string(r))
450 h.Buf.Insert(c.Loc, string(r))
453 curmacro = append(curmacro, r)
455 h.PluginCBRune("onRune", r)
459 func (h *BufPane) VSplitIndex(buf *buffer.Buffer, right bool) *BufPane {
460 e := NewBufPaneFromBuf(buf, h.tab)
461 e.splitID = MainTab().GetNode(h.splitID).VSplit(right)
462 MainTab().Panes = append(MainTab().Panes, e)
464 MainTab().SetActive(len(MainTab().Panes) - 1)
467 func (h *BufPane) HSplitIndex(buf *buffer.Buffer, bottom bool) *BufPane {
468 e := NewBufPaneFromBuf(buf, h.tab)
469 e.splitID = MainTab().GetNode(h.splitID).HSplit(bottom)
470 MainTab().Panes = append(MainTab().Panes, e)
472 MainTab().SetActive(len(MainTab().Panes) - 1)
476 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
477 return h.VSplitIndex(buf, h.Buf.Settings["splitright"].(bool))
479 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
480 return h.HSplitIndex(buf, h.Buf.Settings["splitbottom"].(bool))
482 func (h *BufPane) Close() {
486 func (h *BufPane) SetActive(b bool) {
487 h.BWindow.SetActive(b)
489 // Display any gutter messages for this line
490 c := h.Buf.GetActiveCursor()
492 for _, m := range h.Buf.Messages {
493 if c.Y == m.Start.Y || c.Y == m.End.Y {
494 InfoBar.GutterMessage(m.Msg)
499 if none && InfoBar.HasGutter {
500 InfoBar.ClearGutter()
506 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
507 var BufKeyActions = map[string]BufKeyAction{
508 "CursorUp": (*BufPane).CursorUp,
509 "CursorDown": (*BufPane).CursorDown,
510 "CursorPageUp": (*BufPane).CursorPageUp,
511 "CursorPageDown": (*BufPane).CursorPageDown,
512 "CursorLeft": (*BufPane).CursorLeft,
513 "CursorRight": (*BufPane).CursorRight,
514 "CursorStart": (*BufPane).CursorStart,
515 "CursorEnd": (*BufPane).CursorEnd,
516 "SelectToStart": (*BufPane).SelectToStart,
517 "SelectToEnd": (*BufPane).SelectToEnd,
518 "SelectUp": (*BufPane).SelectUp,
519 "SelectDown": (*BufPane).SelectDown,
520 "SelectLeft": (*BufPane).SelectLeft,
521 "SelectRight": (*BufPane).SelectRight,
522 "WordRight": (*BufPane).WordRight,
523 "WordLeft": (*BufPane).WordLeft,
524 "SelectWordRight": (*BufPane).SelectWordRight,
525 "SelectWordLeft": (*BufPane).SelectWordLeft,
526 "DeleteWordRight": (*BufPane).DeleteWordRight,
527 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
528 "SelectLine": (*BufPane).SelectLine,
529 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
530 "SelectToStartOfText": (*BufPane).SelectToStartOfText,
531 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
532 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
533 "ParagraphNext": (*BufPane).ParagraphNext,
534 "InsertNewline": (*BufPane).InsertNewline,
535 "Backspace": (*BufPane).Backspace,
536 "Delete": (*BufPane).Delete,
537 "InsertTab": (*BufPane).InsertTab,
538 "Save": (*BufPane).Save,
539 "SaveAll": (*BufPane).SaveAll,
540 "SaveAs": (*BufPane).SaveAs,
541 "Find": (*BufPane).Find,
542 "FindNext": (*BufPane).FindNext,
543 "FindPrevious": (*BufPane).FindPrevious,
544 "Center": (*BufPane).Center,
545 "Undo": (*BufPane).Undo,
546 "Redo": (*BufPane).Redo,
547 "Copy": (*BufPane).Copy,
548 "Cut": (*BufPane).Cut,
549 "CutLine": (*BufPane).CutLine,
550 "DuplicateLine": (*BufPane).DuplicateLine,
551 "DeleteLine": (*BufPane).DeleteLine,
552 "MoveLinesUp": (*BufPane).MoveLinesUp,
553 "MoveLinesDown": (*BufPane).MoveLinesDown,
554 "IndentSelection": (*BufPane).IndentSelection,
555 "OutdentSelection": (*BufPane).OutdentSelection,
556 "Autocomplete": (*BufPane).Autocomplete,
557 "CycleAutocompleteBack": (*BufPane).CycleAutocompleteBack,
558 "OutdentLine": (*BufPane).OutdentLine,
559 "Paste": (*BufPane).Paste,
560 "PastePrimary": (*BufPane).PastePrimary,
561 "SelectAll": (*BufPane).SelectAll,
562 "OpenFile": (*BufPane).OpenFile,
563 "Start": (*BufPane).Start,
564 "End": (*BufPane).End,
565 "PageUp": (*BufPane).PageUp,
566 "PageDown": (*BufPane).PageDown,
567 "SelectPageUp": (*BufPane).SelectPageUp,
568 "SelectPageDown": (*BufPane).SelectPageDown,
569 "HalfPageUp": (*BufPane).HalfPageUp,
570 "HalfPageDown": (*BufPane).HalfPageDown,
571 "StartOfText": (*BufPane).StartOfText,
572 "StartOfLine": (*BufPane).StartOfLine,
573 "EndOfLine": (*BufPane).EndOfLine,
574 "ToggleHelp": (*BufPane).ToggleHelp,
575 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
576 "ToggleRuler": (*BufPane).ToggleRuler,
577 "ClearStatus": (*BufPane).ClearStatus,
578 "ShellMode": (*BufPane).ShellMode,
579 "CommandMode": (*BufPane).CommandMode,
580 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
581 "Escape": (*BufPane).Escape,
582 "Quit": (*BufPane).Quit,
583 "QuitAll": (*BufPane).QuitAll,
584 "AddTab": (*BufPane).AddTab,
585 "PreviousTab": (*BufPane).PreviousTab,
586 "NextTab": (*BufPane).NextTab,
587 "NextSplit": (*BufPane).NextSplit,
588 "PreviousSplit": (*BufPane).PreviousSplit,
589 "Unsplit": (*BufPane).Unsplit,
590 "VSplit": (*BufPane).VSplitAction,
591 "HSplit": (*BufPane).HSplitAction,
592 "ToggleMacro": (*BufPane).ToggleMacro,
593 "PlayMacro": (*BufPane).PlayMacro,
594 "Suspend": (*BufPane).Suspend,
595 "ScrollUp": (*BufPane).ScrollUpAction,
596 "ScrollDown": (*BufPane).ScrollDownAction,
597 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
598 "SpawnMultiCursorUp": (*BufPane).SpawnMultiCursorUp,
599 "SpawnMultiCursorDown": (*BufPane).SpawnMultiCursorDown,
600 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
601 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
602 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
603 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
604 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
605 "None": (*BufPane).None,
607 // This was changed to InsertNewline but I don't want to break backwards compatibility
608 "InsertEnter": (*BufPane).InsertNewline,
611 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
612 var BufMouseActions = map[string]BufMouseAction{
613 "MousePress": (*BufPane).MousePress,
614 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
617 // MultiActions is a list of actions that should be executed multiple
618 // times if there are multiple cursors (one per cursor)
619 // Generally actions that modify global editor state like quitting or
620 // saving should not be included in this list
621 var MultiActions = map[string]bool{
624 "CursorPageUp": true,
625 "CursorPageDown": true,
630 "SelectToStart": true,
638 "SelectWordRight": true,
639 "SelectWordLeft": true,
640 "DeleteWordRight": true,
641 "DeleteWordLeft": true,
643 "SelectToStartOfLine": true,
644 "SelectToStartOfText": true,
645 "SelectToEndOfLine": true,
646 "ParagraphPrevious": true,
647 "ParagraphNext": true,
648 "InsertNewline": true,
653 "FindPrevious": true,
656 "DuplicateLine": true,
659 "MoveLinesDown": true,
660 "IndentSelection": true,
661 "OutdentSelection": true,
664 "PastePrimary": true,
665 "SelectPageUp": true,
666 "SelectPageDown": true,
670 "JumpToMatchingBrace": true,