13 "github.com/mitchellh/go-homedir"
14 "github.com/yuin/gopher-lua"
15 "github.com/zyedidia/clipboard"
16 "github.com/zyedidia/tcell"
19 var bindings map[tcell.Key]func(*View) bool
21 // InitBindings initializes the keybindings for micro
23 bindings = make(map[tcell.Key]func(*View) bool)
25 actions := map[string]func(*View) bool{
26 "CursorUp": (*View).CursorUp,
27 "CursorDown": (*View).CursorDown,
28 "CursorLeft": (*View).CursorLeft,
29 "CursorRight": (*View).CursorRight,
30 "CursorStart": (*View).CursorStart,
31 "CursorEnd": (*View).CursorEnd,
32 "SelectToStart": (*View).SelectToStart,
33 "SelectToEnd": (*View).SelectToEnd,
34 "SelectUp": (*View).SelectUp,
35 "SelectDown": (*View).SelectDown,
36 "SelectLeft": (*View).SelectLeft,
37 "SelectRight": (*View).SelectRight,
38 "WordRight": (*View).WordRight,
39 "WordLeft": (*View).WordLeft,
40 "SelectWordRight": (*View).SelectWordRight,
41 "SelectWordLeft": (*View).SelectWordLeft,
42 "SelectToStartOfLine": (*View).SelectToStartOfLine,
43 "SelectToEndOfLine": (*View).SelectToEndOfLine,
44 "InsertEnter": (*View).InsertEnter,
45 "InsertSpace": (*View).InsertSpace,
46 "Backspace": (*View).Backspace,
47 "Delete": (*View).Delete,
48 "InsertTab": (*View).InsertTab,
51 "FindNext": (*View).FindNext,
52 "FindPrevious": (*View).FindPrevious,
57 "CutLine": (*View).CutLine,
58 "Paste": (*View).Paste,
59 "SelectAll": (*View).SelectAll,
60 "OpenFile": (*View).OpenFile,
61 "Start": (*View).Start,
63 "PageUp": (*View).PageUp,
64 "PageDown": (*View).PageDown,
65 "HalfPageUp": (*View).HalfPageUp,
66 "HalfPageDown": (*View).HalfPageDown,
67 "StartOfLine": (*View).StartOfLine,
68 "EndOfLine": (*View).EndOfLine,
69 "ToggleRuler": (*View).ToggleRuler,
70 "JumpLine": (*View).JumpLine,
73 keys := map[string]tcell.Key{
75 "Down": tcell.KeyDown,
76 "Right": tcell.KeyRight,
77 "Left": tcell.KeyLeft,
78 "AltUp": tcell.KeyAltUp,
79 "AltDown": tcell.KeyAltDown,
80 "AltLeft": tcell.KeyAltLeft,
81 "AltRight": tcell.KeyAltRight,
82 "CtrlUp": tcell.KeyCtrlUp,
83 "CtrlDown": tcell.KeyCtrlDown,
84 "CtrlLeft": tcell.KeyCtrlLeft,
85 "CtrlRight": tcell.KeyCtrlRight,
86 "ShiftUp": tcell.KeyShiftUp,
87 "ShiftDown": tcell.KeyShiftDown,
88 "ShiftLeft": tcell.KeyShiftLeft,
89 "ShiftRight": tcell.KeyShiftRight,
90 "AltShiftUp": tcell.KeyAltShiftUp,
91 "AltShiftDown": tcell.KeyAltShiftDown,
92 "AltShiftLeft": tcell.KeyAltShiftLeft,
93 "AltShiftRight": tcell.KeyAltShiftRight,
94 "CtrlShiftUp": tcell.KeyCtrlShiftUp,
95 "CtrlShiftDown": tcell.KeyCtrlShiftDown,
96 "CtrlShiftLeft": tcell.KeyCtrlShiftLeft,
97 "CtrlShiftRight": tcell.KeyCtrlShiftRight,
98 "UpLeft": tcell.KeyUpLeft,
99 "UpRight": tcell.KeyUpRight,
100 "DownLeft": tcell.KeyDownLeft,
101 "DownRight": tcell.KeyDownRight,
102 "Center": tcell.KeyCenter,
103 "PgUp": tcell.KeyPgUp,
104 "PgDn": tcell.KeyPgDn,
105 "Home": tcell.KeyHome,
107 "Insert": tcell.KeyInsert,
108 "Delete": tcell.KeyDelete,
109 "Help": tcell.KeyHelp,
110 "Exit": tcell.KeyExit,
111 "Clear": tcell.KeyClear,
112 "Cancel": tcell.KeyCancel,
113 "Print": tcell.KeyPrint,
114 "Pause": tcell.KeyPause,
115 "Backtab": tcell.KeyBacktab,
180 "CtrlSpace": tcell.KeyCtrlSpace,
181 "CtrlA": tcell.KeyCtrlA,
182 "CtrlB": tcell.KeyCtrlB,
183 "CtrlC": tcell.KeyCtrlC,
184 "CtrlD": tcell.KeyCtrlD,
185 "CtrlE": tcell.KeyCtrlE,
186 "CtrlF": tcell.KeyCtrlF,
187 "CtrlG": tcell.KeyCtrlG,
188 "CtrlH": tcell.KeyCtrlH,
189 "CtrlI": tcell.KeyCtrlI,
190 "CtrlJ": tcell.KeyCtrlJ,
191 "CtrlK": tcell.KeyCtrlK,
192 "CtrlL": tcell.KeyCtrlL,
193 "CtrlM": tcell.KeyCtrlM,
194 "CtrlN": tcell.KeyCtrlN,
195 "CtrlO": tcell.KeyCtrlO,
196 "CtrlP": tcell.KeyCtrlP,
197 "CtrlQ": tcell.KeyCtrlQ,
198 "CtrlR": tcell.KeyCtrlR,
199 "CtrlS": tcell.KeyCtrlS,
200 "CtrlT": tcell.KeyCtrlT,
201 "CtrlU": tcell.KeyCtrlU,
202 "CtrlV": tcell.KeyCtrlV,
203 "CtrlW": tcell.KeyCtrlW,
204 "CtrlX": tcell.KeyCtrlX,
205 "CtrlY": tcell.KeyCtrlY,
206 "CtrlZ": tcell.KeyCtrlZ,
207 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
208 "CtrlBackslash": tcell.KeyCtrlBackslash,
209 "CtrlRightSq": tcell.KeyCtrlRightSq,
210 "CtrlCarat": tcell.KeyCtrlCarat,
211 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
212 "Backspace": tcell.KeyBackspace,
215 "Escape": tcell.KeyEscape,
216 "Enter": tcell.KeyEnter,
217 "Space": tcell.KeySpace,
218 "Backspace2": tcell.KeyBackspace2,
221 var parsed map[string]string
222 defaults := DefaultBindings()
224 filename := configDir + "/bindings.json"
225 if _, e := os.Stat(filename); e == nil {
226 input, err := ioutil.ReadFile(filename)
228 TermMessage("Error reading bindings.json file: " + err.Error())
232 err = json.Unmarshal(input, &parsed)
234 TermMessage("Error reading bindings.json:", err.Error())
238 for k, v := range defaults {
239 bindings[keys[k]] = actions[v]
241 for k, v := range parsed {
242 bindings[keys[k]] = actions[v]
246 // DefaultBindings returns a map containing micro's default keybindings
247 func DefaultBindings() map[string]string {
248 return map[string]string{
250 "Down": "CursorDown",
251 "Right": "CursorRight",
252 "Left": "CursorLeft",
253 "ShiftUp": "SelectUp",
254 "ShiftDown": "SelectDown",
255 "ShiftLeft": "SelectLeft",
256 "ShiftRight": "SelectRight",
257 "AltLeft": "WordLeft",
258 "AltRight": "WordRight",
259 "AltShiftRight": "SelectWordRight",
260 "AltShiftLeft": "SelectWordLeft",
261 "CtrlLeft": "StartOfLine",
262 "CtrlRight": "EndOfLine",
263 "CtrlShiftLeft": "SelectToStartOfLine",
264 "CtrlShiftRight": "SelectToEndOfLine",
265 "CtrlUp": "CursorStart",
266 "CtrlDown": "CursorEnd",
267 "CtrlShiftUp": "SelectToStart",
268 "CtrlShiftDown": "SelectToEnd",
269 "Enter": "InsertEnter",
270 "Space": "InsertSpace",
271 "Backspace": "Backspace",
272 "Backspace2": "Backspace",
278 "CtrlP": "FindPrevious",
285 "CtrlA": "SelectAll",
290 "CtrlU": "HalfPageUp",
291 "CtrlD": "HalfPageDown",
292 "CtrlR": "ToggleRuler",
298 // CursorUp moves the cursor up
299 func (v *View) CursorUp() bool {
300 if v.Cursor.HasSelection() {
301 v.Cursor.SetLoc(v.Cursor.curSelection[0])
302 v.Cursor.ResetSelection()
308 // CursorDown moves the cursor down
309 func (v *View) CursorDown() bool {
310 if v.Cursor.HasSelection() {
311 v.Cursor.SetLoc(v.Cursor.curSelection[1])
312 v.Cursor.ResetSelection()
318 // CursorLeft moves the cursor left
319 func (v *View) CursorLeft() bool {
320 if v.Cursor.HasSelection() {
321 v.Cursor.SetLoc(v.Cursor.curSelection[0])
322 v.Cursor.ResetSelection()
329 // CursorRight moves the cursor right
330 func (v *View) CursorRight() bool {
331 if v.Cursor.HasSelection() {
332 v.Cursor.SetLoc(v.Cursor.curSelection[1] - 1)
333 v.Cursor.ResetSelection()
340 // WordRight moves the cursor one word to the right
341 func (v *View) WordRight() bool {
346 // WordLeft moves the cursor one word to the left
347 func (v *View) WordLeft() bool {
352 // SelectUp selects up one line
353 func (v *View) SelectUp() bool {
354 loc := v.Cursor.Loc()
355 if !v.Cursor.HasSelection() {
356 v.Cursor.origSelection[0] = loc
359 v.Cursor.SelectTo(v.Cursor.Loc())
363 // SelectDown selects down one line
364 func (v *View) SelectDown() bool {
365 loc := v.Cursor.Loc()
366 if !v.Cursor.HasSelection() {
367 v.Cursor.origSelection[0] = loc
370 v.Cursor.SelectTo(v.Cursor.Loc())
374 // SelectLeft selects the character to the left of the cursor
375 func (v *View) SelectLeft() bool {
376 loc := v.Cursor.Loc()
377 count := v.Buf.Len() - 1
381 if !v.Cursor.HasSelection() {
382 v.Cursor.origSelection[0] = loc
385 v.Cursor.SelectTo(v.Cursor.Loc())
389 // SelectRight selects the character to the right of the cursor
390 func (v *View) SelectRight() bool {
391 loc := v.Cursor.Loc()
392 count := v.Buf.Len() - 1
396 if !v.Cursor.HasSelection() {
397 v.Cursor.origSelection[0] = loc
400 v.Cursor.SelectTo(v.Cursor.Loc())
404 // SelectWordRight selects the word to the right of the cursor
405 func (v *View) SelectWordRight() bool {
406 loc := v.Cursor.Loc()
407 if !v.Cursor.HasSelection() {
408 v.Cursor.origSelection[0] = loc
411 v.Cursor.SelectTo(v.Cursor.Loc())
415 // SelectWordLeft selects the word to the left of the cursor
416 func (v *View) SelectWordLeft() bool {
417 loc := v.Cursor.Loc()
418 if !v.Cursor.HasSelection() {
419 v.Cursor.origSelection[0] = loc
422 v.Cursor.SelectTo(v.Cursor.Loc())
426 // StartOfLine moves the cursor to the start of the line
427 func (v *View) StartOfLine() bool {
432 // EndOfLine moves the cursor to the end of the line
433 func (v *View) EndOfLine() bool {
438 // SelectToStartOfLine selects to the start of the current line
439 func (v *View) SelectToStartOfLine() bool {
440 loc := v.Cursor.Loc()
441 if !v.Cursor.HasSelection() {
442 v.Cursor.origSelection[0] = loc
445 v.Cursor.SelectTo(v.Cursor.Loc())
449 // SelectToEndOfLine selects to the end of the current line
450 func (v *View) SelectToEndOfLine() bool {
451 loc := v.Cursor.Loc()
452 if !v.Cursor.HasSelection() {
453 v.Cursor.origSelection[0] = loc
456 v.Cursor.SelectTo(v.Cursor.Loc())
460 // CursorStart moves the cursor to the start of the buffer
461 func (v *View) CursorStart() bool {
467 // CursorEnd moves the cursor to the end of the buffer
468 func (v *View) CursorEnd() bool {
469 v.Cursor.SetLoc(v.Buf.Len())
473 // SelectToStart selects the text from the cursor to the start of the buffer
474 func (v *View) SelectToStart() bool {
475 loc := v.Cursor.Loc()
476 if !v.Cursor.HasSelection() {
477 v.Cursor.origSelection[0] = loc
484 // SelectToEnd selects the text from the cursor to the end of the buffer
485 func (v *View) SelectToEnd() bool {
486 loc := v.Cursor.Loc()
487 if !v.Cursor.HasSelection() {
488 v.Cursor.origSelection[0] = loc
491 v.Cursor.SelectTo(v.Buf.Len())
495 // InsertSpace inserts a space
496 func (v *View) InsertSpace() bool {
498 if v.Cursor.HasSelection() {
499 v.Cursor.DeleteSelection()
500 v.Cursor.ResetSelection()
502 v.eh.Insert(v.Cursor.Loc(), " ")
507 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
508 func (v *View) InsertEnter() bool {
510 if v.Cursor.HasSelection() {
511 v.Cursor.DeleteSelection()
512 v.Cursor.ResetSelection()
515 v.eh.Insert(v.Cursor.Loc(), "\n")
516 ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y])
519 if settings["autoindent"].(bool) {
520 v.eh.Insert(v.Cursor.Loc(), ws)
521 for i := 0; i < len(ws); i++ {
525 v.Cursor.lastVisualX = v.Cursor.GetVisualX()
529 // Backspace deletes the previous character
530 func (v *View) Backspace() bool {
531 // Delete a character
532 if v.Cursor.HasSelection() {
533 v.Cursor.DeleteSelection()
534 v.Cursor.ResetSelection()
535 } else if v.Cursor.Loc() > 0 {
536 // We have to do something a bit hacky here because we want to
537 // delete the line by first moving left and then deleting backwards
538 // but the undo redo would place the cursor in the wrong place
539 // So instead we move left, save the position, move back, delete
540 // and restore the position
542 // If the user is using spaces instead of tabs and they are deleting
543 // whitespace at the start of the line, we should delete as if its a
544 // tab (tabSize number of spaces)
545 lineStart := v.Buf.Lines[v.Cursor.y][:v.Cursor.x]
546 tabSize := int(settings["tabsize"].(float64))
547 if settings["tabsToSpaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
548 loc := v.Cursor.Loc()
549 v.Cursor.SetLoc(loc - tabSize)
550 cx, cy := v.Cursor.x, v.Cursor.y
552 v.eh.Remove(loc-tabSize, loc)
553 v.Cursor.x, v.Cursor.y = cx, cy
556 cx, cy := v.Cursor.x, v.Cursor.y
558 loc := v.Cursor.Loc()
559 v.eh.Remove(loc-1, loc)
560 v.Cursor.x, v.Cursor.y = cx, cy
563 v.Cursor.lastVisualX = v.Cursor.GetVisualX()
567 // Delete deletes the next character
568 func (v *View) Delete() bool {
569 if v.Cursor.HasSelection() {
570 v.Cursor.DeleteSelection()
571 v.Cursor.ResetSelection()
573 loc := v.Cursor.Loc()
574 if loc < v.Buf.Len() {
575 v.eh.Remove(loc, loc+1)
581 // InsertTab inserts a tab or spaces
582 func (v *View) InsertTab() bool {
584 if v.Cursor.HasSelection() {
585 v.Cursor.DeleteSelection()
586 v.Cursor.ResetSelection()
588 if settings["tabsToSpaces"].(bool) {
589 tabSize := int(settings["tabsize"].(float64))
590 v.eh.Insert(v.Cursor.Loc(), Spaces(tabSize))
591 for i := 0; i < tabSize; i++ {
595 v.eh.Insert(v.Cursor.Loc(), "\t")
601 // Save the buffer to disk
602 func (v *View) Save() bool {
603 // If this is an empty buffer, ask for a filename
604 if v.Buf.Path == "" {
605 filename, canceled := messenger.Prompt("Filename: ")
607 v.Buf.Path = filename
608 v.Buf.Name = filename
615 messenger.Error(err.Error())
617 messenger.Message("Saved " + v.Buf.Path)
618 switch v.Buf.Filetype {
623 if err := L.CallByParam(lua.P{
624 Fn: L.GetGlobal("onSave"),
628 // The function isn't defined by this plugin
635 // GoSave saves the current file (must be a go file) and runs goimports or gofmt
636 // depending on the user's configuration
637 func (v *View) GoSave() {
638 if settings["goimports"] == true {
639 messenger.Message("Running goimports...")
640 err := goimports(v.Buf.Path)
644 messenger.Message("Saved " + v.Buf.Path)
647 } else if settings["gofmt"] == true {
648 messenger.Message("Running gofmt...")
649 err := gofmt(v.Buf.Path)
653 messenger.Message("Saved " + v.Buf.Path)
662 // Find opens a prompt and searches forward for the input
663 func (v *View) Find() bool {
664 if v.Cursor.HasSelection() {
665 searchStart = v.Cursor.curSelection[1]
667 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
673 // FindNext searches forwards for the last used search term
674 func (v *View) FindNext() bool {
675 if v.Cursor.HasSelection() {
676 searchStart = v.Cursor.curSelection[1]
678 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
680 messenger.Message("Find: " + lastSearch)
681 Search(lastSearch, v, true)
685 // FindPrevious searches backwards for the last used search term
686 func (v *View) FindPrevious() bool {
687 if v.Cursor.HasSelection() {
688 searchStart = v.Cursor.curSelection[0]
690 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
692 messenger.Message("Find: " + lastSearch)
693 Search(lastSearch, v, false)
697 // Undo undoes the last action
698 func (v *View) Undo() bool {
703 // Redo redoes the last action
704 func (v *View) Redo() bool {
709 // Copy the selection to the system clipboard
710 func (v *View) Copy() bool {
711 if v.Cursor.HasSelection() {
712 clipboard.WriteAll(v.Cursor.GetSelection())
718 // CutLine cuts the current line to the clipboard
719 func (v *View) CutLine() bool {
720 v.Cursor.SelectLine()
721 if v.freshClip == true {
723 if v.Cursor.HasSelection() {
724 if clip, err := clipboard.ReadAll(); err != nil {
727 clipboard.WriteAll(clip + v.Cursor.GetSelection())
730 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
734 v.lastCutTime = time.Now()
735 v.Cursor.DeleteSelection()
736 v.Cursor.ResetSelection()
740 // Cut the selection to the system clipboard
741 func (v *View) Cut() bool {
742 if v.Cursor.HasSelection() {
743 clipboard.WriteAll(v.Cursor.GetSelection())
744 v.Cursor.DeleteSelection()
745 v.Cursor.ResetSelection()
751 // Paste whatever is in the system clipboard into the buffer
752 // Delete and paste if the user has a selection
753 func (v *View) Paste() bool {
754 if v.Cursor.HasSelection() {
755 v.Cursor.DeleteSelection()
756 v.Cursor.ResetSelection()
758 clip, _ := clipboard.ReadAll()
759 v.eh.Insert(v.Cursor.Loc(), clip)
760 v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
765 // SelectAll selects the entire buffer
766 func (v *View) SelectAll() bool {
767 v.Cursor.curSelection[1] = 0
768 v.Cursor.curSelection[0] = v.Buf.Len()
769 // Put the cursor at the beginning
775 // OpenFile opens a new file in the buffer
776 func (v *View) OpenFile() bool {
777 if v.CanClose("Continue? (yes, no, save) ") {
778 filename, canceled := messenger.Prompt("File to open: ")
782 home, _ := homedir.Dir()
783 filename = strings.Replace(filename, "~", home, 1)
784 file, err := ioutil.ReadFile(filename)
787 messenger.Error(err.Error())
790 buf := NewBuffer(string(file), filename)
796 // Start moves the viewport to the start of the buffer
797 func (v *View) Start() bool {
802 // End moves the viewport to the end of the buffer
803 func (v *View) End() bool {
804 if v.height > v.Buf.NumLines {
807 v.Topline = v.Buf.NumLines - v.height
812 // PageUp scrolls the view up a page
813 func (v *View) PageUp() bool {
814 if v.Topline > v.height {
822 // PageDown scrolls the view down a page
823 func (v *View) PageDown() bool {
824 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
825 v.ScrollDown(v.height)
826 } else if v.Buf.NumLines >= v.height {
827 v.Topline = v.Buf.NumLines - v.height
832 // HalfPageUp scrolls the view up half a page
833 func (v *View) HalfPageUp() bool {
834 if v.Topline > v.height/2 {
835 v.ScrollUp(v.height / 2)
842 // HalfPageDown scrolls the view down half a page
843 func (v *View) HalfPageDown() bool {
844 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
845 v.ScrollDown(v.height / 2)
847 if v.Buf.NumLines >= v.height {
848 v.Topline = v.Buf.NumLines - v.height
854 // ToggleRuler turns line numbers off and on
855 func (v *View) ToggleRuler() bool {
856 if settings["ruler"] == false {
857 settings["ruler"] = true
859 settings["ruler"] = false
864 // JumpLine jumps to a line and moves the view accordingly.
865 func (v *View) JumpLine() bool {
866 // Prompt for line number
867 linestring, canceled := messenger.Prompt("Jump to line # ")
871 lineint, err := strconv.Atoi(linestring)
872 lineint = lineint - 1 // fix offset
874 messenger.Error(err) // return errors
877 // Move cursor and view if possible.
878 if lineint < v.Buf.NumLines {
883 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
892 // gofmt runs gofmt on a file
893 func gofmt(file string) error {
894 cmd := exec.Command("gofmt", "-w", file)
898 return errors.New("Check syntax ") //TODO: highlight or display locations
903 // goimports runs goimports on a file
904 func goimports(file string) error {
905 cmd := exec.Command("goimports", "-w", file)
909 return errors.New("Check syntax ") //TODO: highlight or display locations