12 "github.com/mitchellh/go-homedir"
13 "github.com/zyedidia/clipboard"
14 "github.com/zyedidia/tcell"
17 var bindings map[tcell.Key]func(*View) bool
19 // InitBindings initializes the keybindings for micro
21 bindings = make(map[tcell.Key]func(*View) bool)
23 actions := map[string]func(*View) bool{
24 "CursorUp": (*View).CursorUp,
25 "CursorDown": (*View).CursorDown,
26 "CursorLeft": (*View).CursorLeft,
27 "CursorRight": (*View).CursorRight,
28 "CursorStart": (*View).CursorStart,
29 "CursorEnd": (*View).CursorEnd,
30 "SelectToStart": (*View).SelectToStart,
31 "SelectToEnd": (*View).SelectToEnd,
32 "SelectUp": (*View).SelectUp,
33 "SelectDown": (*View).SelectDown,
34 "SelectLeft": (*View).SelectLeft,
35 "SelectRight": (*View).SelectRight,
36 "WordRight": (*View).WordRight,
37 "WordLeft": (*View).WordLeft,
38 "SelectWordRight": (*View).SelectWordRight,
39 "SelectWordLeft": (*View).SelectWordLeft,
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,
49 "FindNext": (*View).FindNext,
50 "FindPrevious": (*View).FindPrevious,
55 "CutLine": (*View).CutLine,
56 "Paste": (*View).Paste,
57 "SelectAll": (*View).SelectAll,
58 "OpenFile": (*View).OpenFile,
59 "Start": (*View).Start,
61 "PageUp": (*View).PageUp,
62 "PageDown": (*View).PageDown,
63 "HalfPageUp": (*View).HalfPageUp,
64 "HalfPageDown": (*View).HalfPageDown,
65 "StartOfLine": (*View).StartOfLine,
66 "EndOfLine": (*View).EndOfLine,
67 "ToggleRuler": (*View).ToggleRuler,
70 keys := map[string]tcell.Key{
72 "Down": tcell.KeyDown,
73 "Right": tcell.KeyRight,
74 "Left": tcell.KeyLeft,
75 "AltUp": tcell.KeyAltUp,
76 "AltDown": tcell.KeyAltDown,
77 "AltLeft": tcell.KeyAltLeft,
78 "AltRight": tcell.KeyAltRight,
79 "CtrlUp": tcell.KeyCtrlUp,
80 "CtrlDown": tcell.KeyCtrlDown,
81 "CtrlLeft": tcell.KeyCtrlLeft,
82 "CtrlRight": tcell.KeyCtrlRight,
83 "ShiftUp": tcell.KeyShiftUp,
84 "ShiftDown": tcell.KeyShiftDown,
85 "ShiftLeft": tcell.KeyShiftLeft,
86 "ShiftRight": tcell.KeyShiftRight,
87 "AltShiftUp": tcell.KeyAltShiftUp,
88 "AltShiftDown": tcell.KeyAltShiftDown,
89 "AltShiftLeft": tcell.KeyAltShiftLeft,
90 "AltShiftRight": tcell.KeyAltShiftRight,
91 "CtrlShiftUp": tcell.KeyCtrlShiftUp,
92 "CtrlShiftDown": tcell.KeyCtrlShiftDown,
93 "CtrlShiftLeft": tcell.KeyCtrlShiftLeft,
94 "CtrlShiftRight": tcell.KeyCtrlShiftRight,
95 "UpLeft": tcell.KeyUpLeft,
96 "UpRight": tcell.KeyUpRight,
97 "DownLeft": tcell.KeyDownLeft,
98 "DownRight": tcell.KeyDownRight,
99 "Center": tcell.KeyCenter,
100 "PgUp": tcell.KeyPgUp,
101 "PgDn": tcell.KeyPgDn,
102 "Home": tcell.KeyHome,
104 "Insert": tcell.KeyInsert,
105 "Delete": tcell.KeyDelete,
106 "Help": tcell.KeyHelp,
107 "Exit": tcell.KeyExit,
108 "Clear": tcell.KeyClear,
109 "Cancel": tcell.KeyCancel,
110 "Print": tcell.KeyPrint,
111 "Pause": tcell.KeyPause,
112 "Backtab": tcell.KeyBacktab,
177 "CtrlSpace": tcell.KeyCtrlSpace,
178 "CtrlA": tcell.KeyCtrlA,
179 "CtrlB": tcell.KeyCtrlB,
180 "CtrlC": tcell.KeyCtrlC,
181 "CtrlD": tcell.KeyCtrlD,
182 "CtrlE": tcell.KeyCtrlE,
183 "CtrlF": tcell.KeyCtrlF,
184 "CtrlG": tcell.KeyCtrlG,
185 "CtrlH": tcell.KeyCtrlH,
186 "CtrlI": tcell.KeyCtrlI,
187 "CtrlJ": tcell.KeyCtrlJ,
188 "CtrlK": tcell.KeyCtrlK,
189 "CtrlL": tcell.KeyCtrlL,
190 "CtrlM": tcell.KeyCtrlM,
191 "CtrlN": tcell.KeyCtrlN,
192 "CtrlO": tcell.KeyCtrlO,
193 "CtrlP": tcell.KeyCtrlP,
194 "CtrlQ": tcell.KeyCtrlQ,
195 "CtrlR": tcell.KeyCtrlR,
196 "CtrlS": tcell.KeyCtrlS,
197 "CtrlT": tcell.KeyCtrlT,
198 "CtrlU": tcell.KeyCtrlU,
199 "CtrlV": tcell.KeyCtrlV,
200 "CtrlW": tcell.KeyCtrlW,
201 "CtrlX": tcell.KeyCtrlX,
202 "CtrlY": tcell.KeyCtrlY,
203 "CtrlZ": tcell.KeyCtrlZ,
204 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
205 "CtrlBackslash": tcell.KeyCtrlBackslash,
206 "CtrlRightSq": tcell.KeyCtrlRightSq,
207 "CtrlCarat": tcell.KeyCtrlCarat,
208 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
209 "Backspace": tcell.KeyBackspace,
212 "Escape": tcell.KeyEscape,
213 "Enter": tcell.KeyEnter,
214 "Space": tcell.KeySpace,
215 "Backspace2": tcell.KeyBackspace2,
218 var parsed map[string]string
219 defaults := DefaultBindings()
221 filename := configDir + "/bindings.json"
222 if _, e := os.Stat(filename); e == nil {
223 input, err := ioutil.ReadFile(filename)
225 TermMessage("Error reading bindings.json file: " + err.Error())
229 err = json.Unmarshal(input, &parsed)
231 TermMessage("Error reading bindings.json:", err.Error())
235 for k, v := range defaults {
236 bindings[keys[k]] = actions[v]
238 for k, v := range parsed {
239 bindings[keys[k]] = actions[v]
243 // DefaultBindings returns a map containing micro's default keybindings
244 func DefaultBindings() map[string]string {
245 return map[string]string{
247 "Down": "CursorDown",
248 "Right": "CursorRight",
249 "Left": "CursorLeft",
250 "ShiftUp": "SelectUp",
251 "ShiftDown": "SelectDown",
252 "ShiftLeft": "SelectLeft",
253 "ShiftRight": "SelectRight",
254 "AltLeft": "WordLeft",
255 "AltRight": "WordRight",
256 "AltShiftRight": "SelectWordRight",
257 "AltShiftLeft": "SelectWordLeft",
258 "CtrlLeft": "StartOfLine",
259 "CtrlRight": "EndOfLine",
260 "CtrlShiftLeft": "SelectToStartOfLine",
261 "CtrlShiftRight": "SelectToEndOfLine",
262 "CtrlUp": "CursorStart",
263 "CtrlDown": "CursorEnd",
264 "CtrlShiftUp": "SelectToStart",
265 "CtrlShiftDown": "SelectToEnd",
266 "Enter": "InsertEnter",
267 "Space": "InsertSpace",
268 "Backspace": "Backspace",
269 "Backspace2": "Backspace",
275 "CtrlP": "FindPrevious",
282 "CtrlA": "SelectAll",
287 "CtrlU": "HalfPageUp",
288 "CtrlD": "HalfPageDown",
289 "CtrlR": "ToggleRuler",
294 // CursorUp moves the cursor up
295 func (v *View) CursorUp() bool {
296 v.cursor.ResetSelection()
301 // CursorDown moves the cursor down
302 func (v *View) CursorDown() bool {
303 v.cursor.ResetSelection()
308 // CursorLeft moves the cursor left
309 func (v *View) CursorLeft() bool {
310 if v.cursor.HasSelection() {
311 v.cursor.SetLoc(v.cursor.curSelection[0])
312 v.cursor.ResetSelection()
319 // CursorRight moves the cursor right
320 func (v *View) CursorRight() bool {
321 if v.cursor.HasSelection() {
322 v.cursor.SetLoc(v.cursor.curSelection[1] - 1)
323 v.cursor.ResetSelection()
330 // WordRight moves the cursor one word to the right
331 func (v *View) WordRight() bool {
336 // WordLeft moves the cursor one word to the left
337 func (v *View) WordLeft() bool {
342 // SelectUp selects up one line
343 func (v *View) SelectUp() bool {
344 loc := v.cursor.Loc()
345 if !v.cursor.HasSelection() {
346 v.cursor.origSelection[0] = loc
349 v.cursor.SelectTo(v.cursor.Loc())
353 // SelectUp selects down one line
354 func (v *View) SelectDown() bool {
355 loc := v.cursor.Loc()
356 if !v.cursor.HasSelection() {
357 v.cursor.origSelection[0] = loc
360 v.cursor.SelectTo(v.cursor.Loc())
364 // SelectLeft selects the character to the left of the cursor
365 func (v *View) SelectLeft() bool {
366 loc := v.cursor.Loc()
367 if !v.cursor.HasSelection() {
368 v.cursor.origSelection[0] = loc
370 v.cursor.SelectTo(loc - 1)
375 // SelectRight selects the character to the right of the cursor
376 func (v *View) SelectRight() bool {
377 loc := v.cursor.Loc()
378 if !v.cursor.HasSelection() {
379 v.cursor.origSelection[0] = loc
381 v.cursor.SelectTo(loc + 1)
386 // SelectWordRight selects the word to the right of the cursor
387 func (v *View) SelectWordRight() bool {
388 loc := v.cursor.Loc()
389 if !v.cursor.HasSelection() {
390 v.cursor.origSelection[0] = loc
393 v.cursor.SelectTo(v.cursor.Loc())
397 // SelectWordLeft selects the word to the left of the cursor
398 func (v *View) SelectWordLeft() bool {
399 loc := v.cursor.Loc()
400 if !v.cursor.HasSelection() {
401 v.cursor.origSelection[0] = loc
404 v.cursor.SelectTo(v.cursor.Loc())
408 // StartOfLine moves the cursor to the start of the line
409 func (v *View) StartOfLine() bool {
414 // EndOfLine moves the cursor to the end of the line
415 func (v *View) EndOfLine() bool {
420 // SelectToStartOfLine selects to the start of the current line
421 func (v *View) SelectToStartOfLine() bool {
422 loc := v.cursor.Loc()
423 if !v.cursor.HasSelection() {
424 v.cursor.origSelection[0] = loc
427 v.cursor.SelectTo(v.cursor.Loc())
431 // SelectToEndOfLine selects to the end of the current line
432 func (v *View) SelectToEndOfLine() bool {
433 loc := v.cursor.Loc()
434 if !v.cursor.HasSelection() {
435 v.cursor.origSelection[0] = loc
438 v.cursor.SelectTo(v.cursor.Loc())
442 // CursorStart moves the cursor to the start of the buffer
443 func (v *View) CursorStart() bool {
449 // CursorEnd moves the cursor to the end of the buffer
450 func (v *View) CursorEnd() bool {
451 v.cursor.SetLoc(len(v.buf.text))
455 // SelectToStart selects the text from the cursor to the start of the buffer
456 func (v *View) SelectToStart() bool {
457 loc := v.cursor.Loc()
458 if !v.cursor.HasSelection() {
459 v.cursor.origSelection[0] = loc
466 // SelectToEnd selects the text from the cursor to the end of the buffer
467 func (v *View) SelectToEnd() bool {
468 loc := v.cursor.Loc()
469 if !v.cursor.HasSelection() {
470 v.cursor.origSelection[0] = loc
473 v.cursor.SelectTo(len(v.buf.text))
477 // InsertSpace inserts a space
478 func (v *View) InsertSpace() bool {
480 if v.cursor.HasSelection() {
481 v.cursor.DeleteSelection()
482 v.cursor.ResetSelection()
484 v.eh.Insert(v.cursor.Loc(), " ")
489 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
490 func (v *View) InsertEnter() bool {
492 if v.cursor.HasSelection() {
493 v.cursor.DeleteSelection()
494 v.cursor.ResetSelection()
497 v.eh.Insert(v.cursor.Loc(), "\n")
498 ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
501 if settings.AutoIndent {
502 v.eh.Insert(v.cursor.Loc(), ws)
503 for i := 0; i < len(ws); i++ {
507 v.cursor.lastVisualX = v.cursor.GetVisualX()
511 // Backspace deletes the previous character
512 func (v *View) Backspace() bool {
513 // Delete a character
514 if v.cursor.HasSelection() {
515 v.cursor.DeleteSelection()
516 v.cursor.ResetSelection()
517 } else if v.cursor.Loc() > 0 {
518 // We have to do something a bit hacky here because we want to
519 // delete the line by first moving left and then deleting backwards
520 // but the undo redo would place the cursor in the wrong place
521 // So instead we move left, save the position, move back, delete
522 // and restore the position
524 // If the user is using spaces instead of tabs and they are deleting
525 // whitespace at the start of the line, we should delete as if its a
526 // tab (tabSize number of spaces)
527 lineStart := v.buf.lines[v.cursor.y][:v.cursor.x]
528 if settings.TabsToSpaces && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%settings.TabSize == 0 {
529 loc := v.cursor.Loc()
530 v.cursor.SetLoc(loc - settings.TabSize)
531 cx, cy := v.cursor.x, v.cursor.y
533 v.eh.Remove(loc-settings.TabSize, loc)
534 v.cursor.x, v.cursor.y = cx, cy
537 cx, cy := v.cursor.x, v.cursor.y
539 loc := v.cursor.Loc()
540 v.eh.Remove(loc-1, loc)
541 v.cursor.x, v.cursor.y = cx, cy
544 v.cursor.lastVisualX = v.cursor.GetVisualX()
548 // Delete deletes the next character
549 func (v *View) Delete() bool {
550 if v.cursor.HasSelection() {
551 v.cursor.DeleteSelection()
552 v.cursor.ResetSelection()
554 loc := v.cursor.Loc()
555 if loc < len(v.buf.text) {
556 v.eh.Remove(loc, loc+1)
562 // InsertTab inserts a tab or spaces
563 func (v *View) InsertTab() bool {
565 if v.cursor.HasSelection() {
566 v.cursor.DeleteSelection()
567 v.cursor.ResetSelection()
569 if settings.TabsToSpaces {
570 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
571 for i := 0; i < settings.TabSize; i++ {
575 v.eh.Insert(v.cursor.Loc(), "\t")
581 // Save the buffer to disk
582 func (v *View) Save() bool {
583 // If this is an empty buffer, ask for a filename
584 if v.buf.path == "" {
585 filename, canceled := messenger.Prompt("Filename: ")
587 v.buf.path = filename
588 v.buf.name = filename
595 messenger.Error(err.Error())
597 messenger.Message("Saved " + v.buf.path)
598 switch v.buf.filetype {
606 // GoSave saves the current file (must be a go file) and runs goimports or gofmt
607 // depending on the user's configuration
608 func (v *View) GoSave() {
609 if settings.GoImports == true {
610 messenger.Message("Running goimports...")
611 err := goimports(v.buf.path)
615 messenger.Message("Saved " + v.buf.path)
618 } else if settings.GoFmt == true {
619 messenger.Message("Running gofmt...")
620 err := gofmt(v.buf.path)
624 messenger.Message("Saved " + v.buf.path)
633 // Find opens a prompt and searches forward for the input
634 func (v *View) Find() bool {
635 if v.cursor.HasSelection() {
636 searchStart = v.cursor.curSelection[1]
638 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
644 // FindNext searches forwards for the last used search term
645 func (v *View) FindNext() bool {
646 if v.cursor.HasSelection() {
647 searchStart = v.cursor.curSelection[1]
649 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
651 messenger.Message("Find: " + lastSearch)
652 Search(lastSearch, v, true)
656 // FindPrevious searches backwards for the last used search term
657 func (v *View) FindPrevious() bool {
658 if v.cursor.HasSelection() {
659 searchStart = v.cursor.curSelection[0]
661 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
663 messenger.Message("Find: " + lastSearch)
664 Search(lastSearch, v, false)
668 // Undo undoes the last action
669 func (v *View) Undo() bool {
674 // Redo redoes the last action
675 func (v *View) Redo() bool {
680 // Copy the selection to the system clipboard
681 func (v *View) Copy() bool {
682 if v.cursor.HasSelection() {
683 clipboard.WriteAll(v.cursor.GetSelection())
689 // CutLine cuts the current line to the clipboard
690 func (v *View) CutLine() bool {
691 v.cursor.SelectLine()
692 if v.freshClip == true {
694 if v.cursor.HasSelection() {
695 if clip, err := clipboard.ReadAll(); err != nil {
698 clipboard.WriteAll(clip + v.cursor.GetSelection())
701 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
705 v.lastCutTime = time.Now()
706 v.cursor.DeleteSelection()
707 v.cursor.ResetSelection()
711 // Cut the selection to the system clipboard
712 func (v *View) Cut() bool {
713 if v.cursor.HasSelection() {
714 clipboard.WriteAll(v.cursor.GetSelection())
715 v.cursor.DeleteSelection()
716 v.cursor.ResetSelection()
722 // Paste whatever is in the system clipboard into the buffer
723 // Delete and paste if the user has a selection
724 func (v *View) Paste() bool {
725 if v.cursor.HasSelection() {
726 v.cursor.DeleteSelection()
727 v.cursor.ResetSelection()
729 clip, _ := clipboard.ReadAll()
730 v.eh.Insert(v.cursor.Loc(), clip)
731 v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
736 // SelectAll selects the entire buffer
737 func (v *View) SelectAll() bool {
738 v.cursor.curSelection[1] = 0
739 v.cursor.curSelection[0] = v.buf.Len()
740 // Put the cursor at the beginning
746 // OpenFile opens a new file in the buffer
747 func (v *View) OpenFile() bool {
748 if v.CanClose("Continue? (yes, no, save) ") {
749 filename, canceled := messenger.Prompt("File to open: ")
753 home, _ := homedir.Dir()
754 filename = strings.Replace(filename, "~", home, 1)
755 file, err := ioutil.ReadFile(filename)
758 messenger.Error(err.Error())
761 buf := NewBuffer(string(file), filename)
767 // Start moves the viewport to the start of the buffer
768 func (v *View) Start() bool {
773 // End moves the viewport to the end of the buffer
774 func (v *View) End() bool {
775 if v.height > len(v.buf.lines) {
778 v.topline = len(v.buf.lines) - v.height
783 // PageUp scrolls the view up a page
784 func (v *View) PageUp() bool {
785 if v.topline > v.height {
793 // PageDown scrolls the view down a page
794 func (v *View) PageDown() bool {
795 if len(v.buf.lines)-(v.topline+v.height) > v.height {
796 v.ScrollDown(v.height)
797 } else if len(v.buf.lines) >= v.height {
798 v.topline = len(v.buf.lines) - v.height
803 // HalfPageUp scrolls the view up half a page
804 func (v *View) HalfPageUp() bool {
805 if v.topline > v.height/2 {
806 v.ScrollUp(v.height / 2)
813 // HalfPageDown scrolls the view down half a page
814 func (v *View) HalfPageDown() bool {
815 if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
816 v.ScrollDown(v.height / 2)
818 if len(v.buf.lines) >= v.height {
819 v.topline = len(v.buf.lines) - v.height
825 // ToggleRuler turns line numbers off and on
826 func (v *View) ToggleRuler() bool {
827 if settings.Ruler == false {
828 settings.Ruler = true
830 settings.Ruler = false
840 // gofmt runs gofmt on a file
841 func gofmt(file string) error {
842 cmd := exec.Command("gofmt", "-w", file)
846 return errors.New("Check syntax ") //TODO: highlight or display locations
851 // goimports runs goimports on a file
852 func goimports(file string) error {
853 cmd := exec.Command("goimports", "-w", file)
857 return errors.New("Check syntax ") //TODO: highlight or display locations