]> git.lizzy.rs Git - micro.git/blob - internal/action/bufpane.go
17a0220d3c375c86cd518fc5262c7dd7f65cd537
[micro.git] / internal / action / bufpane.go
1 package action
2
3 import (
4         "strings"
5         "time"
6
7         luar "layeh.com/gopher-luar"
8
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"
16 )
17
18 type BufKeyAction func(*BufPane) bool
19 type BufMouseAction func(*BufPane, *tcell.EventMouse) bool
20
21 var BufKeyBindings map[Event]BufKeyAction
22 var BufKeyStrings map[Event]string
23 var BufMouseBindings map[MouseEvent]BufMouseAction
24
25 func init() {
26         BufKeyBindings = make(map[Event]BufKeyAction)
27         BufKeyStrings = make(map[Event]string)
28         BufMouseBindings = make(map[MouseEvent]BufMouseAction)
29 }
30
31 func LuaAction(fn string) func(*BufPane) bool {
32         luaFn := strings.Split(fn, ".")
33         if len(luaFn) <= 1 {
34                 return nil
35         }
36         plName, plFn := luaFn[0], luaFn[1]
37         pl := config.FindPlugin(plName)
38         if pl == nil {
39                 return nil
40         }
41         return func(h *BufPane) bool {
42                 val, err := pl.Call(plFn, luar.New(ulua.L, h))
43                 if err != nil {
44                         screen.TermMessage(err)
45                 }
46                 if v, ok := val.(lua.LBool); !ok {
47                         return false
48                 } else {
49                         return bool(v)
50                 }
51         }
52 }
53
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]
70                         afn = LuaAction(a)
71                 } else if f, ok := BufKeyActions[a]; ok {
72                         afn = f
73                 } else {
74                         screen.TermMessage("Error:", action, "does not exist")
75                 }
76                 actionfns[i] = afn
77         }
78         BufKeyBindings[k] = func(h *BufPane) bool {
79                 b := false
80                 for _, a := range actionfns {
81                         b = a(h) || b
82                 }
83                 return b
84         }
85 }
86
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
91         } else {
92                 delete(BufMouseBindings, k)
93                 BufMapKey(k, action)
94         }
95 }
96
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 {
103         display.BWindow
104
105         Buf *buffer.Buffer
106
107         Cursor *buffer.Cursor // the active cursor
108
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
113         mouseReleased bool
114
115         // We need to keep track of insert key press toggle
116         isOverwriteMode bool
117         // This stores when the last click was
118         // This is useful for detecting double and triple clicks
119         lastClickTime time.Time
120         lastLoc       buffer.Loc
121
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
125
126         // freshClip returns true if the clipboard has never been pasted.
127         freshClip bool
128
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
132         doubleClick bool
133         // Same here, just to keep track for mouse move events
134         tripleClick bool
135
136         // Last search stores the last successful search for FindNext and FindPrev
137         lastSearch string
138         // Should the current multiple cursor selection search based on word or
139         // based on selection (false for selection, true for word)
140         multiWord bool
141
142         splitID uint64
143
144         // remember original location of a search in case the search is canceled
145         searchOrig buffer.Loc
146 }
147
148 func NewBufPane(buf *buffer.Buffer, win display.BWindow) *BufPane {
149         h := new(BufPane)
150         h.Buf = buf
151         h.BWindow = win
152
153         h.Cursor = h.Buf.GetActiveCursor()
154         h.mouseReleased = true
155
156         config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h))
157
158         return h
159 }
160
161 func NewBufPaneFromBuf(buf *buffer.Buffer) *BufPane {
162         w := display.NewBufWindow(0, 0, 0, 0, buf)
163         return NewBufPane(buf, w)
164 }
165
166 // PluginCB calls all plugin callbacks with a certain name and
167 // displays an error if there is one and returns the aggregrate
168 // boolean response
169 func (h *BufPane) PluginCB(cb string) bool {
170         b, err := config.RunPluginFnBool(cb, luar.New(ulua.L, h))
171         if err != nil {
172                 screen.TermMessage(err)
173         }
174         return b
175 }
176
177 // PluginCBRune is the same as PluginCB but also passes a rune to
178 // the plugins
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)))
181         if err != nil {
182                 screen.TermMessage(err)
183         }
184         return b
185 }
186
187 func (h *BufPane) OpenBuffer(b *buffer.Buffer) {
188         h.Buf.Close()
189         h.Buf = b
190         h.BWindow.SetBuffer(b)
191         h.Cursor = b.GetActiveCursor()
192         h.Resize(h.GetView().Width, h.GetView().Height)
193         v := new(display.View)
194         h.SetView(v)
195         h.Relocate()
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
200         // is opened
201         h.isOverwriteMode = false
202         h.lastClickTime = time.Time{}
203 }
204
205 func (h *BufPane) ID() uint64 {
206         return h.splitID
207 }
208
209 func (h *BufPane) SetID(i uint64) {
210         h.splitID = i
211 }
212
213 func (h *BufPane) Name() string {
214         return h.Buf.GetName()
215 }
216
217 // HandleEvent executes the tcell event properly
218 func (h *BufPane) HandleEvent(event tcell.Event) {
219         switch e := event.(type) {
220         case *tcell.EventRaw:
221                 re := RawEvent{
222                         esc: e.EscSeq(),
223                 }
224                 h.DoKeyEvent(re)
225         case *tcell.EventKey:
226                 ke := KeyEvent{
227                         code: e.Key(),
228                         mod:  e.Modifiers(),
229                         r:    e.Rune(),
230                 }
231
232                 done := h.DoKeyEvent(ke)
233                 if !done && e.Key() == tcell.KeyRune {
234                         h.DoRuneInsert(e.Rune())
235                 }
236         case *tcell.EventMouse:
237                 switch e.Buttons() {
238                 case tcell.ButtonNone:
239                         // Mouse event with no click
240                         if !h.mouseReleased {
241                                 // Mouse was just released
242
243                                 mx, my := e.Position()
244                                 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
245
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
250                                 // release the mouse
251
252                                 if !h.doubleClick && !h.tripleClick {
253                                         h.Cursor.Loc = mouseLoc
254                                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
255                                         h.Cursor.CopySelection("primary")
256                                 }
257                                 h.mouseReleased = true
258                         }
259                 }
260
261                 me := MouseEvent{
262                         btn: e.Buttons(),
263                         mod: e.Modifiers(),
264                 }
265                 h.DoMouseEvent(me, e)
266         }
267         h.Buf.MergeCursors()
268
269         if h.IsActive() {
270                 // Display any gutter messages for this line
271                 c := h.Buf.GetActiveCursor()
272                 none := true
273                 for _, m := range h.Buf.Messages {
274                         if c.Y == m.Start.Y || c.Y == m.End.Y {
275                                 InfoBar.GutterMessage(m.Msg)
276                                 none = false
277                                 break
278                         }
279                 }
280                 if none && InfoBar.HasGutter {
281                         InfoBar.ClearGutter()
282                 }
283         }
284 }
285
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
293                 }
294                 for _, s := range MultiActions {
295                         if s == estr {
296                                 cursors := h.Buf.GetCursors()
297                                 for _, c := range cursors {
298                                         h.Buf.SetCurCursor(c.Num)
299                                         h.Cursor = c
300                                         if !h.PluginCB("pre" + estr) {
301                                                 // canceled by plugin
302                                                 continue
303                                         }
304                                         rel := action(h)
305                                         if h.PluginCB("on"+estr) && rel {
306                                                 h.Relocate()
307                                         }
308
309                                         if recording_macro {
310                                                 if estr != "ToggleMacro" && estr != "PlayMacro" {
311                                                         curmacro = append(curmacro, e)
312                                                 }
313                                         }
314                                 }
315                                 return true
316                         }
317                 }
318                 if !h.PluginCB("pre" + estr) {
319                         return false
320                 }
321                 rel := action(h)
322                 if h.PluginCB("on"+estr) && rel {
323                         h.Relocate()
324                 }
325                 return true
326         }
327         return false
328 }
329
330 func (h *BufPane) HasKeyEvent(e Event) bool {
331         _, ok := BufKeyBindings[e]
332         return ok
333 }
334
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 {
339                 if action(h, te) {
340                         h.Relocate()
341                 }
342                 return true
343         } else if h.HasKeyEvent(e) {
344                 return h.DoKeyEvent(e)
345         }
346         return false
347 }
348
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)
356                 h.Cursor = c
357                 if !h.PluginCBRune("preRune", r) {
358                         continue
359                 }
360                 if c.HasSelection() {
361                         c.DeleteSelection()
362                         c.ResetSelection()
363                 }
364
365                 if h.isOverwriteMode {
366                         next := c.Loc
367                         next.X++
368                         h.Buf.Replace(c.Loc, next, string(r))
369                 } else {
370                         h.Buf.Insert(c.Loc, string(r))
371                 }
372                 if recording_macro {
373                         curmacro = append(curmacro, r)
374                 }
375                 h.PluginCBRune("onRune", r)
376         }
377 }
378
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)
383         MainTab().Resize()
384         MainTab().SetActive(len(MainTab().Panes) - 1)
385         return e
386 }
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)
391         MainTab().Resize()
392         MainTab().SetActive(len(MainTab().Panes) - 1)
393         return e
394 }
395 func (h *BufPane) Close() {
396         h.Buf.Close()
397 }
398
399 func (h *BufPane) SetActive(b bool) {
400         h.BWindow.SetActive(b)
401         if b {
402                 // Display any gutter messages for this line
403                 c := h.Buf.GetActiveCursor()
404                 none := true
405                 for _, m := range h.Buf.Messages {
406                         if c.Y == m.Start.Y || c.Y == m.End.Y {
407                                 InfoBar.GutterMessage(m.Msg)
408                                 none = false
409                                 break
410                         }
411                 }
412                 if none && InfoBar.HasGutter {
413                         InfoBar.ClearGutter()
414                 }
415         }
416
417 }
418
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,
513
514         // This was changed to InsertNewline but I don't want to break backwards compatibility
515         "InsertEnter": (*BufPane).InsertNewline,
516 }
517
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,
522 }
523
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{
529         "CursorUp",
530         "CursorDown",
531         "CursorPageUp",
532         "CursorPageDown",
533         "CursorLeft",
534         "CursorRight",
535         "CursorStart",
536         "CursorEnd",
537         "SelectToStart",
538         "SelectToEnd",
539         "SelectUp",
540         "SelectDown",
541         "SelectLeft",
542         "SelectRight",
543         "WordRight",
544         "WordLeft",
545         "SelectWordRight",
546         "SelectWordLeft",
547         "DeleteWordRight",
548         "DeleteWordLeft",
549         "SelectLine",
550         "SelectToStartOfLine",
551         "SelectToEndOfLine",
552         "ParagraphPrevious",
553         "ParagraphNext",
554         "InsertNewline",
555         "Backspace",
556         "Delete",
557         "InsertTab",
558         "FindNext",
559         "FindPrevious",
560         "Cut",
561         "CutLine",
562         "DuplicateLine",
563         "DeleteLine",
564         "MoveLinesUp",
565         "MoveLinesDown",
566         "IndentSelection",
567         "OutdentSelection",
568         "OutdentLine",
569         "Paste",
570         "PastePrimary",
571         "SelectPageUp",
572         "SelectPageDown",
573         "StartOfLine",
574         "EndOfLine",
575         "JumpToMatchingBrace",
576 }