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