]> git.lizzy.rs Git - micro.git/blob - cmd/micro/action/bufhandler.go
Merge cursors after any event
[micro.git] / cmd / micro / action / bufhandler.go
1 package action
2
3 import (
4         "time"
5
6         "github.com/zyedidia/micro/cmd/micro/buffer"
7         "github.com/zyedidia/micro/cmd/micro/display"
8         "github.com/zyedidia/micro/cmd/micro/util"
9         "github.com/zyedidia/tcell"
10 )
11
12 type BufKeyAction func(*BufHandler) bool
13 type BufMouseAction func(*BufHandler, *tcell.EventMouse) bool
14
15 var BufKeyBindings map[KeyEvent]BufKeyAction
16 var BufKeyStrings map[KeyEvent]string
17 var BufMouseBindings map[MouseEvent]BufMouseAction
18
19 func init() {
20         BufKeyBindings = make(map[KeyEvent]BufKeyAction)
21         BufKeyStrings = make(map[KeyEvent]string)
22         BufMouseBindings = make(map[MouseEvent]BufMouseAction)
23 }
24
25 // BufMapKey maps a key event to an action
26 func BufMapKey(k KeyEvent, action string) {
27         if f, ok := BufKeyActions[action]; ok {
28                 BufKeyStrings[k] = action
29                 BufKeyBindings[k] = f
30         } else {
31                 util.TermMessage("Error:", action, "does not exist")
32         }
33 }
34
35 // BufMapMouse maps a mouse event to an action
36 func BufMapMouse(k MouseEvent, action string) {
37         if f, ok := BufMouseActions[action]; ok {
38                 BufMouseBindings[k] = f
39         } else if f, ok := BufKeyActions[action]; ok {
40                 // allowed to map mouse buttons to key actions
41                 BufMouseBindings[k] = func(h *BufHandler, e *tcell.EventMouse) bool {
42                         return f(h)
43                 }
44         } else {
45                 util.TermMessage("Error:", action, "does not exist")
46         }
47 }
48
49 // The BufHandler connects the buffer and the window
50 // It provides a cursor (or multiple) and defines a set of actions
51 // that can be taken on the buffer
52 // The ActionHandler can access the window for necessary info about
53 // visual positions for mouse clicks and scrolling
54 type BufHandler struct {
55         Buf *buffer.Buffer
56         Win display.Window
57
58         cursors []*buffer.Cursor
59         Cursor  *buffer.Cursor // the active cursor
60
61         StartLine int // Vertical scrolling
62         StartCol  int // Horizontal scrolling
63
64         // Since tcell doesn't differentiate between a mouse release event
65         // and a mouse move event with no keys pressed, we need to keep
66         // track of whether or not the mouse was pressed (or not released) last event to determine
67         // mouse release events
68         mouseReleased bool
69
70         // We need to keep track of insert key press toggle
71         isOverwriteMode bool
72         // This stores when the last click was
73         // This is useful for detecting double and triple clicks
74         lastClickTime time.Time
75         lastLoc       buffer.Loc
76
77         // lastCutTime stores when the last ctrl+k was issued.
78         // It is used for clearing the clipboard to replace it with fresh cut lines.
79         lastCutTime time.Time
80
81         // freshClip returns true if the clipboard has never been pasted.
82         freshClip bool
83
84         // Was the last mouse event actually a double click?
85         // Useful for detecting triple clicks -- if a double click is detected
86         // but the last mouse event was actually a double click, it's a triple click
87         doubleClick bool
88         // Same here, just to keep track for mouse move events
89         tripleClick bool
90
91         // Last search stores the last successful search for FindNext and FindPrev
92         lastSearch string
93         // Should the current multiple cursor selection search based on word or
94         // based on selection (false for selection, true for word)
95         multiWord bool
96
97         splitID uint64
98 }
99
100 func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler {
101         h := new(BufHandler)
102         h.Buf = buf
103         h.Win = win
104
105         h.cursors = []*buffer.Cursor{buffer.NewCursor(buf, buf.StartCursor)}
106         h.Cursor = h.cursors[0]
107         h.mouseReleased = true
108
109         buf.SetCursors(h.cursors)
110         return h
111 }
112
113 // HandleEvent executes the tcell event properly
114 // TODO: multiple actions bound to one key
115 func (h *BufHandler) HandleEvent(event tcell.Event) {
116         switch e := event.(type) {
117         case *tcell.EventKey:
118                 ke := KeyEvent{
119                         code: e.Key(),
120                         mod:  e.Modifiers(),
121                         r:    e.Rune(),
122                 }
123
124                 done := h.DoKeyEvent(ke)
125                 if !done && e.Key() == tcell.KeyRune {
126                         h.DoRuneInsert(e.Rune())
127                 }
128         case *tcell.EventMouse:
129                 switch e.Buttons() {
130                 case tcell.ButtonNone:
131                         // Mouse event with no click
132                         if !h.mouseReleased {
133                                 // Mouse was just released
134
135                                 mx, my := e.Position()
136                                 mouseLoc := h.Win.GetMouseLoc(buffer.Loc{X: mx, Y: my})
137
138                                 // Relocating here isn't really necessary because the cursor will
139                                 // be in the right place from the last mouse event
140                                 // However, if we are running in a terminal that doesn't support mouse motion
141                                 // events, this still allows the user to make selections, except only after they
142                                 // release the mouse
143
144                                 if !h.doubleClick && !h.tripleClick {
145                                         h.Cursor.Loc = mouseLoc
146                                         h.Cursor.SetSelectionEnd(h.Cursor.Loc)
147                                         h.Cursor.CopySelection("primary")
148                                 }
149                                 h.mouseReleased = true
150                         }
151                 }
152
153                 me := MouseEvent{
154                         btn: e.Buttons(),
155                         mod: e.Modifiers(),
156                 }
157                 h.DoMouseEvent(me, e)
158         }
159         h.Buf.MergeCursors()
160 }
161
162 // DoKeyEvent executes a key event by finding the action it is bound
163 // to and executing it (possibly multiple times for multiple cursors)
164 func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
165         if action, ok := BufKeyBindings[e]; ok {
166                 estr := BufKeyStrings[e]
167                 for _, s := range MultiActions {
168                         if s == estr {
169                                 cursors := h.Buf.GetCursors()
170                                 for _, c := range cursors {
171                                         h.Buf.SetCurCursor(c.Num)
172                                         h.Cursor = c
173                                         if action(h) {
174                                                 h.Win.Relocate()
175                                         }
176                                 }
177                                 return true
178                         }
179                 }
180                 if action(h) {
181                         h.Win.Relocate()
182                 }
183                 return true
184         }
185         return false
186 }
187
188 // DoMouseEvent executes a mouse event by finding the action it is bound
189 // to and executing it
190 func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
191         if action, ok := BufMouseBindings[e]; ok {
192                 if action(h, te) {
193                         h.Win.Relocate()
194                 }
195                 return true
196         }
197         return false
198 }
199
200 // DoRuneInsert inserts a given rune into the current buffer
201 // (possibly multiple times for multiple cursors)
202 func (h *BufHandler) DoRuneInsert(r rune) {
203         cursors := h.Buf.GetCursors()
204         for _, c := range cursors {
205                 // Insert a character
206                 if c.HasSelection() {
207                         c.DeleteSelection()
208                         c.ResetSelection()
209                 }
210
211                 if h.isOverwriteMode {
212                         next := c.Loc
213                         next.X++
214                         h.Buf.Replace(c.Loc, next, string(r))
215                 } else {
216                         h.Buf.Insert(c.Loc, string(r))
217                 }
218         }
219 }
220
221 func (h *BufHandler) vsplit(buf *buffer.Buffer) {
222         e := NewBufEditPane(0, 0, 0, 0, buf)
223         e.splitID = MainTab.GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
224         MainTab.Panes = append(MainTab.Panes, e)
225         MainTab.Resize()
226         MainTab.SetActive(len(MainTab.Panes) - 1)
227 }
228 func (h *BufHandler) hsplit(buf *buffer.Buffer) {
229         e := NewBufEditPane(0, 0, 0, 0, buf)
230         e.splitID = MainTab.GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
231         MainTab.Panes = append(MainTab.Panes, e)
232         MainTab.Resize()
233         MainTab.SetActive(len(MainTab.Panes) - 1)
234 }
235
236 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
237 var BufKeyActions = map[string]BufKeyAction{
238         "CursorUp":               (*BufHandler).CursorUp,
239         "CursorDown":             (*BufHandler).CursorDown,
240         "CursorPageUp":           (*BufHandler).CursorPageUp,
241         "CursorPageDown":         (*BufHandler).CursorPageDown,
242         "CursorLeft":             (*BufHandler).CursorLeft,
243         "CursorRight":            (*BufHandler).CursorRight,
244         "CursorStart":            (*BufHandler).CursorStart,
245         "CursorEnd":              (*BufHandler).CursorEnd,
246         "SelectToStart":          (*BufHandler).SelectToStart,
247         "SelectToEnd":            (*BufHandler).SelectToEnd,
248         "SelectUp":               (*BufHandler).SelectUp,
249         "SelectDown":             (*BufHandler).SelectDown,
250         "SelectLeft":             (*BufHandler).SelectLeft,
251         "SelectRight":            (*BufHandler).SelectRight,
252         "WordRight":              (*BufHandler).WordRight,
253         "WordLeft":               (*BufHandler).WordLeft,
254         "SelectWordRight":        (*BufHandler).SelectWordRight,
255         "SelectWordLeft":         (*BufHandler).SelectWordLeft,
256         "DeleteWordRight":        (*BufHandler).DeleteWordRight,
257         "DeleteWordLeft":         (*BufHandler).DeleteWordLeft,
258         "SelectLine":             (*BufHandler).SelectLine,
259         "SelectToStartOfLine":    (*BufHandler).SelectToStartOfLine,
260         "SelectToEndOfLine":      (*BufHandler).SelectToEndOfLine,
261         "ParagraphPrevious":      (*BufHandler).ParagraphPrevious,
262         "ParagraphNext":          (*BufHandler).ParagraphNext,
263         "InsertNewline":          (*BufHandler).InsertNewline,
264         "InsertSpace":            (*BufHandler).InsertSpace,
265         "Backspace":              (*BufHandler).Backspace,
266         "Delete":                 (*BufHandler).Delete,
267         "InsertTab":              (*BufHandler).InsertTab,
268         "Save":                   (*BufHandler).Save,
269         "SaveAll":                (*BufHandler).SaveAll,
270         "SaveAs":                 (*BufHandler).SaveAs,
271         "Find":                   (*BufHandler).Find,
272         "FindNext":               (*BufHandler).FindNext,
273         "FindPrevious":           (*BufHandler).FindPrevious,
274         "Center":                 (*BufHandler).Center,
275         "Undo":                   (*BufHandler).Undo,
276         "Redo":                   (*BufHandler).Redo,
277         "Copy":                   (*BufHandler).Copy,
278         "Cut":                    (*BufHandler).Cut,
279         "CutLine":                (*BufHandler).CutLine,
280         "DuplicateLine":          (*BufHandler).DuplicateLine,
281         "DeleteLine":             (*BufHandler).DeleteLine,
282         "MoveLinesUp":            (*BufHandler).MoveLinesUp,
283         "MoveLinesDown":          (*BufHandler).MoveLinesDown,
284         "IndentSelection":        (*BufHandler).IndentSelection,
285         "OutdentSelection":       (*BufHandler).OutdentSelection,
286         "OutdentLine":            (*BufHandler).OutdentLine,
287         "Paste":                  (*BufHandler).Paste,
288         "PastePrimary":           (*BufHandler).PastePrimary,
289         "SelectAll":              (*BufHandler).SelectAll,
290         "OpenFile":               (*BufHandler).OpenFile,
291         "Start":                  (*BufHandler).Start,
292         "End":                    (*BufHandler).End,
293         "PageUp":                 (*BufHandler).PageUp,
294         "PageDown":               (*BufHandler).PageDown,
295         "SelectPageUp":           (*BufHandler).SelectPageUp,
296         "SelectPageDown":         (*BufHandler).SelectPageDown,
297         "HalfPageUp":             (*BufHandler).HalfPageUp,
298         "HalfPageDown":           (*BufHandler).HalfPageDown,
299         "StartOfLine":            (*BufHandler).StartOfLine,
300         "EndOfLine":              (*BufHandler).EndOfLine,
301         "ToggleHelp":             (*BufHandler).ToggleHelp,
302         "ToggleKeyMenu":          (*BufHandler).ToggleKeyMenu,
303         "ToggleRuler":            (*BufHandler).ToggleRuler,
304         "JumpLine":               (*BufHandler).JumpLine,
305         "ClearStatus":            (*BufHandler).ClearStatus,
306         "ShellMode":              (*BufHandler).ShellMode,
307         "CommandMode":            (*BufHandler).CommandMode,
308         "ToggleOverwriteMode":    (*BufHandler).ToggleOverwriteMode,
309         "Escape":                 (*BufHandler).Escape,
310         "Quit":                   (*BufHandler).Quit,
311         "QuitAll":                (*BufHandler).QuitAll,
312         "AddTab":                 (*BufHandler).AddTab,
313         "PreviousTab":            (*BufHandler).PreviousTab,
314         "NextTab":                (*BufHandler).NextTab,
315         "NextSplit":              (*BufHandler).NextSplit,
316         "PreviousSplit":          (*BufHandler).PreviousSplit,
317         "Unsplit":                (*BufHandler).Unsplit,
318         "VSplit":                 (*BufHandler).VSplitBinding,
319         "HSplit":                 (*BufHandler).HSplitBinding,
320         "ToggleMacro":            (*BufHandler).ToggleMacro,
321         "PlayMacro":              (*BufHandler).PlayMacro,
322         "Suspend":                (*BufHandler).Suspend,
323         "ScrollUp":               (*BufHandler).ScrollUpAction,
324         "ScrollDown":             (*BufHandler).ScrollDownAction,
325         "SpawnMultiCursor":       (*BufHandler).SpawnMultiCursor,
326         "SpawnMultiCursorSelect": (*BufHandler).SpawnMultiCursorSelect,
327         "RemoveMultiCursor":      (*BufHandler).RemoveMultiCursor,
328         "RemoveAllMultiCursors":  (*BufHandler).RemoveAllMultiCursors,
329         "SkipMultiCursor":        (*BufHandler).SkipMultiCursor,
330         "JumpToMatchingBrace":    (*BufHandler).JumpToMatchingBrace,
331
332         // This was changed to InsertNewline but I don't want to break backwards compatibility
333         "InsertEnter": (*BufHandler).InsertNewline,
334 }
335
336 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
337 var BufMouseActions = map[string]BufMouseAction{
338         "MousePress":       (*BufHandler).MousePress,
339         "MouseMultiCursor": (*BufHandler).MouseMultiCursor,
340 }
341
342 // MultiActions is a list of actions that should be executed multiple
343 // times if there are multiple cursors (one per cursor)
344 // Generally actions that modify global editor state like quitting or
345 // saving should not be included in this list
346 var MultiActions = []string{
347         "CursorUp",
348         "CursorDown",
349         "CursorPageUp",
350         "CursorPageDown",
351         "CursorLeft",
352         "CursorRight",
353         "CursorStart",
354         "CursorEnd",
355         "SelectToStart",
356         "SelectToEnd",
357         "SelectUp",
358         "SelectDown",
359         "SelectLeft",
360         "SelectRight",
361         "WordRight",
362         "WordLeft",
363         "SelectWordRight",
364         "SelectWordLeft",
365         "DeleteWordRight",
366         "DeleteWordLeft",
367         "SelectLine",
368         "SelectToStartOfLine",
369         "SelectToEndOfLine",
370         "ParagraphPrevious",
371         "ParagraphNext",
372         "InsertNewline",
373         "InsertSpace",
374         "Backspace",
375         "Delete",
376         "InsertTab",
377         "FindNext",
378         "FindPrevious",
379         "Cut",
380         "CutLine",
381         "DuplicateLine",
382         "DeleteLine",
383         "MoveLinesUp",
384         "MoveLinesDown",
385         "IndentSelection",
386         "OutdentSelection",
387         "OutdentLine",
388         "Paste",
389         "PastePrimary",
390         "SelectPageUp",
391         "SelectPageDown",
392         "StartOfLine",
393         "EndOfLine",
394         "JumpToMatchingBrace",
395 }