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