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 actions := strings.SplitN(action, ",", -1)
57 BufKeyStrings[k] = action
58 actionfns := make([]func(*BufPane) bool, len(actions))
59 for i, a := range actions {
60 // a = strings.TrimSpace(a)
61 var afn func(*BufPane) bool
62 if strings.HasPrefix(action, "command:") {
63 a = strings.SplitN(a, ":", 2)[1]
64 afn = CommandAction(a)
65 } else if strings.HasPrefix(a, "command-edit:") {
66 a = strings.SplitN(a, ":", 2)[1]
67 afn = CommandEditAction(a)
68 } else if strings.HasPrefix(a, "lua:") {
69 a = strings.SplitN(a, ":", 2)[1]
71 } else if f, ok := BufKeyActions[a]; ok {
74 screen.TermMessage("Error:", action, "does not exist")
78 BufKeyBindings[k] = func(h *BufPane) bool {
80 for _, a := range actionfns {
87 // BufMapMouse maps a mouse event to an action
88 func BufMapMouse(k MouseEvent, action string) {
89 if f, ok := BufMouseActions[action]; ok {
90 BufMouseBindings[k] = f
92 delete(BufMouseBindings, k)
97 // The BufPane connects the buffer and the window
98 // It provides a cursor (or multiple) and defines a set of actions
99 // that can be taken on the buffer
100 // The ActionHandler can access the window for necessary info about
101 // visual positions for mouse clicks and scrolling
102 type BufPane struct {
107 Cursor *buffer.Cursor // the active cursor
109 // Since tcell doesn't differentiate between a mouse release event
110 // and a mouse move event with no keys pressed, we need to keep
111 // track of whether or not the mouse was pressed (or not released) last event to determine
112 // mouse release events
115 // We need to keep track of insert key press toggle
117 // This stores when the last click was
118 // This is useful for detecting double and triple clicks
119 lastClickTime time.Time
122 // lastCutTime stores when the last ctrl+k was issued.
123 // It is used for clearing the clipboard to replace it with fresh cut lines.
124 lastCutTime time.Time
126 // freshClip returns true if the clipboard has never been pasted.
129 // Was the last mouse event actually a double click?
130 // Useful for detecting triple clicks -- if a double click is detected
131 // but the last mouse event was actually a double click, it's a triple click
133 // Same here, just to keep track for mouse move events
136 // Last search stores the last successful search for FindNext and FindPrev
138 // Should the current multiple cursor selection search based on word or
139 // based on selection (false for selection, true for word)
144 // remember original location of a search in case the search is canceled
145 searchOrig buffer.Loc
148 func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
153 h.Cursor = h.Buf.GetActiveCursor()
154 h.mouseReleased = true
156 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
161 func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
162 w := display.NewBufWindow(0, 0, 0, 0, buf)
163 return NewBufPane(buf, w)
166 // PluginCB calls all plugin callbacks with a certain name and
167 // displays an error if there is one and returns the aggregrate
169 func (h *BufPane) PluginCB(cb string) bool {
170 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
172 screen.TermMessage(err)
177 // PluginCBRune is the same as PluginCB but also passes a rune to
179 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
180 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
182 screen.TermMessage(err)
187 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
190 h.BWindow.SetBuffer(b)
191 h.Cursor = b.GetActiveCursor()
192 h.Resize(h.GetView().Width, h.GetView().Height)
193 v := new(display.View)
196 // Set mouseReleased to true because we assume the mouse is not being pressed when
197 // the editor is opened
198 h.mouseReleased = true
199 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
201 h.isOverwriteMode = false
202 h.lastClickTime = time.Time{}
205 func (h *BufPane) ID() uint64 {
209 func (h *BufPane) SetID(i uint64) {
213 func (h *BufPane) Name() string {
214 return h.Buf.GetName()
217 // HandleEvent executes the tcell event properly
218 func (h *BufPane) HandleEvent(event tcell.Event) {
219 switch e := event.(type) {
220 case *tcell.EventRaw:
225 case *tcell.EventKey:
232 done := h.DoKeyEvent(ke)
233 if !done && e.Key() == tcell.KeyRune {
234 h.DoRuneInsert(e.Rune())
236 case *tcell.EventMouse:
238 case tcell.ButtonNone:
239 // Mouse event with no click
240 if !h.mouseReleased {
241 // Mouse was just released
243 mx, my := e.Position()
244 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
246 // Relocating here isn't really necessary because the cursor will
247 // be in the right place from the last mouse event
248 // However, if we are running in a terminal that doesn't support mouse motion
249 // events, this still allows the user to make selections, except only after they
252 if !h.doubleClick && !h.tripleClick {
253 h.Cursor.Loc = mouseLoc
254 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
255 h.Cursor.CopySelection("primary")
257 h.mouseReleased = true
265 h.DoMouseEvent(me, e)
270 // Display any gutter messages for this line
271 c := h.Buf.GetActiveCursor()
273 for _, m := range h.Buf.Messages {
274 if c.Y == m.Start.Y || c.Y == m.End.Y {
275 InfoBar.GutterMessage(m.Msg)
280 if none && InfoBar.HasGutter {
281 InfoBar.ClearGutter()
286 // DoKeyEvent executes a key event by finding the action it is bound
287 // to and executing it (possibly multiple times for multiple cursors)
288 func (h *BufPane) DoKeyEvent(e Event) bool {
289 if action, ok := BufKeyBindings[e]; ok {
290 estr := BufKeyStrings[e]
291 if estr != "InsertTab" {
292 h.Buf.HasSuggestions = false
294 for _, s := range MultiActions {
296 cursors := h.Buf.GetCursors()
297 for _, c := range cursors {
298 h.Buf.SetCurCursor(c.Num)
300 if !h.PluginCB("pre" + estr) {
301 // canceled by plugin
305 if h.PluginCB("on"+estr) && rel {
310 if estr != "ToggleMacro" && estr != "PlayMacro" {
311 curmacro = append(curmacro, e)
318 if !h.PluginCB("pre" + estr) {
322 if h.PluginCB("on"+estr) && rel {
330 func (h *BufPane) HasKeyEvent(e Event) bool {
331 _, ok := BufKeyBindings[e]
335 // DoMouseEvent executes a mouse event by finding the action it is bound
336 // to and executing it
337 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
338 if action, ok := BufMouseBindings[e]; ok {
343 } else if h.HasKeyEvent(e) {
344 return h.DoKeyEvent(e)
349 // DoRuneInsert inserts a given rune into the current buffer
350 // (possibly multiple times for multiple cursors)
351 func (h *BufPane) DoRuneInsert(r rune) {
352 cursors := h.Buf.GetCursors()
353 for _, c := range cursors {
354 // Insert a character
355 h.Buf.SetCurCursor(c.Num)
357 if !h.PluginCBRune("preRune", r) {
360 if c.HasSelection() {
365 if h.isOverwriteMode {
368 h.Buf.Replace(c.Loc, next, string(r))
370 h.Buf.Insert(c.Loc, string(r))
373 curmacro = append(curmacro, r)
375 h.PluginCBRune("onRune", r)
379 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) *BufPane {
380 e := NewBufPaneFromBuf(buf)
381 e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
382 MainTab().Panes = append(MainTab().Panes, e)
384 MainTab().SetActive(len(MainTab().Panes) - 1)
387 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) *BufPane {
388 e := NewBufPaneFromBuf(buf)
389 e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
390 MainTab().Panes = append(MainTab().Panes, e)
392 MainTab().SetActive(len(MainTab().Panes) - 1)
395 func (h *BufPane) Close() {
399 func (h *BufPane) SetActive(b bool) {
400 h.BWindow.SetActive(b)
402 // Display any gutter messages for this line
403 c := h.Buf.GetActiveCursor()
405 for _, m := range h.Buf.Messages {
406 if c.Y == m.Start.Y || c.Y == m.End.Y {
407 InfoBar.GutterMessage(m.Msg)
412 if none && InfoBar.HasGutter {
413 InfoBar.ClearGutter()
419 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
420 var BufKeyActions = map[string]BufKeyAction{
421 "CursorUp": (*BufPane).CursorUp,
422 "CursorDown": (*BufPane).CursorDown,
423 "CursorPageUp": (*BufPane).CursorPageUp,
424 "CursorPageDown": (*BufPane).CursorPageDown,
425 "CursorLeft": (*BufPane).CursorLeft,
426 "CursorRight": (*BufPane).CursorRight,
427 "CursorStart": (*BufPane).CursorStart,
428 "CursorEnd": (*BufPane).CursorEnd,
429 "SelectToStart": (*BufPane).SelectToStart,
430 "SelectToEnd": (*BufPane).SelectToEnd,
431 "SelectUp": (*BufPane).SelectUp,
432 "SelectDown": (*BufPane).SelectDown,
433 "SelectLeft": (*BufPane).SelectLeft,
434 "SelectRight": (*BufPane).SelectRight,
435 "WordRight": (*BufPane).WordRight,
436 "WordLeft": (*BufPane).WordLeft,
437 "SelectWordRight": (*BufPane).SelectWordRight,
438 "SelectWordLeft": (*BufPane).SelectWordLeft,
439 "DeleteWordRight": (*BufPane).DeleteWordRight,
440 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
441 "SelectLine": (*BufPane).SelectLine,
442 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
443 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
444 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
445 "ParagraphNext": (*BufPane).ParagraphNext,
446 "InsertNewline": (*BufPane).InsertNewline,
447 "Backspace": (*BufPane).Backspace,
448 "Delete": (*BufPane).Delete,
449 "InsertTab": (*BufPane).InsertTab,
450 "Save": (*BufPane).Save,
451 "SaveAll": (*BufPane).SaveAll,
452 "SaveAs": (*BufPane).SaveAs,
453 "Find": (*BufPane).Find,
454 "FindNext": (*BufPane).FindNext,
455 "FindPrevious": (*BufPane).FindPrevious,
456 "Center": (*BufPane).Center,
457 "Undo": (*BufPane).Undo,
458 "Redo": (*BufPane).Redo,
459 "Copy": (*BufPane).Copy,
460 "Cut": (*BufPane).Cut,
461 "CutLine": (*BufPane).CutLine,
462 "DuplicateLine": (*BufPane).DuplicateLine,
463 "DeleteLine": (*BufPane).DeleteLine,
464 "MoveLinesUp": (*BufPane).MoveLinesUp,
465 "MoveLinesDown": (*BufPane).MoveLinesDown,
466 "IndentSelection": (*BufPane).IndentSelection,
467 "OutdentSelection": (*BufPane).OutdentSelection,
468 "OutdentLine": (*BufPane).OutdentLine,
469 "Paste": (*BufPane).Paste,
470 "PastePrimary": (*BufPane).PastePrimary,
471 "SelectAll": (*BufPane).SelectAll,
472 "OpenFile": (*BufPane).OpenFile,
473 "Start": (*BufPane).Start,
474 "End": (*BufPane).End,
475 "PageUp": (*BufPane).PageUp,
476 "PageDown": (*BufPane).PageDown,
477 "SelectPageUp": (*BufPane).SelectPageUp,
478 "SelectPageDown": (*BufPane).SelectPageDown,
479 "HalfPageUp": (*BufPane).HalfPageUp,
480 "HalfPageDown": (*BufPane).HalfPageDown,
481 "StartOfLine": (*BufPane).StartOfLine,
482 "EndOfLine": (*BufPane).EndOfLine,
483 "ToggleHelp": (*BufPane).ToggleHelp,
484 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
485 "ToggleRuler": (*BufPane).ToggleRuler,
486 "ClearStatus": (*BufPane).ClearStatus,
487 "ShellMode": (*BufPane).ShellMode,
488 "CommandMode": (*BufPane).CommandMode,
489 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
490 "Escape": (*BufPane).Escape,
491 "Quit": (*BufPane).Quit,
492 "QuitAll": (*BufPane).QuitAll,
493 "AddTab": (*BufPane).AddTab,
494 "PreviousTab": (*BufPane).PreviousTab,
495 "NextTab": (*BufPane).NextTab,
496 "NextSplit": (*BufPane).NextSplit,
497 "PreviousSplit": (*BufPane).PreviousSplit,
498 "Unsplit": (*BufPane).Unsplit,
499 "VSplit": (*BufPane).VSplitAction,
500 "HSplit": (*BufPane).HSplitAction,
501 "ToggleMacro": (*BufPane).ToggleMacro,
502 "PlayMacro": (*BufPane).PlayMacro,
503 "Suspend": (*BufPane).Suspend,
504 "ScrollUp": (*BufPane).ScrollUpAction,
505 "ScrollDown": (*BufPane).ScrollDownAction,
506 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
507 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
508 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
509 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
510 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
511 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
512 "None": (*BufPane).None,
514 // This was changed to InsertNewline but I don't want to break backwards compatibility
515 "InsertEnter": (*BufPane).InsertNewline,
518 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
519 var BufMouseActions = map[string]BufMouseAction{
520 "MousePress": (*BufPane).MousePress,
521 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
524 // MultiActions is a list of actions that should be executed multiple
525 // times if there are multiple cursors (one per cursor)
526 // Generally actions that modify global editor state like quitting or
527 // saving should not be included in this list
528 var MultiActions = []string{
550 "SelectToStartOfLine",
575 "JumpToMatchingBrace",