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