]> git.lizzy.rs Git - micro.git/blob - cmd/micro/bindings.go
Merge
[micro.git] / cmd / micro / bindings.go
1 package main
2
3 import (
4         "encoding/json"
5         "errors"
6         "io/ioutil"
7         "os"
8         "os/exec"
9         "strings"
10         "time"
11
12         "github.com/gdamore/tcell"
13         "github.com/mitchellh/go-homedir"
14         "github.com/zyedidia/clipboard"
15 )
16
17 var bindings map[tcell.Key]func(*View) bool
18
19 // InitBindings initializes the keybindings for micro
20 func InitBindings() {
21         bindings = make(map[tcell.Key]func(*View) bool)
22
23         actions := map[string]func(*View) bool{
24                 "CursorUp":     CursorUp,
25                 "CursorDown":   CursorDown,
26                 "CursorLeft":   CursorLeft,
27                 "CursorRight":  CursorRight,
28                 "InsertEnter":  InsertEnter,
29                 "InsertSpace":  InsertSpace,
30                 "Backspace":    Backspace,
31                 "Delete":       Delete,
32                 "InsertTab":    InsertTab,
33                 "Save":         Save,
34                 "Find":         Find,
35                 "FindNext":     FindNext,
36                 "FindPrevious": FindPrevious,
37                 "Undo":         Undo,
38                 "Redo":         Redo,
39                 "Copy":         Copy,
40                 "Cut":          Cut,
41                 "CutLine":      CutLine,
42                 "Paste":        Paste,
43                 "SelectAll":    SelectAll,
44                 "OpenFile":     OpenFile,
45                 "Beginning":    Beginning,
46                 "End":          End,
47                 "PageUp":       PageUp,
48                 "PageDown":     PageDown,
49                 "HalfPageUp":   HalfPageUp,
50                 "HalfPageDown": HalfPageDown,
51                 "StartOfLine":  StartOfLine,
52                 "EndOfLine":    EndOfLine,
53                 "ToggleRuler":  ToggleRuler,
54         }
55
56         keys := map[string]tcell.Key{
57                 "Up":             tcell.KeyUp,
58                 "Down":           tcell.KeyDown,
59                 "Right":          tcell.KeyRight,
60                 "Left":           tcell.KeyLeft,
61                 "UpLeft":         tcell.KeyUpLeft,
62                 "UpRight":        tcell.KeyUpRight,
63                 "DownLeft":       tcell.KeyDownLeft,
64                 "DownRight":      tcell.KeyDownRight,
65                 "Center":         tcell.KeyCenter,
66                 "PgUp":           tcell.KeyPgUp,
67                 "PgDn":           tcell.KeyPgDn,
68                 "Home":           tcell.KeyHome,
69                 "End":            tcell.KeyEnd,
70                 "Insert":         tcell.KeyInsert,
71                 "Delete":         tcell.KeyDelete,
72                 "Help":           tcell.KeyHelp,
73                 "Exit":           tcell.KeyExit,
74                 "Clear":          tcell.KeyClear,
75                 "Cancel":         tcell.KeyCancel,
76                 "Print":          tcell.KeyPrint,
77                 "Pause":          tcell.KeyPause,
78                 "Backtab":        tcell.KeyBacktab,
79                 "F1":             tcell.KeyF1,
80                 "F2":             tcell.KeyF2,
81                 "F3":             tcell.KeyF3,
82                 "F4":             tcell.KeyF4,
83                 "F5":             tcell.KeyF5,
84                 "F6":             tcell.KeyF6,
85                 "F7":             tcell.KeyF7,
86                 "F8":             tcell.KeyF8,
87                 "F9":             tcell.KeyF9,
88                 "F10":            tcell.KeyF10,
89                 "F11":            tcell.KeyF11,
90                 "F12":            tcell.KeyF12,
91                 "F13":            tcell.KeyF13,
92                 "F14":            tcell.KeyF14,
93                 "F15":            tcell.KeyF15,
94                 "F16":            tcell.KeyF16,
95                 "F17":            tcell.KeyF17,
96                 "F18":            tcell.KeyF18,
97                 "F19":            tcell.KeyF19,
98                 "F20":            tcell.KeyF20,
99                 "F21":            tcell.KeyF21,
100                 "F22":            tcell.KeyF22,
101                 "F23":            tcell.KeyF23,
102                 "F24":            tcell.KeyF24,
103                 "F25":            tcell.KeyF25,
104                 "F26":            tcell.KeyF26,
105                 "F27":            tcell.KeyF27,
106                 "F28":            tcell.KeyF28,
107                 "F29":            tcell.KeyF29,
108                 "F30":            tcell.KeyF30,
109                 "F31":            tcell.KeyF31,
110                 "F32":            tcell.KeyF32,
111                 "F33":            tcell.KeyF33,
112                 "F34":            tcell.KeyF34,
113                 "F35":            tcell.KeyF35,
114                 "F36":            tcell.KeyF36,
115                 "F37":            tcell.KeyF37,
116                 "F38":            tcell.KeyF38,
117                 "F39":            tcell.KeyF39,
118                 "F40":            tcell.KeyF40,
119                 "F41":            tcell.KeyF41,
120                 "F42":            tcell.KeyF42,
121                 "F43":            tcell.KeyF43,
122                 "F44":            tcell.KeyF44,
123                 "F45":            tcell.KeyF45,
124                 "F46":            tcell.KeyF46,
125                 "F47":            tcell.KeyF47,
126                 "F48":            tcell.KeyF48,
127                 "F49":            tcell.KeyF49,
128                 "F50":            tcell.KeyF50,
129                 "F51":            tcell.KeyF51,
130                 "F52":            tcell.KeyF52,
131                 "F53":            tcell.KeyF53,
132                 "F54":            tcell.KeyF54,
133                 "F55":            tcell.KeyF55,
134                 "F56":            tcell.KeyF56,
135                 "F57":            tcell.KeyF57,
136                 "F58":            tcell.KeyF58,
137                 "F59":            tcell.KeyF59,
138                 "F60":            tcell.KeyF60,
139                 "F61":            tcell.KeyF61,
140                 "F62":            tcell.KeyF62,
141                 "F63":            tcell.KeyF63,
142                 "F64":            tcell.KeyF64,
143                 "CtrlSpace":      tcell.KeyCtrlSpace,
144                 "CtrlA":          tcell.KeyCtrlA,
145                 "CtrlB":          tcell.KeyCtrlB,
146                 "CtrlC":          tcell.KeyCtrlC,
147                 "CtrlD":          tcell.KeyCtrlD,
148                 "CtrlE":          tcell.KeyCtrlE,
149                 "CtrlF":          tcell.KeyCtrlF,
150                 "CtrlG":          tcell.KeyCtrlG,
151                 "CtrlH":          tcell.KeyCtrlH,
152                 "CtrlI":          tcell.KeyCtrlI,
153                 "CtrlJ":          tcell.KeyCtrlJ,
154                 "CtrlK":          tcell.KeyCtrlK,
155                 "CtrlL":          tcell.KeyCtrlL,
156                 "CtrlM":          tcell.KeyCtrlM,
157                 "CtrlN":          tcell.KeyCtrlN,
158                 "CtrlO":          tcell.KeyCtrlO,
159                 "CtrlP":          tcell.KeyCtrlP,
160                 "CtrlQ":          tcell.KeyCtrlQ,
161                 "CtrlR":          tcell.KeyCtrlR,
162                 "CtrlS":          tcell.KeyCtrlS,
163                 "CtrlT":          tcell.KeyCtrlT,
164                 "CtrlU":          tcell.KeyCtrlU,
165                 "CtrlV":          tcell.KeyCtrlV,
166                 "CtrlW":          tcell.KeyCtrlW,
167                 "CtrlX":          tcell.KeyCtrlX,
168                 "CtrlY":          tcell.KeyCtrlY,
169                 "CtrlZ":          tcell.KeyCtrlZ,
170                 "CtrlLeftSq":     tcell.KeyCtrlLeftSq,
171                 "CtrlBackslash":  tcell.KeyCtrlBackslash,
172                 "CtrlRightSq":    tcell.KeyCtrlRightSq,
173                 "CtrlCarat":      tcell.KeyCtrlCarat,
174                 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
175                 "Backspace":      tcell.KeyBackspace,
176                 "Tab":            tcell.KeyTab,
177                 "Esc":            tcell.KeyEsc,
178                 "Escape":         tcell.KeyEscape,
179                 "Enter":          tcell.KeyEnter,
180                 "Space":          tcell.KeySpace,
181                 "Backspace2":     tcell.KeyBackspace2,
182         }
183
184         var parsed map[string]string
185         defaults := DefaultBindings()
186
187         filename := configDir + "/bindings.json"
188         if _, e := os.Stat(filename); e == nil {
189                 input, err := ioutil.ReadFile(filename)
190                 if err != nil {
191                         TermMessage("Error reading bindings.json file: " + err.Error())
192                         return
193                 }
194
195                 err = json.Unmarshal(input, &parsed)
196                 if err != nil {
197                         TermMessage("Error reading bindings.json:", err.Error())
198                 }
199         }
200
201         for k, v := range defaults {
202                 bindings[keys[k]] = actions[v]
203         }
204         for k, v := range parsed {
205                 bindings[keys[k]] = actions[v]
206         }
207 }
208
209 // DefaultBindings returns a map containing micro's default keybindings
210 func DefaultBindings() map[string]string {
211         return map[string]string{
212                 "Up":         "CursorUp",
213                 "Down":       "CursorDown",
214                 "Right":      "CursorRight",
215                 "Left":       "CursorLeft",
216                 "Enter":      "InsertEnter",
217                 "Space":      "InsertSpace",
218                 "Backspace":  "Backspace",
219                 "Backspace2": "Backspace",
220                 "Tab":        "InsertTab",
221                 "CtrlO":      "OpenFile",
222                 "CtrlS":      "Save",
223                 "CtrlF":      "Find",
224                 "CtrlN":      "FindNext",
225                 "CtrlP":      "FindPrevious",
226                 "CtrlZ":      "Undo",
227                 "CtrlY":      "Redo",
228                 "CtrlC":      "Copy",
229                 "CtrlX":      "Cut",
230                 "CtrlK":      "CutLine",
231                 "CtrlV":      "Paste",
232                 "CtrlA":      "SelectAll",
233                 "Home":       "Beginning",
234                 "End":        "End",
235                 "PageUp":     "PageUp",
236                 "PageDown":   "PageDown",
237                 "CtrlU":      "HalfPageUp",
238                 "CtrlD":      "HalfPageDown",
239                 "CtrlR":      "ToggleRuler",
240                 "Delete":     "Delete",
241         }
242 }
243
244 // CursorUp moves the cursor up
245 func CursorUp(v *View) bool {
246         v.cursor.ResetSelection()
247         v.cursor.Up()
248         return true
249 }
250
251 // CursorDown moves the cursor down
252 func CursorDown(v *View) bool {
253         v.cursor.ResetSelection()
254         v.cursor.Down()
255         return true
256 }
257
258 // CursorLeft moves the cursor left
259 func CursorLeft(v *View) bool {
260         v.cursor.ResetSelection()
261         v.cursor.Left()
262         return true
263 }
264
265 // CursorRight moves the cursor right
266 func CursorRight(v *View) bool {
267         v.cursor.ResetSelection()
268         v.cursor.Right()
269         return true
270 }
271
272 // InsertSpace inserts a space
273 func InsertSpace(v *View) bool {
274         // Insert a space
275         if v.cursor.HasSelection() {
276                 v.cursor.DeleteSelection()
277                 v.cursor.ResetSelection()
278         }
279         v.eh.Insert(v.cursor.Loc(), " ")
280         v.cursor.Right()
281         return true
282 }
283
284 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
285 func InsertEnter(v *View) bool {
286         // Insert a newline
287         if v.cursor.HasSelection() {
288                 v.cursor.DeleteSelection()
289                 v.cursor.ResetSelection()
290         }
291
292         v.eh.Insert(v.cursor.Loc(), "\n")
293         ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
294         v.cursor.Right()
295
296         if settings.AutoIndent {
297                 v.eh.Insert(v.cursor.Loc(), ws)
298                 for i := 0; i < len(ws); i++ {
299                         v.cursor.Right()
300                 }
301         }
302         v.cursor.lastVisualX = v.cursor.GetVisualX()
303         return true
304 }
305
306 // Backspace deletes the previous character
307 func Backspace(v *View) bool {
308         // Delete a character
309         if v.cursor.HasSelection() {
310                 v.cursor.DeleteSelection()
311                 v.cursor.ResetSelection()
312         } else if v.cursor.Loc() > 0 {
313                 // We have to do something a bit hacky here because we want to
314                 // delete the line by first moving left and then deleting backwards
315                 // but the undo redo would place the cursor in the wrong place
316                 // So instead we move left, save the position, move back, delete
317                 // and restore the position
318
319                 // If the user is using spaces instead of tabs and they are deleting
320                 // whitespace at the start of the line, we should delete as if its a
321                 // tab (tabSize number of spaces)
322                 lineStart := v.buf.lines[v.cursor.y][:v.cursor.x]
323                 if settings.TabsToSpaces && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%settings.TabSize == 0 {
324                         loc := v.cursor.Loc()
325                         v.cursor.SetLoc(loc - settings.TabSize)
326                         cx, cy := v.cursor.x, v.cursor.y
327                         v.cursor.SetLoc(loc)
328                         v.eh.Remove(loc-settings.TabSize, loc)
329                         v.cursor.x, v.cursor.y = cx, cy
330                 } else {
331                         v.cursor.Left()
332                         cx, cy := v.cursor.x, v.cursor.y
333                         v.cursor.Right()
334                         loc := v.cursor.Loc()
335                         v.eh.Remove(loc-1, loc)
336                         v.cursor.x, v.cursor.y = cx, cy
337                 }
338         }
339         v.cursor.lastVisualX = v.cursor.GetVisualX()
340         return true
341 }
342
343 // Delete deletes the next character
344 func Delete(v *View) bool {
345         if v.cursor.HasSelection() {
346                 v.cursor.DeleteSelection()
347                 v.cursor.ResetSelection()
348         } else {
349                 loc := v.cursor.Loc()
350                 if loc < len(v.buf.text) {
351                         v.eh.Remove(loc, loc+1)
352                 }
353         }
354         return true
355 }
356
357 // InsertTab inserts a tab or spaces
358 func InsertTab(v *View) bool {
359         // Insert a tab
360         if v.cursor.HasSelection() {
361                 v.cursor.DeleteSelection()
362                 v.cursor.ResetSelection()
363         }
364         if settings.TabsToSpaces {
365                 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
366                 for i := 0; i < settings.TabSize; i++ {
367                         v.cursor.Right()
368                 }
369         } else {
370                 v.eh.Insert(v.cursor.Loc(), "\t")
371                 v.cursor.Right()
372         }
373         return true
374 }
375
376 // Save the buffer to disk
377 func Save(v *View) bool {
378         // If this is an empty buffer, ask for a filename
379         if v.buf.path == "" {
380                 filename, canceled := messenger.Prompt("Filename: ")
381                 if !canceled {
382                         v.buf.path = filename
383                         v.buf.name = filename
384                 } else {
385                         return true
386                 }
387         }
388         err := v.buf.Save()
389         if err != nil {
390                 messenger.Error(err.Error())
391         } else {
392                 messenger.Message("Saved " + v.buf.path)
393                 switch v.buf.filetype {
394                 case "Go":
395                         GoSave(v)
396                 }
397         }
398         return true
399 }
400
401 // GoSave saves the current file (must be a go file) and runs goimports or gofmt
402 // depending on the user's configuration
403 func GoSave(v *View) {
404         if settings.GoImports == true {
405                 messenger.Message("Running goimports...")
406                 err := goimports(v.buf.path)
407                 if err != nil {
408                         messenger.Error(err)
409                 } else {
410                         messenger.Message("Saved " + v.buf.path)
411                 }
412                 v.reOpen()
413         } else if settings.GoFmt == true {
414                 messenger.Message("Running gofmt...")
415                 err := gofmt(v.buf.path)
416                 if err != nil {
417                         messenger.Error(err)
418                 } else {
419                         messenger.Message("Saved " + v.buf.path)
420                 }
421                 v.reOpen()
422                 return
423         }
424
425         return
426 }
427
428 // Find opens a prompt and searches forward for the input
429 func Find(v *View) bool {
430         if v.cursor.HasSelection() {
431                 searchStart = v.cursor.curSelection[1]
432         } else {
433                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
434         }
435         BeginSearch()
436         return true
437 }
438
439 // FindNext searches forwards for the last used search term
440 func FindNext(v *View) bool {
441         if v.cursor.HasSelection() {
442                 searchStart = v.cursor.curSelection[1]
443         } else {
444                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
445         }
446         messenger.Message("Find: " + lastSearch)
447         Search(lastSearch, v, true)
448         return true
449 }
450
451 // FindPrevious searches backwards for the last used search term
452 func FindPrevious(v *View) bool {
453         if v.cursor.HasSelection() {
454                 searchStart = v.cursor.curSelection[0]
455         } else {
456                 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
457         }
458         messenger.Message("Find: " + lastSearch)
459         Search(lastSearch, v, false)
460         return true
461 }
462
463 // Undo undoes the last action
464 func Undo(v *View) bool {
465         v.eh.Undo()
466         return true
467 }
468
469 // Redo redoes the last action
470 func Redo(v *View) bool {
471         v.eh.Redo()
472         return true
473 }
474
475 // Copy the selection to the system clipboard
476 func Copy(v *View) bool {
477         if v.cursor.HasSelection() {
478                 clipboard.WriteAll(v.cursor.GetSelection())
479                 v.freshClip = true
480         }
481         return true
482 }
483
484 // CutLine cuts the current line to the clipboard
485 func CutLine(v *View) bool {
486         v.cursor.SelectLine()
487         if v.freshClip == true {
488
489                 if v.cursor.HasSelection() {
490                         if clip, err := clipboard.ReadAll(); err != nil {
491                                 messenger.Error(err)
492                         } else {
493                                 clipboard.WriteAll(clip + v.cursor.GetSelection())
494                         }
495                 }
496         } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
497                 Copy(v)
498         }
499         v.freshClip = true
500         v.lastCutTime = time.Now()
501         v.cursor.DeleteSelection()
502         v.cursor.ResetSelection()
503         return true
504 }
505
506 // Cut the selection to the system clipboard
507 func Cut(v *View) bool {
508         if v.cursor.HasSelection() {
509                 clipboard.WriteAll(v.cursor.GetSelection())
510                 v.cursor.DeleteSelection()
511                 v.cursor.ResetSelection()
512                 v.freshClip = true
513         }
514         return true
515 }
516
517 // Paste whatever is in the system clipboard into the buffer
518 // Delete and paste if the user has a selection
519 func Paste(v *View) bool {
520         if v.cursor.HasSelection() {
521                 v.cursor.DeleteSelection()
522                 v.cursor.ResetSelection()
523         }
524         clip, _ := clipboard.ReadAll()
525         v.eh.Insert(v.cursor.Loc(), clip)
526         v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
527         v.freshClip = false
528         return true
529 }
530
531 // SelectAll selects the entire buffer
532 func SelectAll(v *View) bool {
533         v.cursor.curSelection[1] = 0
534         v.cursor.curSelection[0] = v.buf.Len()
535         // Put the cursor at the beginning
536         v.cursor.x = 0
537         v.cursor.y = 0
538         return true
539 }
540
541 // OpenFile opens a new file in the buffer
542 func OpenFile(v *View) bool {
543         if v.CanClose("Continue? (yes, no, save) ") {
544                 filename, canceled := messenger.Prompt("File to open: ")
545                 if canceled {
546                         return true
547                 }
548                 home, _ := homedir.Dir()
549                 filename = strings.Replace(filename, "~", home, 1)
550                 file, err := ioutil.ReadFile(filename)
551
552                 if err != nil {
553                         messenger.Error(err.Error())
554                         return true
555                 }
556                 buf := NewBuffer(string(file), filename)
557                 v.OpenBuffer(buf)
558         }
559         return true
560 }
561
562 // Beginning moves the viewport to the start of the buffer
563 func Beginning(v *View) bool {
564         v.topline = 0
565         return false
566 }
567
568 // End moves the viewport to the end of the buffer
569 func End(v *View) bool {
570         if v.height > len(v.buf.lines) {
571                 v.topline = 0
572         } else {
573                 v.topline = len(v.buf.lines) - v.height
574         }
575         return false
576 }
577
578 // PageUp scrolls the view up a page
579 func PageUp(v *View) bool {
580         if v.topline > v.height {
581                 v.ScrollUp(v.height)
582         } else {
583                 v.topline = 0
584         }
585         return false
586 }
587
588 // PageDown scrolls the view down a page
589 func PageDown(v *View) bool {
590         if len(v.buf.lines)-(v.topline+v.height) > v.height {
591                 v.ScrollDown(v.height)
592         } else {
593                 if len(v.buf.lines) >= v.height {
594                         v.topline = len(v.buf.lines) - v.height
595                 }
596         }
597         return false
598 }
599
600 // HalfPageUp scrolls the view up half a page
601 func HalfPageUp(v *View) bool {
602         if v.topline > v.height/2 {
603                 v.ScrollUp(v.height / 2)
604         } else {
605                 v.topline = 0
606         }
607         return false
608 }
609
610 // HalfPageDown scrolls the view down half a page
611 func HalfPageDown(v *View) bool {
612         if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
613                 v.ScrollDown(v.height / 2)
614         } else {
615                 if len(v.buf.lines) >= v.height {
616                         v.topline = len(v.buf.lines) - v.height
617                 }
618         }
619         return false
620 }
621
622 // ToggleRuler turns line numbers off and on
623 func ToggleRuler(v *View) bool {
624         if settings.Ruler == false {
625                 settings.Ruler = true
626         } else {
627                 settings.Ruler = false
628         }
629         return false
630 }
631
632 // StartOfLine moves the cursor to the start of the line
633 func StartOfLine(v *View) bool {
634         v.cursor.Start()
635         return true
636 }
637
638 // EndOfLine moves the cursor to the end of the line
639 func EndOfLine(v *View) bool {
640         v.cursor.End()
641         return true
642 }
643
644 // None is no action
645 func None() bool {
646         return false
647 }
648
649 // gofmt runs gofmt on a file
650 func gofmt(file string) error {
651         cmd := exec.Command("gofmt", "-w", file)
652         cmd.Start()
653         err := cmd.Wait()
654         if err != nil {
655                 return errors.New("Check syntax ") //TODO: highlight or display locations
656         }
657         return nil
658 }
659
660 // goimports runs goimports on a file
661 func goimports(file string) error {
662         cmd := exec.Command("goimports", "-w", file)
663         cmd.Start()
664         err := cmd.Wait()
665         if err != nil {
666                 return errors.New("Check syntax ") //TODO: highlight or display locations
667         }
668         return nil
669 }