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