8 luar "layeh.com/gopher-luar"
10 lua "github.com/yuin/gopher-lua"
11 "github.com/zyedidia/micro/internal/buffer"
12 "github.com/zyedidia/micro/internal/config"
13 "github.com/zyedidia/micro/internal/display"
14 ulua "github.com/zyedidia/micro/internal/lua"
15 "github.com/zyedidia/micro/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, ".")
34 plName, plFn := luaFn[0], luaFn[1]
35 pl := config.FindPlugin(plName)
36 return func(h *BufPane) bool {
37 val, err := pl.Call(plFn, luar.New(ulua.L, h))
39 screen.TermMessage(err)
41 if v, ok := val.(lua.LBool); !ok {
49 // BufMapKey maps a key event to an action
50 func BufMapKey(k Event, action string) {
51 if strings.HasPrefix(action, "command:") {
52 action = strings.SplitN(action, ":", 2)[1]
53 BufKeyStrings[k] = action
54 BufKeyBindings[k] = CommandAction(action)
55 } else if strings.HasPrefix(action, "command-edit:") {
56 action = strings.SplitN(action, ":", 2)[1]
57 BufKeyStrings[k] = action
58 BufKeyBindings[k] = CommandEditAction(action)
59 } else if strings.HasPrefix(action, "lua:") {
60 action = strings.SplitN(action, ":", 2)[1]
61 BufKeyStrings[k] = action
62 BufKeyBindings[k] = LuaAction(action)
63 } else if f, ok := BufKeyActions[action]; ok {
64 BufKeyStrings[k] = action
67 screen.TermMessage("Error:", action, "does not exist")
71 // BufMapMouse maps a mouse event to an action
72 func BufMapMouse(k MouseEvent, action string) {
73 if f, ok := BufMouseActions[action]; ok {
74 BufMouseBindings[k] = f
76 delete(BufMouseBindings, k)
81 // The BufPane connects the buffer and the window
82 // It provides a cursor (or multiple) and defines a set of actions
83 // that can be taken on the buffer
84 // The ActionHandler can access the window for necessary info about
85 // visual positions for mouse clicks and scrolling
91 Cursor *buffer.Cursor // the active cursor
93 // Since tcell doesn't differentiate between a mouse release event
94 // and a mouse move event with no keys pressed, we need to keep
95 // track of whether or not the mouse was pressed (or not released) last event to determine
96 // mouse release events
99 // We need to keep track of insert key press toggle
101 // This stores when the last click was
102 // This is useful for detecting double and triple clicks
103 lastClickTime time.Time
106 // lastCutTime stores when the last ctrl+k was issued.
107 // It is used for clearing the clipboard to replace it with fresh cut lines.
108 lastCutTime time.Time
110 // freshClip returns true if the clipboard has never been pasted.
113 // Was the last mouse event actually a double click?
114 // Useful for detecting triple clicks -- if a double click is detected
115 // but the last mouse event was actually a double click, it's a triple click
117 // Same here, just to keep track for mouse move events
120 // Last search stores the last successful search for FindNext and FindPrev
122 // Should the current multiple cursor selection search based on word or
123 // based on selection (false for selection, true for word)
128 // remember original location of a search in case the search is canceled
129 searchOrig buffer.Loc
132 func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
137 h.Cursor = h.Buf.GetActiveCursor()
138 h.mouseReleased = true
140 config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
145 func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
146 w := display.NewBufWindow(0, 0, 0, 0, buf)
147 return NewBufPane(buf, w)
150 // PluginCB calls all plugin callbacks with a certain name and
151 // displays an error if there is one and returns the aggregrate
153 func (h *BufPane) PluginCB(cb string) bool {
154 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
156 screen.TermMessage(err)
161 // PluginCBRune is the same as PluginCB but also passes a rune to
163 func (h *BufPane) PluginCBRune(cb string, r rune) bool {
164 b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h), luar.New(ulua.L, string(r)))
166 screen.TermMessage(err)
171 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
174 h.BWindow.SetBuffer(b)
175 h.Cursor = b.GetActiveCursor()
176 h.Resize(h.GetView().Width, h.GetView().Height)
177 v := new(display.View)
180 // Set mouseReleased to true because we assume the mouse is not being pressed when
181 // the editor is opened
182 h.mouseReleased = true
183 // Set isOverwriteMode to false, because we assume we are in the default mode when editor
185 h.isOverwriteMode = false
186 h.lastClickTime = time.Time{}
189 func (h *BufPane) ID() uint64 {
193 func (h *BufPane) SetID(i uint64) {
197 func (h *BufPane) Name() string {
198 return h.Buf.GetName()
201 // HandleEvent executes the tcell event properly
202 // TODO: multiple actions bound to one key
203 func (h *BufPane) HandleEvent(event tcell.Event) {
204 switch e := event.(type) {
205 case *tcell.EventRaw:
210 case *tcell.EventKey:
217 done := h.DoKeyEvent(ke)
218 if !done && e.Key() == tcell.KeyRune {
219 h.DoRuneInsert(e.Rune())
221 case *tcell.EventMouse:
223 case tcell.ButtonNone:
224 // Mouse event with no click
225 if !h.mouseReleased {
226 // Mouse was just released
228 mx, my := e.Position()
229 mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
231 // Relocating here isn't really necessary because the cursor will
232 // be in the right place from the last mouse event
233 // However, if we are running in a terminal that doesn't support mouse motion
234 // events, this still allows the user to make selections, except only after they
237 if !h.doubleClick && !h.tripleClick {
238 h.Cursor.Loc = mouseLoc
239 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
240 h.Cursor.CopySelection("primary")
242 h.mouseReleased = true
250 h.DoMouseEvent(me, e)
254 // Display any gutter messages for this line
255 c := h.Buf.GetActiveCursor()
257 for _, m := range h.Buf.Messages {
258 if c.Y == m.Start.Y || c.Y == m.End.Y {
259 InfoBar.GutterMessage(m.Msg)
264 if none && InfoBar.HasGutter {
265 InfoBar.ClearGutter()
269 // DoKeyEvent executes a key event by finding the action it is bound
270 // to and executing it (possibly multiple times for multiple cursors)
271 func (h *BufPane) DoKeyEvent(e Event) bool {
272 if action, ok := BufKeyBindings[e]; ok {
273 estr := BufKeyStrings[e]
274 if estr != "InsertTab" {
275 h.Buf.HasSuggestions = false
277 for _, s := range MultiActions {
279 cursors := h.Buf.GetCursors()
280 for _, c := range cursors {
281 h.Buf.SetCurCursor(c.Num)
283 if !h.PluginCB("pre" + estr) {
284 // canceled by plugin
288 if h.PluginCB("on"+estr) && rel {
295 if !h.PluginCB("pre" + estr) {
299 log.Println("calling on", estr)
300 if h.PluginCB("on"+estr) && rel {
308 func (h *BufPane) HasKeyEvent(e Event) bool {
309 _, ok := BufKeyBindings[e]
313 // DoMouseEvent executes a mouse event by finding the action it is bound
314 // to and executing it
315 func (h *BufPane) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
316 if action, ok := BufMouseBindings[e]; ok {
321 } else if h.HasKeyEvent(e) {
322 return h.DoKeyEvent(e)
327 // DoRuneInsert inserts a given rune into the current buffer
328 // (possibly multiple times for multiple cursors)
329 func (h *BufPane) DoRuneInsert(r rune) {
330 cursors := h.Buf.GetCursors()
331 for _, c := range cursors {
332 // Insert a character
333 h.Buf.SetCurCursor(c.Num)
334 if !h.PluginCBRune("preRune", r) {
337 if c.HasSelection() {
342 if h.isOverwriteMode {
345 h.Buf.Replace(c.Loc, next, string(r))
347 h.Buf.Insert(c.Loc, string(r))
349 h.PluginCBRune("onRune", r)
353 func (h *BufPane) VSplitBuf(buf *buffer.Buffer) {
354 e := NewBufPaneFromBuf(buf)
355 e.splitID = MainTab().GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
356 MainTab().Panes = append(MainTab().Panes, e)
358 MainTab().SetActive(len(MainTab().Panes) - 1)
360 func (h *BufPane) HSplitBuf(buf *buffer.Buffer) {
361 e := NewBufPaneFromBuf(buf)
362 e.splitID = MainTab().GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
363 MainTab().Panes = append(MainTab().Panes, e)
365 MainTab().SetActive(len(MainTab().Panes) - 1)
367 func (h *BufPane) Close() {
371 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
372 var BufKeyActions = map[string]BufKeyAction{
373 "CursorUp": (*BufPane).CursorUp,
374 "CursorDown": (*BufPane).CursorDown,
375 "CursorPageUp": (*BufPane).CursorPageUp,
376 "CursorPageDown": (*BufPane).CursorPageDown,
377 "CursorLeft": (*BufPane).CursorLeft,
378 "CursorRight": (*BufPane).CursorRight,
379 "CursorStart": (*BufPane).CursorStart,
380 "CursorEnd": (*BufPane).CursorEnd,
381 "SelectToStart": (*BufPane).SelectToStart,
382 "SelectToEnd": (*BufPane).SelectToEnd,
383 "SelectUp": (*BufPane).SelectUp,
384 "SelectDown": (*BufPane).SelectDown,
385 "SelectLeft": (*BufPane).SelectLeft,
386 "SelectRight": (*BufPane).SelectRight,
387 "WordRight": (*BufPane).WordRight,
388 "WordLeft": (*BufPane).WordLeft,
389 "SelectWordRight": (*BufPane).SelectWordRight,
390 "SelectWordLeft": (*BufPane).SelectWordLeft,
391 "DeleteWordRight": (*BufPane).DeleteWordRight,
392 "DeleteWordLeft": (*BufPane).DeleteWordLeft,
393 "SelectLine": (*BufPane).SelectLine,
394 "SelectToStartOfLine": (*BufPane).SelectToStartOfLine,
395 "SelectToEndOfLine": (*BufPane).SelectToEndOfLine,
396 "ParagraphPrevious": (*BufPane).ParagraphPrevious,
397 "ParagraphNext": (*BufPane).ParagraphNext,
398 "InsertNewline": (*BufPane).InsertNewline,
399 "Backspace": (*BufPane).Backspace,
400 "Delete": (*BufPane).Delete,
401 "InsertTab": (*BufPane).InsertTab,
402 "Save": (*BufPane).Save,
403 "SaveAll": (*BufPane).SaveAll,
404 "SaveAs": (*BufPane).SaveAs,
405 "Find": (*BufPane).Find,
406 "FindNext": (*BufPane).FindNext,
407 "FindPrevious": (*BufPane).FindPrevious,
408 "Center": (*BufPane).Center,
409 "Undo": (*BufPane).Undo,
410 "Redo": (*BufPane).Redo,
411 "Copy": (*BufPane).Copy,
412 "Cut": (*BufPane).Cut,
413 "CutLine": (*BufPane).CutLine,
414 "DuplicateLine": (*BufPane).DuplicateLine,
415 "DeleteLine": (*BufPane).DeleteLine,
416 "MoveLinesUp": (*BufPane).MoveLinesUp,
417 "MoveLinesDown": (*BufPane).MoveLinesDown,
418 "IndentSelection": (*BufPane).IndentSelection,
419 "OutdentSelection": (*BufPane).OutdentSelection,
420 "OutdentLine": (*BufPane).OutdentLine,
421 "Paste": (*BufPane).Paste,
422 "PastePrimary": (*BufPane).PastePrimary,
423 "SelectAll": (*BufPane).SelectAll,
424 "OpenFile": (*BufPane).OpenFile,
425 "Start": (*BufPane).Start,
426 "End": (*BufPane).End,
427 "PageUp": (*BufPane).PageUp,
428 "PageDown": (*BufPane).PageDown,
429 "SelectPageUp": (*BufPane).SelectPageUp,
430 "SelectPageDown": (*BufPane).SelectPageDown,
431 "HalfPageUp": (*BufPane).HalfPageUp,
432 "HalfPageDown": (*BufPane).HalfPageDown,
433 "StartOfLine": (*BufPane).StartOfLine,
434 "EndOfLine": (*BufPane).EndOfLine,
435 "ToggleHelp": (*BufPane).ToggleHelp,
436 "ToggleKeyMenu": (*BufPane).ToggleKeyMenu,
437 "ToggleRuler": (*BufPane).ToggleRuler,
438 "ClearStatus": (*BufPane).ClearStatus,
439 "ShellMode": (*BufPane).ShellMode,
440 "CommandMode": (*BufPane).CommandMode,
441 "ToggleOverwriteMode": (*BufPane).ToggleOverwriteMode,
442 "Escape": (*BufPane).Escape,
443 "Quit": (*BufPane).Quit,
444 "QuitAll": (*BufPane).QuitAll,
445 "AddTab": (*BufPane).AddTab,
446 "PreviousTab": (*BufPane).PreviousTab,
447 "NextTab": (*BufPane).NextTab,
448 "NextSplit": (*BufPane).NextSplit,
449 "PreviousSplit": (*BufPane).PreviousSplit,
450 "Unsplit": (*BufPane).Unsplit,
451 "VSplit": (*BufPane).VSplitAction,
452 "HSplit": (*BufPane).HSplitAction,
453 "ToggleMacro": (*BufPane).ToggleMacro,
454 "PlayMacro": (*BufPane).PlayMacro,
455 "Suspend": (*BufPane).Suspend,
456 "ScrollUp": (*BufPane).ScrollUpAction,
457 "ScrollDown": (*BufPane).ScrollDownAction,
458 "SpawnMultiCursor": (*BufPane).SpawnMultiCursor,
459 "SpawnMultiCursorSelect": (*BufPane).SpawnMultiCursorSelect,
460 "RemoveMultiCursor": (*BufPane).RemoveMultiCursor,
461 "RemoveAllMultiCursors": (*BufPane).RemoveAllMultiCursors,
462 "SkipMultiCursor": (*BufPane).SkipMultiCursor,
463 "JumpToMatchingBrace": (*BufPane).JumpToMatchingBrace,
465 // This was changed to InsertNewline but I don't want to break backwards compatibility
466 "InsertEnter": (*BufPane).InsertNewline,
469 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
470 var BufMouseActions = map[string]BufMouseAction{
471 "MousePress": (*BufPane).MousePress,
472 "MouseMultiCursor": (*BufPane).MouseMultiCursor,
475 // MultiActions is a list of actions that should be executed multiple
476 // times if there are multiple cursors (one per cursor)
477 // Generally actions that modify global editor state like quitting or
478 // saving should not be included in this list
479 var MultiActions = []string{
501 "SelectToStartOfLine",
526 "JumpToMatchingBrace",