]> git.lizzy.rs Git - micro.git/blob - cmd/micro/bindings.go
Add the ability to close splits and change splits using the mouse
[micro.git] / cmd / micro / bindings.go
1 package main
2
3 import (
4         "encoding/json"
5         "io/ioutil"
6         "os"
7         "strconv"
8         "strings"
9         "time"
10
11         "github.com/mitchellh/go-homedir"
12         "github.com/zyedidia/clipboard"
13         "github.com/zyedidia/tcell"
14 )
15
16 var bindings map[Key][]func(*View) bool
17 var helpBinding string
18
19 var bindingActions = map[string]func(*View) bool{
20         "CursorUp":            (*View).CursorUp,
21         "CursorDown":          (*View).CursorDown,
22         "CursorPageUp":        (*View).CursorPageUp,
23         "CursorPageDown":      (*View).CursorPageDown,
24         "CursorLeft":          (*View).CursorLeft,
25         "CursorRight":         (*View).CursorRight,
26         "CursorStart":         (*View).CursorStart,
27         "CursorEnd":           (*View).CursorEnd,
28         "SelectToStart":       (*View).SelectToStart,
29         "SelectToEnd":         (*View).SelectToEnd,
30         "SelectUp":            (*View).SelectUp,
31         "SelectDown":          (*View).SelectDown,
32         "SelectLeft":          (*View).SelectLeft,
33         "SelectRight":         (*View).SelectRight,
34         "WordRight":           (*View).WordRight,
35         "WordLeft":            (*View).WordLeft,
36         "SelectWordRight":     (*View).SelectWordRight,
37         "SelectWordLeft":      (*View).SelectWordLeft,
38         "DeleteWordRight":     (*View).DeleteWordRight,
39         "DeleteWordLeft":      (*View).DeleteWordLeft,
40         "SelectToStartOfLine": (*View).SelectToStartOfLine,
41         "SelectToEndOfLine":   (*View).SelectToEndOfLine,
42         "InsertEnter":         (*View).InsertEnter,
43         "InsertSpace":         (*View).InsertSpace,
44         "Backspace":           (*View).Backspace,
45         "Delete":              (*View).Delete,
46         "InsertTab":           (*View).InsertTab,
47         "Save":                (*View).Save,
48         "Find":                (*View).Find,
49         "FindNext":            (*View).FindNext,
50         "FindPrevious":        (*View).FindPrevious,
51         "Undo":                (*View).Undo,
52         "Redo":                (*View).Redo,
53         "Copy":                (*View).Copy,
54         "Cut":                 (*View).Cut,
55         "CutLine":             (*View).CutLine,
56         "DuplicateLine":       (*View).DuplicateLine,
57         "DeleteLine":          (*View).DeleteLine,
58         "Paste":               (*View).Paste,
59         "SelectAll":           (*View).SelectAll,
60         "OpenFile":            (*View).OpenFile,
61         "Start":               (*View).Start,
62         "End":                 (*View).End,
63         "PageUp":              (*View).PageUp,
64         "PageDown":            (*View).PageDown,
65         "HalfPageUp":          (*View).HalfPageUp,
66         "HalfPageDown":        (*View).HalfPageDown,
67         "StartOfLine":         (*View).StartOfLine,
68         "EndOfLine":           (*View).EndOfLine,
69         "ToggleHelp":          (*View).ToggleHelp,
70         "ToggleRuler":         (*View).ToggleRuler,
71         "JumpLine":            (*View).JumpLine,
72         "ClearStatus":         (*View).ClearStatus,
73         "ShellMode":           (*View).ShellMode,
74         "CommandMode":         (*View).CommandMode,
75         "Quit":                (*View).Quit,
76         "AddTab":              (*View).AddTab,
77         "PreviousTab":         (*View).PreviousTab,
78         "NextTab":             (*View).NextTab,
79 }
80
81 var bindingKeys = map[string]tcell.Key{
82         "Up":             tcell.KeyUp,
83         "Down":           tcell.KeyDown,
84         "Right":          tcell.KeyRight,
85         "Left":           tcell.KeyLeft,
86         "UpLeft":         tcell.KeyUpLeft,
87         "UpRight":        tcell.KeyUpRight,
88         "DownLeft":       tcell.KeyDownLeft,
89         "DownRight":      tcell.KeyDownRight,
90         "Center":         tcell.KeyCenter,
91         "PageUp":         tcell.KeyPgUp,
92         "PageDown":       tcell.KeyPgDn,
93         "Home":           tcell.KeyHome,
94         "End":            tcell.KeyEnd,
95         "Insert":         tcell.KeyInsert,
96         "Delete":         tcell.KeyDelete,
97         "Help":           tcell.KeyHelp,
98         "Exit":           tcell.KeyExit,
99         "Clear":          tcell.KeyClear,
100         "Cancel":         tcell.KeyCancel,
101         "Print":          tcell.KeyPrint,
102         "Pause":          tcell.KeyPause,
103         "Backtab":        tcell.KeyBacktab,
104         "F1":             tcell.KeyF1,
105         "F2":             tcell.KeyF2,
106         "F3":             tcell.KeyF3,
107         "F4":             tcell.KeyF4,
108         "F5":             tcell.KeyF5,
109         "F6":             tcell.KeyF6,
110         "F7":             tcell.KeyF7,
111         "F8":             tcell.KeyF8,
112         "F9":             tcell.KeyF9,
113         "F10":            tcell.KeyF10,
114         "F11":            tcell.KeyF11,
115         "F12":            tcell.KeyF12,
116         "F13":            tcell.KeyF13,
117         "F14":            tcell.KeyF14,
118         "F15":            tcell.KeyF15,
119         "F16":            tcell.KeyF16,
120         "F17":            tcell.KeyF17,
121         "F18":            tcell.KeyF18,
122         "F19":            tcell.KeyF19,
123         "F20":            tcell.KeyF20,
124         "F21":            tcell.KeyF21,
125         "F22":            tcell.KeyF22,
126         "F23":            tcell.KeyF23,
127         "F24":            tcell.KeyF24,
128         "F25":            tcell.KeyF25,
129         "F26":            tcell.KeyF26,
130         "F27":            tcell.KeyF27,
131         "F28":            tcell.KeyF28,
132         "F29":            tcell.KeyF29,
133         "F30":            tcell.KeyF30,
134         "F31":            tcell.KeyF31,
135         "F32":            tcell.KeyF32,
136         "F33":            tcell.KeyF33,
137         "F34":            tcell.KeyF34,
138         "F35":            tcell.KeyF35,
139         "F36":            tcell.KeyF36,
140         "F37":            tcell.KeyF37,
141         "F38":            tcell.KeyF38,
142         "F39":            tcell.KeyF39,
143         "F40":            tcell.KeyF40,
144         "F41":            tcell.KeyF41,
145         "F42":            tcell.KeyF42,
146         "F43":            tcell.KeyF43,
147         "F44":            tcell.KeyF44,
148         "F45":            tcell.KeyF45,
149         "F46":            tcell.KeyF46,
150         "F47":            tcell.KeyF47,
151         "F48":            tcell.KeyF48,
152         "F49":            tcell.KeyF49,
153         "F50":            tcell.KeyF50,
154         "F51":            tcell.KeyF51,
155         "F52":            tcell.KeyF52,
156         "F53":            tcell.KeyF53,
157         "F54":            tcell.KeyF54,
158         "F55":            tcell.KeyF55,
159         "F56":            tcell.KeyF56,
160         "F57":            tcell.KeyF57,
161         "F58":            tcell.KeyF58,
162         "F59":            tcell.KeyF59,
163         "F60":            tcell.KeyF60,
164         "F61":            tcell.KeyF61,
165         "F62":            tcell.KeyF62,
166         "F63":            tcell.KeyF63,
167         "F64":            tcell.KeyF64,
168         "CtrlSpace":      tcell.KeyCtrlSpace,
169         "CtrlA":          tcell.KeyCtrlA,
170         "CtrlB":          tcell.KeyCtrlB,
171         "CtrlC":          tcell.KeyCtrlC,
172         "CtrlD":          tcell.KeyCtrlD,
173         "CtrlE":          tcell.KeyCtrlE,
174         "CtrlF":          tcell.KeyCtrlF,
175         "CtrlG":          tcell.KeyCtrlG,
176         "CtrlH":          tcell.KeyCtrlH,
177         "CtrlI":          tcell.KeyCtrlI,
178         "CtrlJ":          tcell.KeyCtrlJ,
179         "CtrlK":          tcell.KeyCtrlK,
180         "CtrlL":          tcell.KeyCtrlL,
181         "CtrlM":          tcell.KeyCtrlM,
182         "CtrlN":          tcell.KeyCtrlN,
183         "CtrlO":          tcell.KeyCtrlO,
184         "CtrlP":          tcell.KeyCtrlP,
185         "CtrlQ":          tcell.KeyCtrlQ,
186         "CtrlR":          tcell.KeyCtrlR,
187         "CtrlS":          tcell.KeyCtrlS,
188         "CtrlT":          tcell.KeyCtrlT,
189         "CtrlU":          tcell.KeyCtrlU,
190         "CtrlV":          tcell.KeyCtrlV,
191         "CtrlW":          tcell.KeyCtrlW,
192         "CtrlX":          tcell.KeyCtrlX,
193         "CtrlY":          tcell.KeyCtrlY,
194         "CtrlZ":          tcell.KeyCtrlZ,
195         "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
196         "CtrlBackslash":  tcell.KeyCtrlBackslash,
197         "CtrlRightSq":    tcell.KeyCtrlRightSq,
198         "CtrlCarat":      tcell.KeyCtrlCarat,
199         "CtrlUnderscore": tcell.KeyCtrlUnderscore,
200         "Backspace":      tcell.KeyBackspace,
201         "Tab":            tcell.KeyTab,
202         "Esc":            tcell.KeyEsc,
203         "Escape":         tcell.KeyEscape,
204         "Enter":          tcell.KeyEnter,
205         "Backspace2":     tcell.KeyBackspace2,
206
207         // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
208         "PgUp":   tcell.KeyPgUp,
209         "PgDown": tcell.KeyPgDn,
210 }
211
212 // The Key struct holds the data for a keypress (keycode + modifiers)
213 type Key struct {
214         keyCode   tcell.Key
215         modifiers tcell.ModMask
216         r         rune
217 }
218
219 // InitBindings initializes the keybindings for micro
220 func InitBindings() {
221         bindings = make(map[Key][]func(*View) bool)
222
223         var parsed map[string]string
224         defaults := DefaultBindings()
225
226         filename := configDir + "/bindings.json"
227         if _, e := os.Stat(filename); e == nil {
228                 input, err := ioutil.ReadFile(filename)
229                 if err != nil {
230                         TermMessage("Error reading bindings.json file: " + err.Error())
231                         return
232                 }
233
234                 err = json.Unmarshal(input, &parsed)
235                 if err != nil {
236                         TermMessage("Error reading bindings.json:", err.Error())
237                 }
238         }
239
240         parseBindings(defaults)
241         parseBindings(parsed)
242 }
243
244 func parseBindings(userBindings map[string]string) {
245         for k, v := range userBindings {
246                 BindKey(k, v)
247         }
248 }
249
250 // findKey will find binding Key 'b' using string 'k'
251 func findKey(k string) (b Key, ok bool) {
252         modifiers := tcell.ModNone
253
254         // First, we'll strip off all the modifiers in the name and add them to the
255         // ModMask
256 modSearch:
257         for {
258                 switch {
259                 case strings.HasPrefix(k, "-"):
260                         // We optionally support dashes between modifiers
261                         k = k[1:]
262                 case strings.HasPrefix(k, "Ctrl"):
263                         k = k[4:]
264                         modifiers |= tcell.ModCtrl
265                 case strings.HasPrefix(k, "Alt"):
266                         k = k[3:]
267                         modifiers |= tcell.ModAlt
268                 case strings.HasPrefix(k, "Shift"):
269                         k = k[5:]
270                         modifiers |= tcell.ModShift
271                 default:
272                         break modSearch
273                 }
274         }
275
276         // Control is handled specially, since some character codes in bindingKeys
277         // are different when Control is depressed. We should check for Control keys
278         // first.
279         if modifiers&tcell.ModCtrl != 0 {
280                 // see if the key is in bindingKeys with the Ctrl prefix.
281                 if code, ok := bindingKeys["Ctrl"+k]; ok {
282                         // It is, we're done.
283                         return Key{
284                                 keyCode:   code,
285                                 modifiers: modifiers,
286                                 r:         0,
287                         }, true
288                 }
289         }
290
291         // See if we can find the key in bindingKeys
292         if code, ok := bindingKeys[k]; ok {
293                 return Key{
294                         keyCode:   code,
295                         modifiers: modifiers,
296                         r:         0,
297                 }, true
298         }
299
300         // If we were given one character, then we've got a rune.
301         if len(k) == 1 {
302                 return Key{
303                         keyCode:   tcell.KeyRune,
304                         modifiers: modifiers,
305                         r:         rune(k[0]),
306                 }, true
307         }
308
309         // We don't know what happened.
310         return Key{}, false
311 }
312
313 // findAction will find 'action' using string 'v'
314 func findAction(v string) (action func(*View) bool) {
315         action, ok := bindingActions[v]
316         if !ok {
317                 // If the user seems to be binding a function that doesn't exist
318                 // We hope that it's a lua function that exists and bind it to that
319                 action = LuaFunctionBinding(v)
320         }
321         return action
322 }
323
324 // BindKey takes a key and an action and binds the two together
325 func BindKey(k, v string) {
326         key, ok := findKey(k)
327         if !ok {
328                 return
329         }
330         if v == "ToggleHelp" {
331                 helpBinding = k
332         }
333
334         actionNames := strings.Split(v, ",")
335         actions := make([]func(*View) bool, 0, len(actionNames))
336         for _, actionName := range actionNames {
337                 actions = append(actions, findAction(actionName))
338         }
339
340         bindings[key] = actions
341 }
342
343 // DefaultBindings returns a map containing micro's default keybindings
344 func DefaultBindings() map[string]string {
345         return map[string]string{
346                 "Up":             "CursorUp",
347                 "Down":           "CursorDown",
348                 "Right":          "CursorRight",
349                 "Left":           "CursorLeft",
350                 "ShiftUp":        "SelectUp",
351                 "ShiftDown":      "SelectDown",
352                 "ShiftLeft":      "SelectLeft",
353                 "ShiftRight":     "SelectRight",
354                 "AltLeft":        "WordLeft",
355                 "AltRight":       "WordRight",
356                 "AltShiftRight":  "SelectWordRight",
357                 "AltShiftLeft":   "SelectWordLeft",
358                 "CtrlLeft":       "StartOfLine",
359                 "CtrlRight":      "EndOfLine",
360                 "CtrlShiftLeft":  "SelectToStartOfLine",
361                 "CtrlShiftRight": "SelectToEndOfLine",
362                 "CtrlUp":         "CursorStart",
363                 "CtrlDown":       "CursorEnd",
364                 "CtrlShiftUp":    "SelectToStart",
365                 "CtrlShiftDown":  "SelectToEnd",
366                 "Enter":          "InsertEnter",
367                 "Space":          "InsertSpace",
368                 "Backspace":      "Backspace",
369                 "Backspace2":     "Backspace",
370                 "Alt-Backspace":  "DeleteWordLeft",
371                 "Alt-Backspace2": "DeleteWordLeft",
372                 "Tab":            "InsertTab",
373                 "CtrlO":          "OpenFile",
374                 "CtrlS":          "Save",
375                 "CtrlF":          "Find",
376                 "CtrlN":          "FindNext",
377                 "CtrlP":          "FindPrevious",
378                 "CtrlZ":          "Undo",
379                 "CtrlY":          "Redo",
380                 "CtrlC":          "Copy",
381                 "CtrlX":          "Cut",
382                 "CtrlK":          "CutLine",
383                 "CtrlD":          "DuplicateLine",
384                 "CtrlV":          "Paste",
385                 "CtrlA":          "SelectAll",
386                 "CtrlT":          "AddTab",
387                 "CtrlRightSq":    "PreviousTab",
388                 "CtrlBackslash":  "NextTab",
389                 "Home":           "Start",
390                 "End":            "End",
391                 "PageUp":         "CursorPageUp",
392                 "PageDown":       "CursorPageDown",
393                 "CtrlG":          "ToggleHelp",
394                 "CtrlR":          "ToggleRuler",
395                 "CtrlL":          "JumpLine",
396                 "Delete":         "Delete",
397                 "Esc":            "ClearStatus",
398                 "CtrlB":          "ShellMode",
399                 "CtrlQ":          "Quit",
400                 "CtrlE":          "CommandMode",
401
402                 // Emacs-style keybindings
403                 "Alt-f": "WordRight",
404                 "Alt-b": "WordLeft",
405                 "Alt-a": "StartOfLine",
406                 "Alt-e": "EndOfLine",
407                 "Alt-p": "CursorUp",
408                 "Alt-n": "CursorDown",
409         }
410 }
411
412 // CursorUp moves the cursor up
413 func (v *View) CursorUp() bool {
414         if v.Cursor.HasSelection() {
415                 v.Cursor.Loc = v.Cursor.CurSelection[0]
416                 v.Cursor.ResetSelection()
417         }
418         v.Cursor.Up()
419         return true
420 }
421
422 // CursorDown moves the cursor down
423 func (v *View) CursorDown() bool {
424         if v.Cursor.HasSelection() {
425                 v.Cursor.Loc = v.Cursor.CurSelection[1]
426                 v.Cursor.ResetSelection()
427         }
428         v.Cursor.Down()
429         return true
430 }
431
432 // CursorLeft moves the cursor left
433 func (v *View) CursorLeft() bool {
434         if v.Cursor.HasSelection() {
435                 v.Cursor.Loc = v.Cursor.CurSelection[0]
436                 v.Cursor.ResetSelection()
437         } else {
438                 v.Cursor.Left()
439         }
440         return true
441 }
442
443 // CursorRight moves the cursor right
444 func (v *View) CursorRight() bool {
445         if v.Cursor.HasSelection() {
446                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
447                 v.Cursor.ResetSelection()
448         } else {
449                 v.Cursor.Right()
450         }
451         return true
452 }
453
454 // WordRight moves the cursor one word to the right
455 func (v *View) WordRight() bool {
456         v.Cursor.WordRight()
457         return true
458 }
459
460 // WordLeft moves the cursor one word to the left
461 func (v *View) WordLeft() bool {
462         v.Cursor.WordLeft()
463         return true
464 }
465
466 // SelectUp selects up one line
467 func (v *View) SelectUp() bool {
468         if !v.Cursor.HasSelection() {
469                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
470         }
471         v.Cursor.Up()
472         v.Cursor.SelectTo(v.Cursor.Loc)
473         return true
474 }
475
476 // SelectDown selects down one line
477 func (v *View) SelectDown() bool {
478         if !v.Cursor.HasSelection() {
479                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
480         }
481         v.Cursor.Down()
482         v.Cursor.SelectTo(v.Cursor.Loc)
483         return true
484 }
485
486 // SelectLeft selects the character to the left of the cursor
487 func (v *View) SelectLeft() bool {
488         loc := v.Cursor.Loc
489         count := v.Buf.End().Move(-1, v.Buf)
490         if loc.GreaterThan(count) {
491                 loc = count
492         }
493         if !v.Cursor.HasSelection() {
494                 v.Cursor.OrigSelection[0] = loc
495         }
496         v.Cursor.Left()
497         v.Cursor.SelectTo(v.Cursor.Loc)
498         return true
499 }
500
501 // SelectRight selects the character to the right of the cursor
502 func (v *View) SelectRight() bool {
503         loc := v.Cursor.Loc
504         count := v.Buf.End().Move(-1, v.Buf)
505         if loc.GreaterThan(count) {
506                 loc = count
507         }
508         if !v.Cursor.HasSelection() {
509                 v.Cursor.OrigSelection[0] = loc
510         }
511         v.Cursor.Right()
512         v.Cursor.SelectTo(v.Cursor.Loc)
513         return true
514 }
515
516 // SelectWordRight selects the word to the right of the cursor
517 func (v *View) SelectWordRight() bool {
518         if !v.Cursor.HasSelection() {
519                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
520         }
521         v.Cursor.WordRight()
522         v.Cursor.SelectTo(v.Cursor.Loc)
523         return true
524 }
525
526 // SelectWordLeft selects the word to the left of the cursor
527 func (v *View) SelectWordLeft() bool {
528         if !v.Cursor.HasSelection() {
529                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
530         }
531         v.Cursor.WordLeft()
532         v.Cursor.SelectTo(v.Cursor.Loc)
533         return true
534 }
535
536 // StartOfLine moves the cursor to the start of the line
537 func (v *View) StartOfLine() bool {
538         v.Cursor.Start()
539         return true
540 }
541
542 // EndOfLine moves the cursor to the end of the line
543 func (v *View) EndOfLine() bool {
544         v.Cursor.End()
545         return true
546 }
547
548 // SelectToStartOfLine selects to the start of the current line
549 func (v *View) SelectToStartOfLine() bool {
550         if !v.Cursor.HasSelection() {
551                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
552         }
553         v.Cursor.Start()
554         v.Cursor.SelectTo(v.Cursor.Loc)
555         return true
556 }
557
558 // SelectToEndOfLine selects to the end of the current line
559 func (v *View) SelectToEndOfLine() bool {
560         if !v.Cursor.HasSelection() {
561                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
562         }
563         v.Cursor.End()
564         v.Cursor.SelectTo(v.Cursor.Loc)
565         return true
566 }
567
568 // CursorStart moves the cursor to the start of the buffer
569 func (v *View) CursorStart() bool {
570         v.Cursor.X = 0
571         v.Cursor.Y = 0
572         return true
573 }
574
575 // CursorEnd moves the cursor to the end of the buffer
576 func (v *View) CursorEnd() bool {
577         v.Cursor.Loc = v.Buf.End()
578         return true
579 }
580
581 // SelectToStart selects the text from the cursor to the start of the buffer
582 func (v *View) SelectToStart() bool {
583         if !v.Cursor.HasSelection() {
584                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
585         }
586         v.CursorStart()
587         v.Cursor.SelectTo(v.Buf.Start())
588         return true
589 }
590
591 // SelectToEnd selects the text from the cursor to the end of the buffer
592 func (v *View) SelectToEnd() bool {
593         if !v.Cursor.HasSelection() {
594                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
595         }
596         v.CursorEnd()
597         v.Cursor.SelectTo(v.Buf.End())
598         return true
599 }
600
601 // InsertSpace inserts a space
602 func (v *View) InsertSpace() bool {
603         if v.Cursor.HasSelection() {
604                 v.Cursor.DeleteSelection()
605                 v.Cursor.ResetSelection()
606         }
607         v.Buf.Insert(v.Cursor.Loc, " ")
608         v.Cursor.Right()
609         return true
610 }
611
612 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
613 func (v *View) InsertEnter() bool {
614         // Insert a newline
615         if v.Cursor.HasSelection() {
616                 v.Cursor.DeleteSelection()
617                 v.Cursor.ResetSelection()
618         }
619
620         v.Buf.Insert(v.Cursor.Loc, "\n")
621         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
622         v.Cursor.Right()
623
624         if settings["autoindent"].(bool) {
625                 v.Buf.Insert(v.Cursor.Loc, ws)
626                 for i := 0; i < len(ws); i++ {
627                         v.Cursor.Right()
628                 }
629         }
630         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
631         return true
632 }
633
634 // Backspace deletes the previous character
635 func (v *View) Backspace() bool {
636         // Delete a character
637         if v.Cursor.HasSelection() {
638                 v.Cursor.DeleteSelection()
639                 v.Cursor.ResetSelection()
640         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
641                 // We have to do something a bit hacky here because we want to
642                 // delete the line by first moving left and then deleting backwards
643                 // but the undo redo would place the cursor in the wrong place
644                 // So instead we move left, save the position, move back, delete
645                 // and restore the position
646
647                 // If the user is using spaces instead of tabs and they are deleting
648                 // whitespace at the start of the line, we should delete as if its a
649                 // tab (tabSize number of spaces)
650                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
651                 tabSize := int(settings["tabsize"].(float64))
652                 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
653                         loc := v.Cursor.Loc
654                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
655                         cx, cy := v.Cursor.X, v.Cursor.Y
656                         v.Cursor.Loc = loc
657                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
658                         v.Cursor.X, v.Cursor.Y = cx, cy
659                 } else {
660                         v.Cursor.Left()
661                         cx, cy := v.Cursor.X, v.Cursor.Y
662                         v.Cursor.Right()
663                         loc := v.Cursor.Loc
664                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
665                         v.Cursor.X, v.Cursor.Y = cx, cy
666                 }
667         }
668         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
669         return true
670 }
671
672 // DeleteWordRight deletes the word to the right of the cursor
673 func (v *View) DeleteWordRight() bool {
674         v.SelectWordRight()
675         if v.Cursor.HasSelection() {
676                 v.Cursor.DeleteSelection()
677                 v.Cursor.ResetSelection()
678         }
679         return true
680 }
681
682 // DeleteWordLeft deletes the word to the left of the cursor
683 func (v *View) DeleteWordLeft() bool {
684         v.SelectWordLeft()
685         if v.Cursor.HasSelection() {
686                 v.Cursor.DeleteSelection()
687                 v.Cursor.ResetSelection()
688         }
689         return true
690 }
691
692 // Delete deletes the next character
693 func (v *View) Delete() bool {
694         if v.Cursor.HasSelection() {
695                 v.Cursor.DeleteSelection()
696                 v.Cursor.ResetSelection()
697         } else {
698                 loc := v.Cursor.Loc
699                 if loc.LessThan(v.Buf.End()) {
700                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
701                 }
702         }
703         return true
704 }
705
706 // InsertTab inserts a tab or spaces
707 func (v *View) InsertTab() bool {
708         // Insert a tab
709         if v.Cursor.HasSelection() {
710                 v.Cursor.DeleteSelection()
711                 v.Cursor.ResetSelection()
712         }
713         if settings["tabstospaces"].(bool) {
714                 tabSize := int(settings["tabsize"].(float64))
715                 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
716                 for i := 0; i < tabSize; i++ {
717                         v.Cursor.Right()
718                 }
719         } else {
720                 v.Buf.Insert(v.Cursor.Loc, "\t")
721                 v.Cursor.Right()
722         }
723         return true
724 }
725
726 // Save the buffer to disk
727 func (v *View) Save() bool {
728         if v.helpOpen {
729                 // We can't save the help text
730                 return false
731         }
732         // If this is an empty buffer, ask for a filename
733         if v.Buf.Path == "" {
734                 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
735                 if !canceled {
736                         v.Buf.Path = filename
737                         v.Buf.Name = filename
738                 } else {
739                         return false
740                 }
741         }
742         err := v.Buf.Save()
743         if err != nil {
744                 if strings.HasSuffix(err.Error(), "permission denied") {
745                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
746                         if choice {
747                                 err = v.Buf.SaveWithSudo()
748                                 if err != nil {
749                                         messenger.Error(err.Error())
750                                         return false
751                                 }
752                                 messenger.Message("Saved " + v.Buf.Path)
753                         }
754                         messenger.Reset()
755                         messenger.Clear()
756                 } else {
757                         messenger.Error(err.Error())
758                 }
759         } else {
760                 messenger.Message("Saved " + v.Buf.Path)
761         }
762         return false
763 }
764
765 // Find opens a prompt and searches forward for the input
766 func (v *View) Find() bool {
767         if v.Cursor.HasSelection() {
768                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
769         } else {
770                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
771         }
772         BeginSearch()
773         return true
774 }
775
776 // FindNext searches forwards for the last used search term
777 func (v *View) FindNext() bool {
778         if v.Cursor.HasSelection() {
779                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
780         } else {
781                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
782         }
783         messenger.Message("Finding: " + lastSearch)
784         Search(lastSearch, v, true)
785         return true
786 }
787
788 // FindPrevious searches backwards for the last used search term
789 func (v *View) FindPrevious() bool {
790         if v.Cursor.HasSelection() {
791                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
792         } else {
793                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
794         }
795         messenger.Message("Finding: " + lastSearch)
796         Search(lastSearch, v, false)
797         return true
798 }
799
800 // Undo undoes the last action
801 func (v *View) Undo() bool {
802         v.Buf.Undo()
803         messenger.Message("Undid action")
804         return true
805 }
806
807 // Redo redoes the last action
808 func (v *View) Redo() bool {
809         v.Buf.Redo()
810         messenger.Message("Redid action")
811         return true
812 }
813
814 // Copy the selection to the system clipboard
815 func (v *View) Copy() bool {
816         if v.Cursor.HasSelection() {
817                 clipboard.WriteAll(v.Cursor.GetSelection())
818                 v.freshClip = true
819                 messenger.Message("Copied selection")
820         }
821         return true
822 }
823
824 // CutLine cuts the current line to the clipboard
825 func (v *View) CutLine() bool {
826         v.Cursor.SelectLine()
827         if !v.Cursor.HasSelection() {
828                 return false
829         }
830         if v.freshClip == true {
831                 if v.Cursor.HasSelection() {
832                         if clip, err := clipboard.ReadAll(); err != nil {
833                                 messenger.Error(err)
834                         } else {
835                                 clipboard.WriteAll(clip + v.Cursor.GetSelection())
836                         }
837                 }
838         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
839                 v.Copy()
840         }
841         v.freshClip = true
842         v.lastCutTime = time.Now()
843         v.Cursor.DeleteSelection()
844         v.Cursor.ResetSelection()
845         messenger.Message("Cut line")
846         return true
847 }
848
849 // Cut the selection to the system clipboard
850 func (v *View) Cut() bool {
851         if v.Cursor.HasSelection() {
852                 clipboard.WriteAll(v.Cursor.GetSelection())
853                 v.Cursor.DeleteSelection()
854                 v.Cursor.ResetSelection()
855                 v.freshClip = true
856                 messenger.Message("Cut selection")
857         }
858         return true
859 }
860
861 // DuplicateLine duplicates the current line
862 func (v *View) DuplicateLine() bool {
863         v.Cursor.End()
864         v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
865         v.Cursor.Right()
866         messenger.Message("Duplicated line")
867         return true
868 }
869
870 // DeleteLine deletes the current line
871 func (v *View) DeleteLine() bool {
872         v.Cursor.SelectLine()
873         if !v.Cursor.HasSelection() {
874                 return false
875         }
876         v.Cursor.DeleteSelection()
877         v.Cursor.ResetSelection()
878         messenger.Message("Deleted line")
879         return true
880 }
881
882 // Paste whatever is in the system clipboard into the buffer
883 // Delete and paste if the user has a selection
884 func (v *View) Paste() bool {
885         if v.Cursor.HasSelection() {
886                 v.Cursor.DeleteSelection()
887                 v.Cursor.ResetSelection()
888         }
889         clip, _ := clipboard.ReadAll()
890         v.Buf.Insert(v.Cursor.Loc, clip)
891         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
892         v.freshClip = false
893         messenger.Message("Pasted clipboard")
894         return true
895 }
896
897 // SelectAll selects the entire buffer
898 func (v *View) SelectAll() bool {
899         v.Cursor.CurSelection[0] = v.Buf.Start()
900         v.Cursor.CurSelection[1] = v.Buf.End()
901         // Put the cursor at the beginning
902         v.Cursor.X = 0
903         v.Cursor.Y = 0
904         return true
905 }
906
907 // OpenFile opens a new file in the buffer
908 func (v *View) OpenFile() bool {
909         if v.CanClose("Continue? (yes, no, save) ") {
910                 filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
911                 if canceled {
912                         return false
913                 }
914                 home, _ := homedir.Dir()
915                 filename = strings.Replace(filename, "~", home, 1)
916                 file, err := ioutil.ReadFile(filename)
917
918                 var buf *Buffer
919                 if err != nil {
920                         // File does not exist -- create an empty buffer with that name
921                         buf = NewBuffer([]byte{}, filename)
922                 } else {
923                         buf = NewBuffer(file, filename)
924                 }
925                 v.OpenBuffer(buf)
926                 return true
927         }
928         return false
929 }
930
931 // Start moves the viewport to the start of the buffer
932 func (v *View) Start() bool {
933         v.Topline = 0
934         return false
935 }
936
937 // End moves the viewport to the end of the buffer
938 func (v *View) End() bool {
939         if v.height > v.Buf.NumLines {
940                 v.Topline = 0
941         } else {
942                 v.Topline = v.Buf.NumLines - v.height
943         }
944         return false
945 }
946
947 // PageUp scrolls the view up a page
948 func (v *View) PageUp() bool {
949         if v.Topline > v.height {
950                 v.ScrollUp(v.height)
951         } else {
952                 v.Topline = 0
953         }
954         return false
955 }
956
957 // PageDown scrolls the view down a page
958 func (v *View) PageDown() bool {
959         if v.Buf.NumLines-(v.Topline+v.height) > v.height {
960                 v.ScrollDown(v.height)
961         } else if v.Buf.NumLines >= v.height {
962                 v.Topline = v.Buf.NumLines - v.height
963         }
964         return false
965 }
966
967 // CursorPageUp places the cursor a page up
968 func (v *View) CursorPageUp() bool {
969         if v.Cursor.HasSelection() {
970                 v.Cursor.Loc = v.Cursor.CurSelection[0]
971                 v.Cursor.ResetSelection()
972         }
973         v.Cursor.UpN(v.height)
974         return true
975 }
976
977 // CursorPageDown places the cursor a page up
978 func (v *View) CursorPageDown() bool {
979         if v.Cursor.HasSelection() {
980                 v.Cursor.Loc = v.Cursor.CurSelection[1]
981                 v.Cursor.ResetSelection()
982         }
983         v.Cursor.DownN(v.height)
984         return true
985 }
986
987 // HalfPageUp scrolls the view up half a page
988 func (v *View) HalfPageUp() bool {
989         if v.Topline > v.height/2 {
990                 v.ScrollUp(v.height / 2)
991         } else {
992                 v.Topline = 0
993         }
994         return false
995 }
996
997 // HalfPageDown scrolls the view down half a page
998 func (v *View) HalfPageDown() bool {
999         if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
1000                 v.ScrollDown(v.height / 2)
1001         } else {
1002                 if v.Buf.NumLines >= v.height {
1003                         v.Topline = v.Buf.NumLines - v.height
1004                 }
1005         }
1006         return false
1007 }
1008
1009 // ToggleRuler turns line numbers off and on
1010 func (v *View) ToggleRuler() bool {
1011         if settings["ruler"] == false {
1012                 settings["ruler"] = true
1013                 messenger.Message("Enabled ruler")
1014         } else {
1015                 settings["ruler"] = false
1016                 messenger.Message("Disabled ruler")
1017         }
1018         return false
1019 }
1020
1021 // JumpLine jumps to a line and moves the view accordingly.
1022 func (v *View) JumpLine() bool {
1023         // Prompt for line number
1024         linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
1025         if canceled {
1026                 return false
1027         }
1028         lineint, err := strconv.Atoi(linestring)
1029         lineint = lineint - 1 // fix offset
1030         if err != nil {
1031                 messenger.Error(err) // return errors
1032                 return false
1033         }
1034         // Move cursor and view if possible.
1035         if lineint < v.Buf.NumLines && lineint >= 0 {
1036                 v.Cursor.X = 0
1037                 v.Cursor.Y = lineint
1038                 return true
1039         }
1040         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1041         return false
1042 }
1043
1044 // ClearStatus clears the messenger bar
1045 func (v *View) ClearStatus() bool {
1046         messenger.Message("")
1047         return false
1048 }
1049
1050 // ToggleHelp toggles the help screen
1051 func (v *View) ToggleHelp() bool {
1052         if !v.helpOpen {
1053                 v.lastBuffer = v.Buf
1054                 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
1055                 helpBuffer.Name = "Help"
1056                 v.helpOpen = true
1057                 v.OpenBuffer(helpBuffer)
1058         } else {
1059                 v.OpenBuffer(v.lastBuffer)
1060                 v.helpOpen = false
1061         }
1062         return true
1063 }
1064
1065 // ShellMode opens a terminal to run a shell command
1066 func (v *View) ShellMode() bool {
1067         input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
1068         if !canceled {
1069                 // The true here is for openTerm to make the command interactive
1070                 HandleShellCommand(input, true)
1071         }
1072         return false
1073 }
1074
1075 // CommandMode lets the user enter a command
1076 func (v *View) CommandMode() bool {
1077         input, canceled := messenger.Prompt("> ", "Command", NoCompletion)
1078         if !canceled {
1079                 HandleCommand(input)
1080         }
1081         return false
1082 }
1083
1084 // Quit quits the editor
1085 // This behavior needs to be changed and should really only quit the editor if this
1086 // is the last view
1087 // However, since micro only supports one view for now, it doesn't really matter
1088 func (v *View) Quit() bool {
1089         if v.helpOpen {
1090                 return v.ToggleHelp()
1091         }
1092         // Make sure not to quit if there are unsaved changes
1093         if v.CanClose("Quit anyway? (yes, no, save) ") {
1094                 v.CloseBuffer()
1095                 if len(tabs[curTab].views) > 1 {
1096                         var view *View
1097                         if v.splitChild != nil {
1098                                 view = v.splitChild
1099                                 view.splitParent = v.splitParent
1100                         } else if v.splitParent != nil {
1101                                 view = v.splitParent
1102                                 v.splitParent.splitChild = nil
1103                         }
1104                         view.x, view.y = view.splitOrigPos[0], view.splitOrigPos[1]
1105                         view.widthPercent, view.heightPercent = view.splitOrigDimensions[0], view.splitOrigDimensions[1]
1106                         view.Resize(screen.Size())
1107                         if settings["syntax"].(bool) {
1108                                 view.matches = Match(view)
1109                         }
1110                         tabs[curTab].views = tabs[curTab].views[:v.Num+copy(tabs[curTab].views[v.Num:], tabs[curTab].views[v.Num+1:])]
1111                         for i, v := range tabs[curTab].views {
1112                                 v.Num = i
1113                         }
1114                         tabs[curTab].curView = view.Num
1115                 } else if len(tabs) > 1 {
1116                         if len(tabs[v.TabNum].views) == 1 {
1117                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1118                                 for i, t := range tabs {
1119                                         t.SetNum(i)
1120                                 }
1121                                 if curTab >= len(tabs) {
1122                                         curTab--
1123                                 }
1124                                 if curTab == 0 {
1125                                         CurView().Resize(screen.Size())
1126                                         CurView().matches = Match(CurView())
1127                                 }
1128                         }
1129                 } else {
1130                         screen.Fini()
1131                         os.Exit(0)
1132                 }
1133         }
1134         return false
1135 }
1136
1137 // AddTab adds a new tab with an empty buffer
1138 func (v *View) AddTab() bool {
1139         tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1140         tab.SetNum(len(tabs))
1141         tabs = append(tabs, tab)
1142         curTab++
1143         if len(tabs) == 2 {
1144                 for _, t := range tabs {
1145                         for _, v := range t.views {
1146                                 v.Resize(screen.Size())
1147                         }
1148                 }
1149         }
1150         return true
1151 }
1152
1153 // PreviousTab switches to the previous tab in the tab list
1154 func (v *View) PreviousTab() bool {
1155         if curTab > 0 {
1156                 curTab--
1157         } else if curTab == 0 {
1158                 curTab = len(tabs) - 1
1159         }
1160         return false
1161 }
1162
1163 // NextTab switches to the next tab in the tab list
1164 func (v *View) NextTab() bool {
1165         if curTab < len(tabs)-1 {
1166                 curTab++
1167         } else if curTab == len(tabs)-1 {
1168                 curTab = 0
1169         }
1170         return false
1171 }
1172
1173 // None is no action
1174 func None() bool {
1175         return false
1176 }