]> git.lizzy.rs Git - micro.git/blob - cmd/micro/action/bufhandler.go
7443631247a48e1fcca9fa45ef062f2ed3b45531
[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 }
160
161 // DoKeyEvent executes a key event by finding the action it is bound
162 // to and executing it (possibly multiple times for multiple cursors)
163 func (h *BufHandler) DoKeyEvent(e KeyEvent) bool {
164         if action, ok := BufKeyBindings[e]; ok {
165                 estr := BufKeyStrings[e]
166                 for _, s := range MultiActions {
167                         if s == estr {
168                                 cursors := h.Buf.GetCursors()
169                                 for _, c := range cursors {
170                                         h.Buf.SetCurCursor(c.Num)
171                                         h.Cursor = c
172                                         if action(h) {
173                                                 h.Win.Relocate()
174                                         }
175                                 }
176                                 return true
177                         }
178                 }
179                 if action(h) {
180                         h.Win.Relocate()
181                 }
182                 return true
183         }
184         return false
185 }
186
187 // DoMouseEvent executes a mouse event by finding the action it is bound
188 // to and executing it
189 func (h *BufHandler) DoMouseEvent(e MouseEvent, te *tcell.EventMouse) bool {
190         if action, ok := BufMouseBindings[e]; ok {
191                 if action(h, te) {
192                         h.Win.Relocate()
193                 }
194                 return true
195         }
196         return false
197 }
198
199 // DoRuneInsert inserts a given rune into the current buffer
200 // (possibly multiple times for multiple cursors)
201 func (h *BufHandler) DoRuneInsert(r rune) {
202         cursors := h.Buf.GetCursors()
203         for _, c := range cursors {
204                 // Insert a character
205                 if c.HasSelection() {
206                         c.DeleteSelection()
207                         c.ResetSelection()
208                 }
209
210                 if h.isOverwriteMode {
211                         next := c.Loc
212                         next.X++
213                         h.Buf.Replace(c.Loc, next, string(r))
214                 } else {
215                         h.Buf.Insert(c.Loc, string(r))
216                 }
217         }
218 }
219
220 func (h *BufHandler) vsplit(buf *buffer.Buffer) {
221         e := NewBufEditPane(0, 0, 0, 0, buf)
222         e.splitID = MainTab.GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool))
223         MainTab.Panes = append(MainTab.Panes, e)
224         MainTab.Resize()
225         MainTab.SetActive(len(MainTab.Panes) - 1)
226 }
227 func (h *BufHandler) hsplit(buf *buffer.Buffer) {
228         e := NewBufEditPane(0, 0, 0, 0, buf)
229         e.splitID = MainTab.GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool))
230         MainTab.Panes = append(MainTab.Panes, e)
231         MainTab.Resize()
232         MainTab.SetActive(len(MainTab.Panes) - 1)
233 }
234
235 // BufKeyActions contains the list of all possible key actions the bufhandler could execute
236 var BufKeyActions = map[string]BufKeyAction{
237         "CursorUp":               (*BufHandler).CursorUp,
238         "CursorDown":             (*BufHandler).CursorDown,
239         "CursorPageUp":           (*BufHandler).CursorPageUp,
240         "CursorPageDown":         (*BufHandler).CursorPageDown,
241         "CursorLeft":             (*BufHandler).CursorLeft,
242         "CursorRight":            (*BufHandler).CursorRight,
243         "CursorStart":            (*BufHandler).CursorStart,
244         "CursorEnd":              (*BufHandler).CursorEnd,
245         "SelectToStart":          (*BufHandler).SelectToStart,
246         "SelectToEnd":            (*BufHandler).SelectToEnd,
247         "SelectUp":               (*BufHandler).SelectUp,
248         "SelectDown":             (*BufHandler).SelectDown,
249         "SelectLeft":             (*BufHandler).SelectLeft,
250         "SelectRight":            (*BufHandler).SelectRight,
251         "WordRight":              (*BufHandler).WordRight,
252         "WordLeft":               (*BufHandler).WordLeft,
253         "SelectWordRight":        (*BufHandler).SelectWordRight,
254         "SelectWordLeft":         (*BufHandler).SelectWordLeft,
255         "DeleteWordRight":        (*BufHandler).DeleteWordRight,
256         "DeleteWordLeft":         (*BufHandler).DeleteWordLeft,
257         "SelectLine":             (*BufHandler).SelectLine,
258         "SelectToStartOfLine":    (*BufHandler).SelectToStartOfLine,
259         "SelectToEndOfLine":      (*BufHandler).SelectToEndOfLine,
260         "ParagraphPrevious":      (*BufHandler).ParagraphPrevious,
261         "ParagraphNext":          (*BufHandler).ParagraphNext,
262         "InsertNewline":          (*BufHandler).InsertNewline,
263         "InsertSpace":            (*BufHandler).InsertSpace,
264         "Backspace":              (*BufHandler).Backspace,
265         "Delete":                 (*BufHandler).Delete,
266         "InsertTab":              (*BufHandler).InsertTab,
267         "Save":                   (*BufHandler).Save,
268         "SaveAll":                (*BufHandler).SaveAll,
269         "SaveAs":                 (*BufHandler).SaveAs,
270         "Find":                   (*BufHandler).Find,
271         "FindNext":               (*BufHandler).FindNext,
272         "FindPrevious":           (*BufHandler).FindPrevious,
273         "Center":                 (*BufHandler).Center,
274         "Undo":                   (*BufHandler).Undo,
275         "Redo":                   (*BufHandler).Redo,
276         "Copy":                   (*BufHandler).Copy,
277         "Cut":                    (*BufHandler).Cut,
278         "CutLine":                (*BufHandler).CutLine,
279         "DuplicateLine":          (*BufHandler).DuplicateLine,
280         "DeleteLine":             (*BufHandler).DeleteLine,
281         "MoveLinesUp":            (*BufHandler).MoveLinesUp,
282         "MoveLinesDown":          (*BufHandler).MoveLinesDown,
283         "IndentSelection":        (*BufHandler).IndentSelection,
284         "OutdentSelection":       (*BufHandler).OutdentSelection,
285         "OutdentLine":            (*BufHandler).OutdentLine,
286         "Paste":                  (*BufHandler).Paste,
287         "PastePrimary":           (*BufHandler).PastePrimary,
288         "SelectAll":              (*BufHandler).SelectAll,
289         "OpenFile":               (*BufHandler).OpenFile,
290         "Start":                  (*BufHandler).Start,
291         "End":                    (*BufHandler).End,
292         "PageUp":                 (*BufHandler).PageUp,
293         "PageDown":               (*BufHandler).PageDown,
294         "SelectPageUp":           (*BufHandler).SelectPageUp,
295         "SelectPageDown":         (*BufHandler).SelectPageDown,
296         "HalfPageUp":             (*BufHandler).HalfPageUp,
297         "HalfPageDown":           (*BufHandler).HalfPageDown,
298         "StartOfLine":            (*BufHandler).StartOfLine,
299         "EndOfLine":              (*BufHandler).EndOfLine,
300         "ToggleHelp":             (*BufHandler).ToggleHelp,
301         "ToggleKeyMenu":          (*BufHandler).ToggleKeyMenu,
302         "ToggleRuler":            (*BufHandler).ToggleRuler,
303         "JumpLine":               (*BufHandler).JumpLine,
304         "ClearStatus":            (*BufHandler).ClearStatus,
305         "ShellMode":              (*BufHandler).ShellMode,
306         "CommandMode":            (*BufHandler).CommandMode,
307         "ToggleOverwriteMode":    (*BufHandler).ToggleOverwriteMode,
308         "Escape":                 (*BufHandler).Escape,
309         "Quit":                   (*BufHandler).Quit,
310         "QuitAll":                (*BufHandler).QuitAll,
311         "AddTab":                 (*BufHandler).AddTab,
312         "PreviousTab":            (*BufHandler).PreviousTab,
313         "NextTab":                (*BufHandler).NextTab,
314         "NextSplit":              (*BufHandler).NextSplit,
315         "PreviousSplit":          (*BufHandler).PreviousSplit,
316         "Unsplit":                (*BufHandler).Unsplit,
317         "VSplit":                 (*BufHandler).VSplitBinding,
318         "HSplit":                 (*BufHandler).HSplitBinding,
319         "ToggleMacro":            (*BufHandler).ToggleMacro,
320         "PlayMacro":              (*BufHandler).PlayMacro,
321         "Suspend":                (*BufHandler).Suspend,
322         "ScrollUp":               (*BufHandler).ScrollUpAction,
323         "ScrollDown":             (*BufHandler).ScrollDownAction,
324         "SpawnMultiCursor":       (*BufHandler).SpawnMultiCursor,
325         "SpawnMultiCursorSelect": (*BufHandler).SpawnMultiCursorSelect,
326         "RemoveMultiCursor":      (*BufHandler).RemoveMultiCursor,
327         "RemoveAllMultiCursors":  (*BufHandler).RemoveAllMultiCursors,
328         "SkipMultiCursor":        (*BufHandler).SkipMultiCursor,
329         "JumpToMatchingBrace":    (*BufHandler).JumpToMatchingBrace,
330
331         // This was changed to InsertNewline but I don't want to break backwards compatibility
332         "InsertEnter": (*BufHandler).InsertNewline,
333 }
334
335 // BufMouseActions contains the list of all possible mouse actions the bufhandler could execute
336 var BufMouseActions = map[string]BufMouseAction{
337         "MousePress":       (*BufHandler).MousePress,
338         "MouseMultiCursor": (*BufHandler).MouseMultiCursor,
339 }
340
341 // MultiActions is a list of actions that should be executed multiple
342 // times if there are multiple cursors (one per cursor)
343 // Generally actions that modify global editor state like quitting or
344 // saving should not be included in this list
345 var MultiActions = []string{
346         "CursorUp",
347         "CursorDown",
348         "CursorPageUp",
349         "CursorPageDown",
350         "CursorLeft",
351         "CursorRight",
352         "CursorStart",
353         "CursorEnd",
354         "SelectToStart",
355         "SelectToEnd",
356         "SelectUp",
357         "SelectDown",
358         "SelectLeft",
359         "SelectRight",
360         "WordRight",
361         "WordLeft",
362         "SelectWordRight",
363         "SelectWordLeft",
364         "DeleteWordRight",
365         "DeleteWordLeft",
366         "SelectLine",
367         "SelectToStartOfLine",
368         "SelectToEndOfLine",
369         "ParagraphPrevious",
370         "ParagraphNext",
371         "InsertNewline",
372         "InsertSpace",
373         "Backspace",
374         "Delete",
375         "InsertTab",
376         "FindNext",
377         "FindPrevious",
378         "Cut",
379         "CutLine",
380         "DuplicateLine",
381         "DeleteLine",
382         "MoveLinesUp",
383         "MoveLinesDown",
384         "IndentSelection",
385         "OutdentSelection",
386         "OutdentLine",
387         "Paste",
388         "PastePrimary",
389         "SelectPageUp",
390         "SelectPageDown",
391         "StartOfLine",
392         "EndOfLine",
393         "JumpToMatchingBrace",
394 }