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