]> git.lizzy.rs Git - micro.git/blob - internal/action/bufpane.go
Add goto command
[micro.git] / internal / action / bufpane.go
1 package action
2
3 import (
4         "log"
5         "strings"
6         "time"
7
8         luar "layeh.com/gopher-luar"
9
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"
17 )
18
19 type BufKeyAction func(*BufPane) bool
20 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
21
22 var BufKeyBindings map[Event]BufKeyAction
23 var BufKeyStrings map[Event]string
24 var BufMouseBindings map[MouseEvent]BufMouseAction
25
26 func init() {
27         BufKeyBindings = make(map[Event]BufKeyAction)
28         BufKeyStrings = make(map[Event]string)
29         BufMouseBindings = make(map[MouseEvent]BufMouseAction)
30 }
31
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))
38                 if err != nil {
39                         screen.TermMessage(err)
40                 }
41                 if v, ok := val.(lua.LBool); !ok {
42                         return false
43                 } else {
44                         return bool(v)
45                 }
46         }
47 }
48
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
65                 BufKeyBindings[k] = f
66         } else {
67                 screen.TermMessage("Error:", action, "does not exist")
68         }
69 }
70
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
75         } else {
76                 delete(BufMouseBindings, k)
77                 BufMapKey(k, action)
78         }
79 }
80
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
86 type BufPane struct {
87         display.BWindow
88
89         Buf *buffer.Buffer
90
91         Cursor *buffer.Cursor // the active cursor
92
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
97         mouseReleased bool
98
99         // We need to keep track of insert key press toggle
100         isOverwriteMode bool
101         // This stores when the last click was
102         // This is useful for detecting double and triple clicks
103         lastClickTime time.Time
104         lastLoc       buffer.Loc
105
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
109
110         // freshClip returns true if the clipboard has never been pasted.
111         freshClip bool
112
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
116         doubleClick bool
117         // Same here, just to keep track for mouse move events
118         tripleClick bool
119
120         // Last search stores the last successful search for FindNext and FindPrev
121         lastSearch string
122         // Should the current multiple cursor selection search based on word or
123         // based on selection (false for selection, true for word)
124         multiWord bool
125
126         splitID uint64
127
128         // remember original location of a search in case the search is canceled
129         searchOrig buffer.Loc
130 }
131
132 func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
133         h := new(BufPane)
134         h.Buf = buf
135         h.BWindow = win
136
137         h.Cursor = h.Buf.GetActiveCursor()
138         h.mouseReleased = true
139
140         config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
141
142         return h
143 }
144
145 func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
146         w := display.NewBufWindow(0, 0, 0, 0, buf)
147         return NewBufPane(buf, w)
148 }
149
150 // PluginCB calls all plugin callbacks with a certain name and
151 // displays an error if there is one and returns the aggregrate
152 // boolean response
153 func (h *BufPane) PluginCB(cb string) bool {
154         b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
155         if err != nil {
156                 screen.TermMessage(err)
157         }
158         return b
159 }
160
161 // PluginCBRune is the same as PluginCB but also passes a rune to
162 // the plugins
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)))
165         if err != nil {
166                 screen.TermMessage(err)
167         }
168         return b
169 }
170
171 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
172         h.Buf.Close()
173         h.Buf = b
174         h.BWindow.SetBuffer(b)
175         h.Cursor = b.GetActiveCursor()
176         h.Resize(h.GetView().Width, h.GetView().Height)
177         v := new(display.View)
178         h.SetView(v)
179         h.Relocate()
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
184         // is opened
185         h.isOverwriteMode = false
186         h.lastClickTime = time.Time{}
187 }
188
189 func (h *BufPane) ID() uint64 {
190         return h.splitID
191 }
192
193 func (h *BufPane) SetID(i uint64) {
194         h.splitID = i
195 }
196
197 func (h *BufPane) Name() string {
198         return h.Buf.GetName()
199 }
200
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:
206                 re := RawEvent{
207                         esc: e.EscSeq(),
208                 }
209                 h.DoKeyEvent(re)
210         case *tcell.EventKey:
211                 ke := KeyEvent{
212                         code: e.Key(),
213                         mod:  e.Modifiers(),
214                         r:    e.Rune(),
215                 }
216
217                 done := h.DoKeyEvent(ke)
218                 if !done && e.Key() == tcell.KeyRune {
219                         h.DoRuneInsert(e.Rune())
220                 }
221         case *tcell.EventMouse:
222                 switch e.Buttons() {
223                 case tcell.ButtonNone:
224                         // Mouse event with no click
225                         if !h.mouseReleased {
226                                 // Mouse was just released
227
228                                 mx, my := e.Position()
229                                 mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
230
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
235                                 // release the mouse
236
237                                 if !h.doubleClick && !h.tripleClick {
238                                         h.Cursor.Loc = mouseLoc
239                                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
240                                         h.Cursor.CopySelection("primary")
241                                 }
242                                 h.mouseReleased = true
243                         }
244                 }
245
246                 me := MouseEvent{
247                         btn: e.Buttons(),
248                         mod: e.Modifiers(),
249                 }
250                 h.DoMouseEvent(me, e)
251         }
252         h.Buf.MergeCursors()
253
254         // Display any gutter messages for this line
255         c := h.Buf.GetActiveCursor()
256         none := true
257         for _, m := range h.Buf.Messages {
258                 if c.Y == m.Start.Y || c.Y == m.End.Y {
259                         InfoBar.GutterMessage(m.Msg)
260                         none = false
261                         break
262                 }
263         }
264         if none && InfoBar.HasGutter {
265                 InfoBar.ClearGutter()
266         }
267 }
268
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
276                 }
277                 for _, s := range MultiActions {
278                         if s == estr {
279                                 cursors := h.Buf.GetCursors()
280                                 for _, c := range cursors {
281                                         h.Buf.SetCurCursor(c.Num)
282                                         h.Cursor = c
283                                         if !h.PluginCB("pre" + estr) {
284                                                 // canceled by plugin
285                                                 continue
286                                         }
287                                         rel := action(h)
288                                         if h.PluginCB("on"+estr) && rel {
289                                                 h.Relocate()
290                                         }
291                                 }
292                                 return true
293                         }
294                 }
295                 if !h.PluginCB("pre" + estr) {
296                         return false
297                 }
298                 rel := action(h)
299                 log.Println("calling on", estr)
300                 if h.PluginCB("on"+estr) && rel {
301                         h.Relocate()
302                 }
303                 return true
304         }
305         return false
306 }
307
308 func (h *BufPane) HasKeyEvent(e Event) bool {
309         _, ok := BufKeyBindings[e]
310         return ok
311 }
312
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 {
317                 if action(h, te) {
318                         h.Relocate()
319                 }
320                 return true
321         } else if h.HasKeyEvent(e) {
322                 return h.DoKeyEvent(e)
323         }
324         return false
325 }
326
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) {
335                         continue
336                 }
337                 if c.HasSelection() {
338                         c.DeleteSelection()
339                         c.ResetSelection()
340                 }
341
342                 if h.isOverwriteMode {
343                         next := c.Loc
344                         next.X++
345                         h.Buf.Replace(c.Loc, next, string(r))
346                 } else {
347                         h.Buf.Insert(c.Loc, string(r))
348                 }
349                 h.PluginCBRune("onRune", r)
350         }
351 }
352
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)
357         MainTab().Resize()
358         MainTab().SetActive(len(MainTab().Panes) - 1)
359 }
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)
364         MainTab().Resize()
365         MainTab().SetActive(len(MainTab().Panes) - 1)
366 }
367 func (h *BufPane) Close() {
368         h.Buf.Close()
369 }
370
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,
464
465         // This was changed to InsertNewline but I don't want to break backwards compatibility
466         "InsertEnter": (*BufPane).InsertNewline,
467 }
468
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,
473 }
474
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{
480         "CursorUp",
481         "CursorDown",
482         "CursorPageUp",
483         "CursorPageDown",
484         "CursorLeft",
485         "CursorRight",
486         "CursorStart",
487         "CursorEnd",
488         "SelectToStart",
489         "SelectToEnd",
490         "SelectUp",
491         "SelectDown",
492         "SelectLeft",
493         "SelectRight",
494         "WordRight",
495         "WordLeft",
496         "SelectWordRight",
497         "SelectWordLeft",
498         "DeleteWordRight",
499         "DeleteWordLeft",
500         "SelectLine",
501         "SelectToStartOfLine",
502         "SelectToEndOfLine",
503         "ParagraphPrevious",
504         "ParagraphNext",
505         "InsertNewline",
506         "Backspace",
507         "Delete",
508         "InsertTab",
509         "FindNext",
510         "FindPrevious",
511         "Cut",
512         "CutLine",
513         "DuplicateLine",
514         "DeleteLine",
515         "MoveLinesUp",
516         "MoveLinesDown",
517         "IndentSelection",
518         "OutdentSelection",
519         "OutdentLine",
520         "Paste",
521         "PastePrimary",
522         "SelectPageUp",
523         "SelectPageDown",
524         "StartOfLine",
525         "EndOfLine",
526         "JumpToMatchingBrace",
527 }