11 "github.com/mitchellh/go-homedir"
12 "github.com/zyedidia/clipboard"
13 "github.com/zyedidia/tcell"
16 var bindings map[tcell.Key]func(*View) bool
18 // InitBindings initializes the keybindings for micro
20 bindings = make(map[tcell.Key]func(*View) bool)
22 actions := map[string]func(*View) bool{
23 "CursorUp": (*View).CursorUp,
24 "CursorDown": (*View).CursorDown,
25 "CursorLeft": (*View).CursorLeft,
26 "CursorRight": (*View).CursorRight,
27 "CursorStart": (*View).CursorStart,
28 "CursorEnd": (*View).CursorEnd,
29 "SelectToStart": (*View).SelectToStart,
30 "SelectToEnd": (*View).SelectToEnd,
31 "SelectUp": (*View).SelectUp,
32 "SelectDown": (*View).SelectDown,
33 "SelectLeft": (*View).SelectLeft,
34 "SelectRight": (*View).SelectRight,
35 "WordRight": (*View).WordRight,
36 "WordLeft": (*View).WordLeft,
37 "SelectWordRight": (*View).SelectWordRight,
38 "SelectWordLeft": (*View).SelectWordLeft,
39 "SelectToStartOfLine": (*View).SelectToStartOfLine,
40 "SelectToEndOfLine": (*View).SelectToEndOfLine,
41 "InsertEnter": (*View).InsertEnter,
42 "InsertSpace": (*View).InsertSpace,
43 "Backspace": (*View).Backspace,
44 "Delete": (*View).Delete,
45 "InsertTab": (*View).InsertTab,
48 "FindNext": (*View).FindNext,
49 "FindPrevious": (*View).FindPrevious,
54 "CutLine": (*View).CutLine,
55 "Paste": (*View).Paste,
56 "SelectAll": (*View).SelectAll,
57 "OpenFile": (*View).OpenFile,
58 "Start": (*View).Start,
60 "PageUp": (*View).PageUp,
61 "PageDown": (*View).PageDown,
62 "HalfPageUp": (*View).HalfPageUp,
63 "HalfPageDown": (*View).HalfPageDown,
64 "StartOfLine": (*View).StartOfLine,
65 "EndOfLine": (*View).EndOfLine,
66 "ToggleRuler": (*View).ToggleRuler,
67 "JumpLine": (*View).JumpLine,
68 "ClearStatus": (*View).ClearStatus,
71 keys := map[string]tcell.Key{
73 "Down": tcell.KeyDown,
74 "Right": tcell.KeyRight,
75 "Left": tcell.KeyLeft,
76 "AltUp": tcell.KeyAltUp,
77 "AltDown": tcell.KeyAltDown,
78 "AltLeft": tcell.KeyAltLeft,
79 "AltRight": tcell.KeyAltRight,
80 "CtrlUp": tcell.KeyCtrlUp,
81 "CtrlDown": tcell.KeyCtrlDown,
82 "CtrlLeft": tcell.KeyCtrlLeft,
83 "CtrlRight": tcell.KeyCtrlRight,
84 "ShiftUp": tcell.KeyShiftUp,
85 "ShiftDown": tcell.KeyShiftDown,
86 "ShiftLeft": tcell.KeyShiftLeft,
87 "ShiftRight": tcell.KeyShiftRight,
88 "AltShiftUp": tcell.KeyAltShiftUp,
89 "AltShiftDown": tcell.KeyAltShiftDown,
90 "AltShiftLeft": tcell.KeyAltShiftLeft,
91 "AltShiftRight": tcell.KeyAltShiftRight,
92 "CtrlShiftUp": tcell.KeyCtrlShiftUp,
93 "CtrlShiftDown": tcell.KeyCtrlShiftDown,
94 "CtrlShiftLeft": tcell.KeyCtrlShiftLeft,
95 "CtrlShiftRight": tcell.KeyCtrlShiftRight,
96 "UpLeft": tcell.KeyUpLeft,
97 "UpRight": tcell.KeyUpRight,
98 "DownLeft": tcell.KeyDownLeft,
99 "DownRight": tcell.KeyDownRight,
100 "Center": tcell.KeyCenter,
101 "PgUp": tcell.KeyPgUp,
102 "PgDn": tcell.KeyPgDn,
103 "Home": tcell.KeyHome,
105 "Insert": tcell.KeyInsert,
106 "Delete": tcell.KeyDelete,
107 "Help": tcell.KeyHelp,
108 "Exit": tcell.KeyExit,
109 "Clear": tcell.KeyClear,
110 "Cancel": tcell.KeyCancel,
111 "Print": tcell.KeyPrint,
112 "Pause": tcell.KeyPause,
113 "Backtab": tcell.KeyBacktab,
178 "CtrlSpace": tcell.KeyCtrlSpace,
179 "CtrlA": tcell.KeyCtrlA,
180 "CtrlB": tcell.KeyCtrlB,
181 "CtrlC": tcell.KeyCtrlC,
182 "CtrlD": tcell.KeyCtrlD,
183 "CtrlE": tcell.KeyCtrlE,
184 "CtrlF": tcell.KeyCtrlF,
185 "CtrlG": tcell.KeyCtrlG,
186 "CtrlH": tcell.KeyCtrlH,
187 "CtrlI": tcell.KeyCtrlI,
188 "CtrlJ": tcell.KeyCtrlJ,
189 "CtrlK": tcell.KeyCtrlK,
190 "CtrlL": tcell.KeyCtrlL,
191 "CtrlM": tcell.KeyCtrlM,
192 "CtrlN": tcell.KeyCtrlN,
193 "CtrlO": tcell.KeyCtrlO,
194 "CtrlP": tcell.KeyCtrlP,
195 "CtrlQ": tcell.KeyCtrlQ,
196 "CtrlR": tcell.KeyCtrlR,
197 "CtrlS": tcell.KeyCtrlS,
198 "CtrlT": tcell.KeyCtrlT,
199 "CtrlU": tcell.KeyCtrlU,
200 "CtrlV": tcell.KeyCtrlV,
201 "CtrlW": tcell.KeyCtrlW,
202 "CtrlX": tcell.KeyCtrlX,
203 "CtrlY": tcell.KeyCtrlY,
204 "CtrlZ": tcell.KeyCtrlZ,
205 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
206 "CtrlBackslash": tcell.KeyCtrlBackslash,
207 "CtrlRightSq": tcell.KeyCtrlRightSq,
208 "CtrlCarat": tcell.KeyCtrlCarat,
209 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
210 "Backspace": tcell.KeyBackspace,
213 "Escape": tcell.KeyEscape,
214 "Enter": tcell.KeyEnter,
215 "Space": tcell.KeySpace,
216 "Backspace2": tcell.KeyBackspace2,
219 var parsed map[string]string
220 defaults := DefaultBindings()
222 filename := configDir + "/bindings.json"
223 if _, e := os.Stat(filename); e == nil {
224 input, err := ioutil.ReadFile(filename)
226 TermMessage("Error reading bindings.json file: " + err.Error())
230 err = json.Unmarshal(input, &parsed)
232 TermMessage("Error reading bindings.json:", err.Error())
236 for k, v := range defaults {
237 bindings[keys[k]] = actions[v]
239 for k, v := range parsed {
240 bindings[keys[k]] = actions[v]
244 // DefaultBindings returns a map containing micro's default keybindings
245 func DefaultBindings() map[string]string {
246 return map[string]string{
248 "Down": "CursorDown",
249 "Right": "CursorRight",
250 "Left": "CursorLeft",
251 "ShiftUp": "SelectUp",
252 "ShiftDown": "SelectDown",
253 "ShiftLeft": "SelectLeft",
254 "ShiftRight": "SelectRight",
255 "AltLeft": "WordLeft",
256 "AltRight": "WordRight",
257 "AltShiftRight": "SelectWordRight",
258 "AltShiftLeft": "SelectWordLeft",
259 "CtrlLeft": "StartOfLine",
260 "CtrlRight": "EndOfLine",
261 "CtrlShiftLeft": "SelectToStartOfLine",
262 "CtrlShiftRight": "SelectToEndOfLine",
263 "CtrlUp": "CursorStart",
264 "CtrlDown": "CursorEnd",
265 "CtrlShiftUp": "SelectToStart",
266 "CtrlShiftDown": "SelectToEnd",
267 "Enter": "InsertEnter",
268 "Space": "InsertSpace",
269 "Backspace": "Backspace",
270 "Backspace2": "Backspace",
276 "CtrlP": "FindPrevious",
283 "CtrlA": "SelectAll",
288 "CtrlU": "HalfPageUp",
289 "CtrlD": "HalfPageDown",
290 "CtrlR": "ToggleRuler",
293 "Esc": "ClearStatus",
297 // CursorUp moves the cursor up
298 func (v *View) CursorUp() bool {
299 if v.Cursor.HasSelection() {
300 v.Cursor.SetLoc(v.Cursor.curSelection[0])
301 v.Cursor.ResetSelection()
307 // CursorDown moves the cursor down
308 func (v *View) CursorDown() bool {
309 if v.Cursor.HasSelection() {
310 v.Cursor.SetLoc(v.Cursor.curSelection[1])
311 v.Cursor.ResetSelection()
317 // CursorLeft moves the cursor left
318 func (v *View) CursorLeft() bool {
319 if v.Cursor.HasSelection() {
320 v.Cursor.SetLoc(v.Cursor.curSelection[0])
321 v.Cursor.ResetSelection()
328 // CursorRight moves the cursor right
329 func (v *View) CursorRight() bool {
330 if v.Cursor.HasSelection() {
331 v.Cursor.SetLoc(v.Cursor.curSelection[1] - 1)
332 v.Cursor.ResetSelection()
339 // WordRight moves the cursor one word to the right
340 func (v *View) WordRight() bool {
345 // WordLeft moves the cursor one word to the left
346 func (v *View) WordLeft() bool {
351 // SelectUp selects up one line
352 func (v *View) SelectUp() bool {
353 loc := v.Cursor.Loc()
354 if !v.Cursor.HasSelection() {
355 v.Cursor.origSelection[0] = loc
358 v.Cursor.SelectTo(v.Cursor.Loc())
362 // SelectDown selects down one line
363 func (v *View) SelectDown() bool {
364 loc := v.Cursor.Loc()
365 if !v.Cursor.HasSelection() {
366 v.Cursor.origSelection[0] = loc
369 v.Cursor.SelectTo(v.Cursor.Loc())
373 // SelectLeft selects the character to the left of the cursor
374 func (v *View) SelectLeft() bool {
375 loc := v.Cursor.Loc()
376 count := v.Buf.Len() - 1
380 if !v.Cursor.HasSelection() {
381 v.Cursor.origSelection[0] = loc
384 v.Cursor.SelectTo(v.Cursor.Loc())
388 // SelectRight selects the character to the right of the cursor
389 func (v *View) SelectRight() bool {
390 loc := v.Cursor.Loc()
391 count := v.Buf.Len() - 1
395 if !v.Cursor.HasSelection() {
396 v.Cursor.origSelection[0] = loc
399 v.Cursor.SelectTo(v.Cursor.Loc())
403 // SelectWordRight selects the word to the right of the cursor
404 func (v *View) SelectWordRight() bool {
405 loc := v.Cursor.Loc()
406 if !v.Cursor.HasSelection() {
407 v.Cursor.origSelection[0] = loc
410 v.Cursor.SelectTo(v.Cursor.Loc())
414 // SelectWordLeft selects the word to the left of the cursor
415 func (v *View) SelectWordLeft() bool {
416 loc := v.Cursor.Loc()
417 if !v.Cursor.HasSelection() {
418 v.Cursor.origSelection[0] = loc
421 v.Cursor.SelectTo(v.Cursor.Loc())
425 // StartOfLine moves the cursor to the start of the line
426 func (v *View) StartOfLine() bool {
431 // EndOfLine moves the cursor to the end of the line
432 func (v *View) EndOfLine() bool {
437 // SelectToStartOfLine selects to the start of the current line
438 func (v *View) SelectToStartOfLine() bool {
439 loc := v.Cursor.Loc()
440 if !v.Cursor.HasSelection() {
441 v.Cursor.origSelection[0] = loc
444 v.Cursor.SelectTo(v.Cursor.Loc())
448 // SelectToEndOfLine selects to the end of the current line
449 func (v *View) SelectToEndOfLine() bool {
450 loc := v.Cursor.Loc()
451 if !v.Cursor.HasSelection() {
452 v.Cursor.origSelection[0] = loc
455 v.Cursor.SelectTo(v.Cursor.Loc())
459 // CursorStart moves the cursor to the start of the buffer
460 func (v *View) CursorStart() bool {
466 // CursorEnd moves the cursor to the end of the buffer
467 func (v *View) CursorEnd() bool {
468 v.Cursor.SetLoc(v.Buf.Len())
472 // SelectToStart selects the text from the cursor to the start of the buffer
473 func (v *View) SelectToStart() bool {
474 loc := v.Cursor.Loc()
475 if !v.Cursor.HasSelection() {
476 v.Cursor.origSelection[0] = loc
483 // SelectToEnd selects the text from the cursor to the end of the buffer
484 func (v *View) SelectToEnd() bool {
485 loc := v.Cursor.Loc()
486 if !v.Cursor.HasSelection() {
487 v.Cursor.origSelection[0] = loc
490 v.Cursor.SelectTo(v.Buf.Len())
494 // InsertSpace inserts a space
495 func (v *View) InsertSpace() bool {
497 if v.Cursor.HasSelection() {
498 v.Cursor.DeleteSelection()
499 v.Cursor.ResetSelection()
501 v.eh.Insert(v.Cursor.Loc(), " ")
506 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
507 func (v *View) InsertEnter() bool {
509 if v.Cursor.HasSelection() {
510 v.Cursor.DeleteSelection()
511 v.Cursor.ResetSelection()
514 v.eh.Insert(v.Cursor.Loc(), "\n")
515 ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.y])
518 if settings["autoindent"].(bool) {
519 v.eh.Insert(v.Cursor.Loc(), ws)
520 for i := 0; i < len(ws); i++ {
524 v.Cursor.lastVisualX = v.Cursor.GetVisualX()
528 // Backspace deletes the previous character
529 func (v *View) Backspace() bool {
530 // Delete a character
531 if v.Cursor.HasSelection() {
532 v.Cursor.DeleteSelection()
533 v.Cursor.ResetSelection()
534 } else if v.Cursor.Loc() > 0 {
535 // We have to do something a bit hacky here because we want to
536 // delete the line by first moving left and then deleting backwards
537 // but the undo redo would place the cursor in the wrong place
538 // So instead we move left, save the position, move back, delete
539 // and restore the position
541 // If the user is using spaces instead of tabs and they are deleting
542 // whitespace at the start of the line, we should delete as if its a
543 // tab (tabSize number of spaces)
544 lineStart := v.Buf.Lines[v.Cursor.y][:v.Cursor.x]
545 tabSize := int(settings["tabsize"].(float64))
546 if settings["tabsToSpaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
547 loc := v.Cursor.Loc()
548 v.Cursor.SetLoc(loc - tabSize)
549 cx, cy := v.Cursor.x, v.Cursor.y
551 v.eh.Remove(loc-tabSize, loc)
552 v.Cursor.x, v.Cursor.y = cx, cy
555 cx, cy := v.Cursor.x, v.Cursor.y
557 loc := v.Cursor.Loc()
558 v.eh.Remove(loc-1, loc)
559 v.Cursor.x, v.Cursor.y = cx, cy
562 v.Cursor.lastVisualX = v.Cursor.GetVisualX()
566 // Delete deletes the next character
567 func (v *View) Delete() bool {
568 if v.Cursor.HasSelection() {
569 v.Cursor.DeleteSelection()
570 v.Cursor.ResetSelection()
572 loc := v.Cursor.Loc()
573 if loc < v.Buf.Len() {
574 v.eh.Remove(loc, loc+1)
580 // InsertTab inserts a tab or spaces
581 func (v *View) InsertTab() bool {
583 if v.Cursor.HasSelection() {
584 v.Cursor.DeleteSelection()
585 v.Cursor.ResetSelection()
587 if settings["tabsToSpaces"].(bool) {
588 tabSize := int(settings["tabsize"].(float64))
589 v.eh.Insert(v.Cursor.Loc(), Spaces(tabSize))
590 for i := 0; i < tabSize; i++ {
594 v.eh.Insert(v.Cursor.Loc(), "\t")
600 // Save the buffer to disk
601 func (v *View) Save() bool {
602 // If this is an empty buffer, ask for a filename
603 if v.Buf.Path == "" {
604 filename, canceled := messenger.Prompt("Filename: ")
606 v.Buf.Path = filename
607 v.Buf.Name = filename
614 messenger.Error(err.Error())
616 messenger.Message("Saved " + v.Buf.Path)
621 // Find opens a prompt and searches forward for the input
622 func (v *View) Find() bool {
623 if v.Cursor.HasSelection() {
624 searchStart = v.Cursor.curSelection[1]
626 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
632 // FindNext searches forwards for the last used search term
633 func (v *View) FindNext() bool {
634 if v.Cursor.HasSelection() {
635 searchStart = v.Cursor.curSelection[1]
637 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
639 messenger.Message("Finding: " + lastSearch)
640 Search(lastSearch, v, true)
644 // FindPrevious searches backwards for the last used search term
645 func (v *View) FindPrevious() bool {
646 if v.Cursor.HasSelection() {
647 searchStart = v.Cursor.curSelection[0]
649 searchStart = ToCharPos(v.Cursor.x, v.Cursor.y, v.Buf)
651 messenger.Message("Finding: " + lastSearch)
652 Search(lastSearch, v, false)
656 // Undo undoes the last action
657 func (v *View) Undo() bool {
662 // Redo redoes the last action
663 func (v *View) Redo() bool {
668 // Copy the selection to the system clipboard
669 func (v *View) Copy() bool {
670 if v.Cursor.HasSelection() {
671 clipboard.WriteAll(v.Cursor.GetSelection())
673 messenger.Message("Copied selection to clipboard")
678 // CutLine cuts the current line to the clipboard
679 func (v *View) CutLine() bool {
680 v.Cursor.SelectLine()
681 if v.freshClip == true {
683 if v.Cursor.HasSelection() {
684 if clip, err := clipboard.ReadAll(); err != nil {
687 clipboard.WriteAll(clip + v.Cursor.GetSelection())
690 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
694 v.lastCutTime = time.Now()
695 v.Cursor.DeleteSelection()
696 v.Cursor.ResetSelection()
700 // Cut the selection to the system clipboard
701 func (v *View) Cut() bool {
702 if v.Cursor.HasSelection() {
703 clipboard.WriteAll(v.Cursor.GetSelection())
704 v.Cursor.DeleteSelection()
705 v.Cursor.ResetSelection()
711 // Paste whatever is in the system clipboard into the buffer
712 // Delete and paste if the user has a selection
713 func (v *View) Paste() bool {
714 if v.Cursor.HasSelection() {
715 v.Cursor.DeleteSelection()
716 v.Cursor.ResetSelection()
718 clip, _ := clipboard.ReadAll()
719 v.eh.Insert(v.Cursor.Loc(), clip)
720 v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
725 // SelectAll selects the entire buffer
726 func (v *View) SelectAll() bool {
727 v.Cursor.curSelection[0] = 0
728 v.Cursor.curSelection[1] = v.Buf.Len()
729 // Put the cursor at the beginning
735 // OpenFile opens a new file in the buffer
736 func (v *View) OpenFile() bool {
737 if v.CanClose("Continue? (yes, no, save) ") {
738 filename, canceled := messenger.Prompt("File to open: ")
742 home, _ := homedir.Dir()
743 filename = strings.Replace(filename, "~", home, 1)
744 file, err := ioutil.ReadFile(filename)
747 messenger.Error(err.Error())
750 buf := NewBuffer(string(file), filename)
756 // Start moves the viewport to the start of the buffer
757 func (v *View) Start() bool {
762 // End moves the viewport to the end of the buffer
763 func (v *View) End() bool {
764 if v.height > v.Buf.NumLines {
767 v.Topline = v.Buf.NumLines - v.height
772 // PageUp scrolls the view up a page
773 func (v *View) PageUp() bool {
774 if v.Topline > v.height {
782 // PageDown scrolls the view down a page
783 func (v *View) PageDown() bool {
784 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
785 v.ScrollDown(v.height)
786 } else if v.Buf.NumLines >= v.height {
787 v.Topline = v.Buf.NumLines - v.height
792 // HalfPageUp scrolls the view up half a page
793 func (v *View) HalfPageUp() bool {
794 if v.Topline > v.height/2 {
795 v.ScrollUp(v.height / 2)
802 // HalfPageDown scrolls the view down half a page
803 func (v *View) HalfPageDown() bool {
804 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
805 v.ScrollDown(v.height / 2)
807 if v.Buf.NumLines >= v.height {
808 v.Topline = v.Buf.NumLines - v.height
814 // ToggleRuler turns line numbers off and on
815 func (v *View) ToggleRuler() bool {
816 if settings["ruler"] == false {
817 settings["ruler"] = true
819 settings["ruler"] = false
824 // JumpLine jumps to a line and moves the view accordingly.
825 func (v *View) JumpLine() bool {
826 // Prompt for line number
827 linestring, canceled := messenger.Prompt("Jump to line # ")
831 lineint, err := strconv.Atoi(linestring)
832 lineint = lineint - 1 // fix offset
834 messenger.Error(err) // return errors
837 // Move cursor and view if possible.
838 if lineint < v.Buf.NumLines {
843 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
847 // ClearStatus clears the messenger bar
848 func (v *View) ClearStatus() bool {
849 messenger.Message("")