]> git.lizzy.rs Git - micro.git/blob - cmd/micro/bindings.go
14de9ac507ffd81b6a1c2c8684bcf67d85585cc9
[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         "LastTab":             (*View).LastTab,
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                 "Home":           "Start",
388                 "End":            "End",
389                 "PageUp":         "CursorPageUp",
390                 "PageDown":       "CursorPageDown",
391                 "CtrlG":          "ToggleHelp",
392                 "CtrlR":          "ToggleRuler",
393                 "CtrlL":          "JumpLine",
394                 "Delete":         "Delete",
395                 "Esc":            "ClearStatus",
396                 "CtrlB":          "ShellMode",
397                 "CtrlQ":          "Quit",
398                 "CtrlE":          "CommandMode",
399
400                 // Emacs-style keybindings
401                 "Alt-f": "WordRight",
402                 "Alt-b": "WordLeft",
403                 "Alt-a": "StartOfLine",
404                 "Alt-e": "EndOfLine",
405                 "Alt-p": "CursorUp",
406                 "Alt-n": "CursorDown",
407         }
408 }
409
410 // CursorUp moves the cursor up
411 func (v *View) CursorUp() bool {
412         if v.Cursor.HasSelection() {
413                 v.Cursor.Loc = v.Cursor.CurSelection[0]
414                 v.Cursor.ResetSelection()
415         }
416         v.Cursor.Up()
417         return true
418 }
419
420 // CursorDown moves the cursor down
421 func (v *View) CursorDown() bool {
422         if v.Cursor.HasSelection() {
423                 v.Cursor.Loc = v.Cursor.CurSelection[1]
424                 v.Cursor.ResetSelection()
425         }
426         v.Cursor.Down()
427         return true
428 }
429
430 // CursorLeft moves the cursor left
431 func (v *View) CursorLeft() bool {
432         if v.Cursor.HasSelection() {
433                 v.Cursor.Loc = v.Cursor.CurSelection[0]
434                 v.Cursor.ResetSelection()
435         } else {
436                 v.Cursor.Left()
437         }
438         return true
439 }
440
441 // CursorRight moves the cursor right
442 func (v *View) CursorRight() bool {
443         if v.Cursor.HasSelection() {
444                 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
445                 v.Cursor.ResetSelection()
446         } else {
447                 v.Cursor.Right()
448         }
449         return true
450 }
451
452 // WordRight moves the cursor one word to the right
453 func (v *View) WordRight() bool {
454         v.Cursor.WordRight()
455         return true
456 }
457
458 // WordLeft moves the cursor one word to the left
459 func (v *View) WordLeft() bool {
460         v.Cursor.WordLeft()
461         return true
462 }
463
464 // SelectUp selects up one line
465 func (v *View) SelectUp() bool {
466         if !v.Cursor.HasSelection() {
467                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
468         }
469         v.Cursor.Up()
470         v.Cursor.SelectTo(v.Cursor.Loc)
471         return true
472 }
473
474 // SelectDown selects down one line
475 func (v *View) SelectDown() bool {
476         if !v.Cursor.HasSelection() {
477                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
478         }
479         v.Cursor.Down()
480         v.Cursor.SelectTo(v.Cursor.Loc)
481         return true
482 }
483
484 // SelectLeft selects the character to the left of the cursor
485 func (v *View) SelectLeft() bool {
486         loc := v.Cursor.Loc
487         count := v.Buf.End().Move(-1, v.Buf)
488         if loc.GreaterThan(count) {
489                 loc = count
490         }
491         if !v.Cursor.HasSelection() {
492                 v.Cursor.OrigSelection[0] = loc
493         }
494         v.Cursor.Left()
495         v.Cursor.SelectTo(v.Cursor.Loc)
496         return true
497 }
498
499 // SelectRight selects the character to the right of the cursor
500 func (v *View) SelectRight() bool {
501         loc := v.Cursor.Loc
502         count := v.Buf.End().Move(-1, v.Buf)
503         if loc.GreaterThan(count) {
504                 loc = count
505         }
506         if !v.Cursor.HasSelection() {
507                 v.Cursor.OrigSelection[0] = loc
508         }
509         v.Cursor.Right()
510         v.Cursor.SelectTo(v.Cursor.Loc)
511         return true
512 }
513
514 // SelectWordRight selects the word to the right of the cursor
515 func (v *View) SelectWordRight() bool {
516         if !v.Cursor.HasSelection() {
517                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
518         }
519         v.Cursor.WordRight()
520         v.Cursor.SelectTo(v.Cursor.Loc)
521         return true
522 }
523
524 // SelectWordLeft selects the word to the left of the cursor
525 func (v *View) SelectWordLeft() bool {
526         if !v.Cursor.HasSelection() {
527                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
528         }
529         v.Cursor.WordLeft()
530         v.Cursor.SelectTo(v.Cursor.Loc)
531         return true
532 }
533
534 // StartOfLine moves the cursor to the start of the line
535 func (v *View) StartOfLine() bool {
536         v.Cursor.Start()
537         return true
538 }
539
540 // EndOfLine moves the cursor to the end of the line
541 func (v *View) EndOfLine() bool {
542         v.Cursor.End()
543         return true
544 }
545
546 // SelectToStartOfLine selects to the start of the current line
547 func (v *View) SelectToStartOfLine() bool {
548         if !v.Cursor.HasSelection() {
549                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
550         }
551         v.Cursor.Start()
552         v.Cursor.SelectTo(v.Cursor.Loc)
553         return true
554 }
555
556 // SelectToEndOfLine selects to the end of the current line
557 func (v *View) SelectToEndOfLine() bool {
558         if !v.Cursor.HasSelection() {
559                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
560         }
561         v.Cursor.End()
562         v.Cursor.SelectTo(v.Cursor.Loc)
563         return true
564 }
565
566 // CursorStart moves the cursor to the start of the buffer
567 func (v *View) CursorStart() bool {
568         v.Cursor.X = 0
569         v.Cursor.Y = 0
570         return true
571 }
572
573 // CursorEnd moves the cursor to the end of the buffer
574 func (v *View) CursorEnd() bool {
575         v.Cursor.Loc = v.Buf.End()
576         return true
577 }
578
579 // SelectToStart selects the text from the cursor to the start of the buffer
580 func (v *View) SelectToStart() bool {
581         if !v.Cursor.HasSelection() {
582                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
583         }
584         v.CursorStart()
585         v.Cursor.SelectTo(v.Buf.Start())
586         return true
587 }
588
589 // SelectToEnd selects the text from the cursor to the end of the buffer
590 func (v *View) SelectToEnd() bool {
591         if !v.Cursor.HasSelection() {
592                 v.Cursor.OrigSelection[0] = v.Cursor.Loc
593         }
594         v.CursorEnd()
595         v.Cursor.SelectTo(v.Buf.End())
596         return true
597 }
598
599 // InsertSpace inserts a space
600 func (v *View) InsertSpace() bool {
601         if v.Cursor.HasSelection() {
602                 v.Cursor.DeleteSelection()
603                 v.Cursor.ResetSelection()
604         }
605         v.Buf.Insert(v.Cursor.Loc, " ")
606         v.Cursor.Right()
607         return true
608 }
609
610 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
611 func (v *View) InsertEnter() bool {
612         // Insert a newline
613         if v.Cursor.HasSelection() {
614                 v.Cursor.DeleteSelection()
615                 v.Cursor.ResetSelection()
616         }
617
618         v.Buf.Insert(v.Cursor.Loc, "\n")
619         ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
620         v.Cursor.Right()
621
622         if settings["autoindent"].(bool) {
623                 v.Buf.Insert(v.Cursor.Loc, ws)
624                 for i := 0; i < len(ws); i++ {
625                         v.Cursor.Right()
626                 }
627         }
628         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
629         return true
630 }
631
632 // Backspace deletes the previous character
633 func (v *View) Backspace() bool {
634         // Delete a character
635         if v.Cursor.HasSelection() {
636                 v.Cursor.DeleteSelection()
637                 v.Cursor.ResetSelection()
638         } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
639                 // We have to do something a bit hacky here because we want to
640                 // delete the line by first moving left and then deleting backwards
641                 // but the undo redo would place the cursor in the wrong place
642                 // So instead we move left, save the position, move back, delete
643                 // and restore the position
644
645                 // If the user is using spaces instead of tabs and they are deleting
646                 // whitespace at the start of the line, we should delete as if its a
647                 // tab (tabSize number of spaces)
648                 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
649                 tabSize := int(settings["tabsize"].(float64))
650                 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
651                         loc := v.Cursor.Loc
652                         v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
653                         cx, cy := v.Cursor.X, v.Cursor.Y
654                         v.Cursor.Loc = loc
655                         v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
656                         v.Cursor.X, v.Cursor.Y = cx, cy
657                 } else {
658                         v.Cursor.Left()
659                         cx, cy := v.Cursor.X, v.Cursor.Y
660                         v.Cursor.Right()
661                         loc := v.Cursor.Loc
662                         v.Buf.Remove(loc.Move(-1, v.Buf), loc)
663                         v.Cursor.X, v.Cursor.Y = cx, cy
664                 }
665         }
666         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
667         return true
668 }
669
670 // DeleteWordRight deletes the word to the right of the cursor
671 func (v *View) DeleteWordRight() bool {
672         v.SelectWordRight()
673         if v.Cursor.HasSelection() {
674                 v.Cursor.DeleteSelection()
675                 v.Cursor.ResetSelection()
676         }
677         return true
678 }
679
680 // DeleteWordLeft deletes the word to the left of the cursor
681 func (v *View) DeleteWordLeft() bool {
682         v.SelectWordLeft()
683         if v.Cursor.HasSelection() {
684                 v.Cursor.DeleteSelection()
685                 v.Cursor.ResetSelection()
686         }
687         return true
688 }
689
690 // Delete deletes the next character
691 func (v *View) Delete() bool {
692         if v.Cursor.HasSelection() {
693                 v.Cursor.DeleteSelection()
694                 v.Cursor.ResetSelection()
695         } else {
696                 loc := v.Cursor.Loc
697                 if loc.LessThan(v.Buf.End()) {
698                         v.Buf.Remove(loc, loc.Move(1, v.Buf))
699                 }
700         }
701         return true
702 }
703
704 // InsertTab inserts a tab or spaces
705 func (v *View) InsertTab() bool {
706         // Insert a tab
707         if v.Cursor.HasSelection() {
708                 v.Cursor.DeleteSelection()
709                 v.Cursor.ResetSelection()
710         }
711         if settings["tabstospaces"].(bool) {
712                 tabSize := int(settings["tabsize"].(float64))
713                 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
714                 for i := 0; i < tabSize; i++ {
715                         v.Cursor.Right()
716                 }
717         } else {
718                 v.Buf.Insert(v.Cursor.Loc, "\t")
719                 v.Cursor.Right()
720         }
721         return true
722 }
723
724 // Save the buffer to disk
725 func (v *View) Save() bool {
726         if v.helpOpen {
727                 // We can't save the help text
728                 return false
729         }
730         // If this is an empty buffer, ask for a filename
731         if v.Buf.Path == "" {
732                 filename, canceled := messenger.Prompt("Filename: ", "Save")
733                 if !canceled {
734                         v.Buf.Path = filename
735                         v.Buf.Name = filename
736                 } else {
737                         return false
738                 }
739         }
740         err := v.Buf.Save()
741         if err != nil {
742                 if strings.HasSuffix(err.Error(), "permission denied") {
743                         choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
744                         if choice {
745                                 err = v.Buf.SaveWithSudo()
746                                 if err != nil {
747                                         messenger.Error(err.Error())
748                                         return false
749                                 }
750                         }
751                         messenger.Reset()
752                         messenger.Clear()
753                 } else {
754                         messenger.Error(err.Error())
755                 }
756         } else {
757                 messenger.Message("Saved " + v.Buf.Path)
758         }
759         return false
760 }
761
762 // Find opens a prompt and searches forward for the input
763 func (v *View) Find() bool {
764         if v.Cursor.HasSelection() {
765                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
766         } else {
767                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
768         }
769         BeginSearch()
770         return true
771 }
772
773 // FindNext searches forwards for the last used search term
774 func (v *View) FindNext() bool {
775         if v.Cursor.HasSelection() {
776                 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
777         } else {
778                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
779         }
780         messenger.Message("Finding: " + lastSearch)
781         Search(lastSearch, v, true)
782         return true
783 }
784
785 // FindPrevious searches backwards for the last used search term
786 func (v *View) FindPrevious() bool {
787         if v.Cursor.HasSelection() {
788                 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
789         } else {
790                 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
791         }
792         messenger.Message("Finding: " + lastSearch)
793         Search(lastSearch, v, false)
794         return true
795 }
796
797 // Undo undoes the last action
798 func (v *View) Undo() bool {
799         v.Buf.Undo()
800         messenger.Message("Undid action")
801         return true
802 }
803
804 // Redo redoes the last action
805 func (v *View) Redo() bool {
806         v.Buf.Redo()
807         messenger.Message("Redid action")
808         return true
809 }
810
811 // Copy the selection to the system clipboard
812 func (v *View) Copy() bool {
813         if v.Cursor.HasSelection() {
814                 clipboard.WriteAll(v.Cursor.GetSelection())
815                 v.freshClip = true
816                 messenger.Message("Copied selection")
817         }
818         return true
819 }
820
821 // CutLine cuts the current line to the clipboard
822 func (v *View) CutLine() bool {
823         v.Cursor.SelectLine()
824         if !v.Cursor.HasSelection() {
825                 return false
826         }
827         if v.freshClip == true {
828                 if v.Cursor.HasSelection() {
829                         if clip, err := clipboard.ReadAll(); err != nil {
830                                 messenger.Error(err)
831                         } else {
832                                 clipboard.WriteAll(clip + v.Cursor.GetSelection())
833                         }
834                 }
835         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
836                 v.Copy()
837         }
838         v.freshClip = true
839         v.lastCutTime = time.Now()
840         v.Cursor.DeleteSelection()
841         v.Cursor.ResetSelection()
842         messenger.Message("Cut line")
843         return true
844 }
845
846 // Cut the selection to the system clipboard
847 func (v *View) Cut() bool {
848         if v.Cursor.HasSelection() {
849                 clipboard.WriteAll(v.Cursor.GetSelection())
850                 v.Cursor.DeleteSelection()
851                 v.Cursor.ResetSelection()
852                 v.freshClip = true
853                 messenger.Message("Cut selection")
854         }
855         return true
856 }
857
858 // DuplicateLine duplicates the current line
859 func (v *View) DuplicateLine() bool {
860         v.Cursor.End()
861         v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
862         v.Cursor.Right()
863         messenger.Message("Duplicated line")
864         return true
865 }
866
867 // DeleteLine deletes the current line
868 func (v *View) DeleteLine() bool {
869         v.Cursor.SelectLine()
870         if !v.Cursor.HasSelection() {
871                 return false
872         }
873         v.Cursor.DeleteSelection()
874         v.Cursor.ResetSelection()
875         messenger.Message("Deleted line")
876         return true
877 }
878
879 // Paste whatever is in the system clipboard into the buffer
880 // Delete and paste if the user has a selection
881 func (v *View) Paste() bool {
882         if v.Cursor.HasSelection() {
883                 v.Cursor.DeleteSelection()
884                 v.Cursor.ResetSelection()
885         }
886         clip, _ := clipboard.ReadAll()
887         v.Buf.Insert(v.Cursor.Loc, clip)
888         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
889         v.freshClip = false
890         messenger.Message("Pasted clipboard")
891         return true
892 }
893
894 // SelectAll selects the entire buffer
895 func (v *View) SelectAll() bool {
896         v.Cursor.CurSelection[0] = v.Buf.Start()
897         v.Cursor.CurSelection[1] = v.Buf.End()
898         // Put the cursor at the beginning
899         v.Cursor.X = 0
900         v.Cursor.Y = 0
901         return true
902 }
903
904 // OpenFile opens a new file in the buffer
905 func (v *View) OpenFile() bool {
906         if v.CanClose("Continue? (yes, no, save) ") {
907                 filename, canceled := messenger.Prompt("File to open: ", "Open")
908                 if canceled {
909                         return false
910                 }
911                 home, _ := homedir.Dir()
912                 filename = strings.Replace(filename, "~", home, 1)
913                 file, err := ioutil.ReadFile(filename)
914
915                 if err != nil {
916                         messenger.Error(err.Error())
917                         return false
918                 }
919                 buf := NewBuffer(file, filename)
920                 v.OpenBuffer(buf)
921                 return true
922         }
923         return false
924 }
925
926 // Start moves the viewport to the start of the buffer
927 func (v *View) Start() bool {
928         v.Topline = 0
929         return false
930 }
931
932 // End moves the viewport to the end of the buffer
933 func (v *View) End() bool {
934         if v.height > v.Buf.NumLines {
935                 v.Topline = 0
936         } else {
937                 v.Topline = v.Buf.NumLines - v.height
938         }
939         return false
940 }
941
942 // PageUp scrolls the view up a page
943 func (v *View) PageUp() bool {
944         if v.Topline > v.height {
945                 v.ScrollUp(v.height)
946         } else {
947                 v.Topline = 0
948         }
949         return false
950 }
951
952 // PageDown scrolls the view down a page
953 func (v *View) PageDown() bool {
954         if v.Buf.NumLines-(v.Topline+v.height) > v.height {
955                 v.ScrollDown(v.height)
956         } else if v.Buf.NumLines >= v.height {
957                 v.Topline = v.Buf.NumLines - v.height
958         }
959         return false
960 }
961
962 // CursorPageUp places the cursor a page up
963 func (v *View) CursorPageUp() bool {
964         if v.Cursor.HasSelection() {
965                 v.Cursor.Loc = v.Cursor.CurSelection[0]
966                 v.Cursor.ResetSelection()
967         }
968         v.Cursor.UpN(v.height)
969         return true
970 }
971
972 // CursorPageDown places the cursor a page up
973 func (v *View) CursorPageDown() bool {
974         if v.Cursor.HasSelection() {
975                 v.Cursor.Loc = v.Cursor.CurSelection[1]
976                 v.Cursor.ResetSelection()
977         }
978         v.Cursor.DownN(v.height)
979         return true
980 }
981
982 // HalfPageUp scrolls the view up half a page
983 func (v *View) HalfPageUp() bool {
984         if v.Topline > v.height/2 {
985                 v.ScrollUp(v.height / 2)
986         } else {
987                 v.Topline = 0
988         }
989         return false
990 }
991
992 // HalfPageDown scrolls the view down half a page
993 func (v *View) HalfPageDown() bool {
994         if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
995                 v.ScrollDown(v.height / 2)
996         } else {
997                 if v.Buf.NumLines >= v.height {
998                         v.Topline = v.Buf.NumLines - v.height
999                 }
1000         }
1001         return false
1002 }
1003
1004 // ToggleRuler turns line numbers off and on
1005 func (v *View) ToggleRuler() bool {
1006         if settings["ruler"] == false {
1007                 settings["ruler"] = true
1008                 messenger.Message("Enabled ruler")
1009         } else {
1010                 settings["ruler"] = false
1011                 messenger.Message("Disabled ruler")
1012         }
1013         return false
1014 }
1015
1016 // JumpLine jumps to a line and moves the view accordingly.
1017 func (v *View) JumpLine() bool {
1018         // Prompt for line number
1019         linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber")
1020         if canceled {
1021                 return false
1022         }
1023         lineint, err := strconv.Atoi(linestring)
1024         lineint = lineint - 1 // fix offset
1025         if err != nil {
1026                 messenger.Error(err) // return errors
1027                 return false
1028         }
1029         // Move cursor and view if possible.
1030         if lineint < v.Buf.NumLines {
1031                 v.Cursor.X = 0
1032                 v.Cursor.Y = lineint
1033                 return true
1034         }
1035         messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1036         return false
1037 }
1038
1039 // ClearStatus clears the messenger bar
1040 func (v *View) ClearStatus() bool {
1041         messenger.Message("")
1042         return false
1043 }
1044
1045 // ToggleHelp toggles the help screen
1046 func (v *View) ToggleHelp() bool {
1047         if !v.helpOpen {
1048                 v.lastBuffer = v.Buf
1049                 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
1050                 helpBuffer.Name = "Help"
1051                 v.helpOpen = true
1052                 v.OpenBuffer(helpBuffer)
1053         } else {
1054                 v.OpenBuffer(v.lastBuffer)
1055                 v.helpOpen = false
1056         }
1057         return true
1058 }
1059
1060 // ShellMode opens a terminal to run a shell command
1061 func (v *View) ShellMode() bool {
1062         input, canceled := messenger.Prompt("$ ", "Shell")
1063         if !canceled {
1064                 // The true here is for openTerm to make the command interactive
1065                 HandleShellCommand(input, true)
1066         }
1067         return false
1068 }
1069
1070 // CommandMode lets the user enter a command
1071 func (v *View) CommandMode() bool {
1072         input, canceled := messenger.Prompt("> ", "Command")
1073         if !canceled {
1074                 HandleCommand(input)
1075         }
1076         return false
1077 }
1078
1079 // Quit quits the editor
1080 // This behavior needs to be changed and should really only quit the editor if this
1081 // is the last view
1082 // However, since micro only supports one view for now, it doesn't really matter
1083 func (v *View) Quit() bool {
1084         if v.helpOpen {
1085                 return v.ToggleHelp()
1086         }
1087         // Make sure not to quit if there are unsaved changes
1088         if v.CanClose("Quit anyway? (yes, no, save) ") {
1089                 v.CloseBuffer()
1090                 if len(tabs) > 1 {
1091                         if len(tabs[v.TabNum].views) == 1 {
1092                                 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1093                                 for i, t := range tabs {
1094                                         t.SetNum(i)
1095                                 }
1096                                 if curTab >= len(tabs) {
1097                                         curTab--
1098                                 }
1099                                 if curTab == 0 {
1100                                         tab := tabs[curTab]
1101                                         tab.views[tab.curView].Resize(screen.Size())
1102                                 }
1103                         }
1104                 } else {
1105                         screen.Fini()
1106                         os.Exit(0)
1107                 }
1108         }
1109         return false
1110 }
1111
1112 func (v *View) AddTab() bool {
1113         tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1114         tab.SetNum(len(tabs))
1115         tabs = append(tabs, tab)
1116         curTab++
1117         return true
1118 }
1119
1120 func (v *View) LastTab() bool {
1121         if curTab > 0 {
1122                 curTab--
1123         }
1124         return true
1125 }
1126
1127 func (v *View) NextTab() bool {
1128         if curTab < len(tabs)-1 {
1129                 curTab++
1130         }
1131         return true
1132 }
1133
1134 // None is no action
1135 func None() bool {
1136         return false
1137 }