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 "SelectLeft": (*View).SelectLeft,
33 "SelectRight": (*View).SelectRight,
34 "WordRight": (*View).WordRight,
35 "WordLeft": (*View).WordLeft,
36 "SelectWordRight": (*View).SelectWordRight,
37 "SelectWordLeft": (*View).SelectWordLeft,
38 "SelectToStartOfLine": (*View).SelectToStartOfLine,
39 "SelectToEndOfLine": (*View).SelectToEndOfLine,
40 "InsertEnter": (*View).InsertEnter,
41 "InsertSpace": (*View).InsertSpace,
42 "Backspace": (*View).Backspace,
43 "Delete": (*View).Delete,
44 "InsertTab": (*View).InsertTab,
47 "FindNext": (*View).FindNext,
48 "FindPrevious": (*View).FindPrevious,
53 "CutLine": (*View).CutLine,
54 "Paste": (*View).Paste,
55 "SelectAll": (*View).SelectAll,
56 "OpenFile": (*View).OpenFile,
57 "Start": (*View).Start,
59 "PageUp": (*View).PageUp,
60 "PageDown": (*View).PageDown,
61 "HalfPageUp": (*View).HalfPageUp,
62 "HalfPageDown": (*View).HalfPageDown,
63 "StartOfLine": (*View).StartOfLine,
64 "EndOfLine": (*View).EndOfLine,
65 "ToggleRuler": (*View).ToggleRuler,
68 keys := map[string]tcell.Key{
70 "Down": tcell.KeyDown,
71 "Right": tcell.KeyRight,
72 "Left": tcell.KeyLeft,
73 "AltUp": tcell.KeyAltUp,
74 "AltDown": tcell.KeyAltDown,
75 "AltLeft": tcell.KeyAltLeft,
76 "AltRight": tcell.KeyAltRight,
77 "CtrlUp": tcell.KeyCtrlUp,
78 "CtrlDown": tcell.KeyCtrlDown,
79 "CtrlLeft": tcell.KeyCtrlLeft,
80 "CtrlRight": tcell.KeyCtrlRight,
81 "ShiftUp": tcell.KeyShiftUp,
82 "ShiftDown": tcell.KeyShiftDown,
83 "ShiftLeft": tcell.KeyShiftLeft,
84 "ShiftRight": tcell.KeyShiftRight,
85 "AltShiftUp": tcell.KeyAltShiftUp,
86 "AltShiftDown": tcell.KeyAltShiftDown,
87 "AltShiftLeft": tcell.KeyAltShiftLeft,
88 "AltShiftRight": tcell.KeyAltShiftRight,
89 "CtrlShiftUp": tcell.KeyCtrlShiftUp,
90 "CtrlShiftDown": tcell.KeyCtrlShiftDown,
91 "CtrlShiftLeft": tcell.KeyCtrlShiftLeft,
92 "CtrlShiftRight": tcell.KeyCtrlShiftRight,
93 "UpLeft": tcell.KeyUpLeft,
94 "UpRight": tcell.KeyUpRight,
95 "DownLeft": tcell.KeyDownLeft,
96 "DownRight": tcell.KeyDownRight,
97 "Center": tcell.KeyCenter,
98 "PgUp": tcell.KeyPgUp,
99 "PgDn": tcell.KeyPgDn,
100 "Home": tcell.KeyHome,
102 "Insert": tcell.KeyInsert,
103 "Delete": tcell.KeyDelete,
104 "Help": tcell.KeyHelp,
105 "Exit": tcell.KeyExit,
106 "Clear": tcell.KeyClear,
107 "Cancel": tcell.KeyCancel,
108 "Print": tcell.KeyPrint,
109 "Pause": tcell.KeyPause,
110 "Backtab": tcell.KeyBacktab,
175 "CtrlSpace": tcell.KeyCtrlSpace,
176 "CtrlA": tcell.KeyCtrlA,
177 "CtrlB": tcell.KeyCtrlB,
178 "CtrlC": tcell.KeyCtrlC,
179 "CtrlD": tcell.KeyCtrlD,
180 "CtrlE": tcell.KeyCtrlE,
181 "CtrlF": tcell.KeyCtrlF,
182 "CtrlG": tcell.KeyCtrlG,
183 "CtrlH": tcell.KeyCtrlH,
184 "CtrlI": tcell.KeyCtrlI,
185 "CtrlJ": tcell.KeyCtrlJ,
186 "CtrlK": tcell.KeyCtrlK,
187 "CtrlL": tcell.KeyCtrlL,
188 "CtrlM": tcell.KeyCtrlM,
189 "CtrlN": tcell.KeyCtrlN,
190 "CtrlO": tcell.KeyCtrlO,
191 "CtrlP": tcell.KeyCtrlP,
192 "CtrlQ": tcell.KeyCtrlQ,
193 "CtrlR": tcell.KeyCtrlR,
194 "CtrlS": tcell.KeyCtrlS,
195 "CtrlT": tcell.KeyCtrlT,
196 "CtrlU": tcell.KeyCtrlU,
197 "CtrlV": tcell.KeyCtrlV,
198 "CtrlW": tcell.KeyCtrlW,
199 "CtrlX": tcell.KeyCtrlX,
200 "CtrlY": tcell.KeyCtrlY,
201 "CtrlZ": tcell.KeyCtrlZ,
202 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
203 "CtrlBackslash": tcell.KeyCtrlBackslash,
204 "CtrlRightSq": tcell.KeyCtrlRightSq,
205 "CtrlCarat": tcell.KeyCtrlCarat,
206 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
207 "Backspace": tcell.KeyBackspace,
210 "Escape": tcell.KeyEscape,
211 "Enter": tcell.KeyEnter,
212 "Space": tcell.KeySpace,
213 "Backspace2": tcell.KeyBackspace2,
216 var parsed map[string]string
217 defaults := DefaultBindings()
219 filename := configDir + "/bindings.json"
220 if _, e := os.Stat(filename); e == nil {
221 input, err := ioutil.ReadFile(filename)
223 TermMessage("Error reading bindings.json file: " + err.Error())
227 err = json.Unmarshal(input, &parsed)
229 TermMessage("Error reading bindings.json:", err.Error())
233 for k, v := range defaults {
234 bindings[keys[k]] = actions[v]
236 for k, v := range parsed {
237 bindings[keys[k]] = actions[v]
241 // DefaultBindings returns a map containing micro's default keybindings
242 func DefaultBindings() map[string]string {
243 return map[string]string{
245 "Down": "CursorDown",
246 "Right": "CursorRight",
247 "Left": "CursorLeft",
248 "ShiftLeft": "SelectLeft",
249 "ShiftRight": "SelectRight",
250 "AltLeft": "WordLeft",
251 "AltRight": "WordRight",
252 "AltShiftRight": "SelectWordRight",
253 "AltShiftLeft": "SelectWordLeft",
254 "CtrlLeft": "StartOfLine",
255 "CtrlRight": "EndOfLine",
256 "CtrlShiftLeft": "SelectToStartOfLine",
257 "CtrlShiftRight": "SelectToEndOfLine",
258 "CtrlUp": "CursorStart",
259 "CtrlDown": "CursorEnd",
260 "CtrlShiftUp": "SelectToStart",
261 "CtrlShiftDown": "SelectToEnd",
262 "Enter": "InsertEnter",
263 "Space": "InsertSpace",
264 "Backspace": "Backspace",
265 "Backspace2": "Backspace",
271 "CtrlP": "FindPrevious",
278 "CtrlA": "SelectAll",
283 "CtrlU": "HalfPageUp",
284 "CtrlD": "HalfPageDown",
285 "CtrlR": "ToggleRuler",
290 // CursorUp moves the cursor up
291 func (v *View) CursorUp() bool {
292 v.cursor.ResetSelection()
297 // CursorDown moves the cursor down
298 func (v *View) CursorDown() bool {
299 v.cursor.ResetSelection()
304 // CursorLeft moves the cursor left
305 func (v *View) CursorLeft() bool {
306 if v.cursor.HasSelection() {
307 v.cursor.SetLoc(v.cursor.curSelection[0])
308 v.cursor.ResetSelection()
315 // CursorRight moves the cursor right
316 func (v *View) CursorRight() bool {
317 if v.cursor.HasSelection() {
318 v.cursor.SetLoc(v.cursor.curSelection[1] - 1)
319 v.cursor.ResetSelection()
326 // WordRight moves the cursor one word to the right
327 func (v *View) WordRight() bool {
332 // WordLeft moves the cursor one word to the left
333 func (v *View) WordLeft() bool {
338 // SelectLeft selects the character to the left of the cursor
339 func (v *View) SelectLeft() bool {
340 loc := v.cursor.Loc()
341 if !v.cursor.HasSelection() {
342 v.cursor.origSelection[0] = loc
344 v.cursor.SelectTo(loc - 1)
349 // SelectRight selects the character to the right of the cursor
350 func (v *View) SelectRight() bool {
351 loc := v.cursor.Loc()
352 if !v.cursor.HasSelection() {
353 v.cursor.origSelection[0] = loc
355 v.cursor.SelectTo(loc + 1)
360 // SelectWordRight selects the word to the right of the cursor
361 func (v *View) SelectWordRight() bool {
362 loc := v.cursor.Loc()
363 if !v.cursor.HasSelection() {
364 v.cursor.origSelection[0] = loc
367 v.cursor.SelectTo(v.cursor.Loc())
371 // SelectWordLeft selects the word to the left of the cursor
372 func (v *View) SelectWordLeft() bool {
373 loc := v.cursor.Loc()
374 if !v.cursor.HasSelection() {
375 v.cursor.origSelection[0] = loc
378 v.cursor.SelectTo(v.cursor.Loc())
382 // StartOfLine moves the cursor to the start of the line
383 func (v *View) StartOfLine() bool {
388 // EndOfLine moves the cursor to the end of the line
389 func (v *View) EndOfLine() bool {
394 // SelectToStartOfLine selects to the start of the current line
395 func (v *View) SelectToStartOfLine() bool {
396 loc := v.cursor.Loc()
397 if !v.cursor.HasSelection() {
398 v.cursor.origSelection[0] = loc
401 v.cursor.SelectTo(v.cursor.Loc())
405 // SelectToEndOfLine selects to the end of the current line
406 func (v *View) SelectToEndOfLine() bool {
407 loc := v.cursor.Loc()
408 if !v.cursor.HasSelection() {
409 v.cursor.origSelection[0] = loc
412 v.cursor.SelectTo(v.cursor.Loc())
416 // CursorStart moves the cursor to the start of the buffer
417 func (v *View) CursorStart() bool {
423 // CursorEnd moves the cursor to the end of the buffer
424 func (v *View) CursorEnd() bool {
425 v.cursor.SetLoc(len(v.buf.text))
429 // SelectToStart selects the text from the cursor to the start of the buffer
430 func (v *View) SelectToStart() bool {
431 loc := v.cursor.Loc()
432 if !v.cursor.HasSelection() {
433 v.cursor.origSelection[0] = loc
440 // SelectToEnd selects the text from the cursor to the end of the buffer
441 func (v *View) SelectToEnd() bool {
442 loc := v.cursor.Loc()
443 if !v.cursor.HasSelection() {
444 v.cursor.origSelection[0] = loc
447 v.cursor.SelectTo(len(v.buf.text))
451 // InsertSpace inserts a space
452 func (v *View) InsertSpace() bool {
454 if v.cursor.HasSelection() {
455 v.cursor.DeleteSelection()
456 v.cursor.ResetSelection()
458 v.eh.Insert(v.cursor.Loc(), " ")
463 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
464 func (v *View) InsertEnter() bool {
466 if v.cursor.HasSelection() {
467 v.cursor.DeleteSelection()
468 v.cursor.ResetSelection()
471 v.eh.Insert(v.cursor.Loc(), "\n")
472 ws := GetLeadingWhitespace(v.buf.lines[v.cursor.y])
475 if settings.AutoIndent {
476 v.eh.Insert(v.cursor.Loc(), ws)
477 for i := 0; i < len(ws); i++ {
481 v.cursor.lastVisualX = v.cursor.GetVisualX()
485 // Backspace deletes the previous character
486 func (v *View) Backspace() bool {
487 // Delete a character
488 if v.cursor.HasSelection() {
489 v.cursor.DeleteSelection()
490 v.cursor.ResetSelection()
491 } else if v.cursor.Loc() > 0 {
492 // We have to do something a bit hacky here because we want to
493 // delete the line by first moving left and then deleting backwards
494 // but the undo redo would place the cursor in the wrong place
495 // So instead we move left, save the position, move back, delete
496 // and restore the position
498 // If the user is using spaces instead of tabs and they are deleting
499 // whitespace at the start of the line, we should delete as if its a
500 // tab (tabSize number of spaces)
501 lineStart := v.buf.lines[v.cursor.y][:v.cursor.x]
502 if settings.TabsToSpaces && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%settings.TabSize == 0 {
503 loc := v.cursor.Loc()
504 v.cursor.SetLoc(loc - settings.TabSize)
505 cx, cy := v.cursor.x, v.cursor.y
507 v.eh.Remove(loc-settings.TabSize, loc)
508 v.cursor.x, v.cursor.y = cx, cy
511 cx, cy := v.cursor.x, v.cursor.y
513 loc := v.cursor.Loc()
514 v.eh.Remove(loc-1, loc)
515 v.cursor.x, v.cursor.y = cx, cy
518 v.cursor.lastVisualX = v.cursor.GetVisualX()
522 // Delete deletes the next character
523 func (v *View) Delete() bool {
524 if v.cursor.HasSelection() {
525 v.cursor.DeleteSelection()
526 v.cursor.ResetSelection()
528 loc := v.cursor.Loc()
529 if loc < len(v.buf.text) {
530 v.eh.Remove(loc, loc+1)
536 // InsertTab inserts a tab or spaces
537 func (v *View) InsertTab() bool {
539 if v.cursor.HasSelection() {
540 v.cursor.DeleteSelection()
541 v.cursor.ResetSelection()
543 if settings.TabsToSpaces {
544 v.eh.Insert(v.cursor.Loc(), Spaces(settings.TabSize))
545 for i := 0; i < settings.TabSize; i++ {
549 v.eh.Insert(v.cursor.Loc(), "\t")
555 // Save the buffer to disk
556 func (v *View) Save() bool {
557 // If this is an empty buffer, ask for a filename
558 if v.buf.path == "" {
559 filename, canceled := messenger.Prompt("Filename: ")
561 v.buf.path = filename
562 v.buf.name = filename
569 messenger.Error(err.Error())
571 messenger.Message("Saved " + v.buf.path)
572 switch v.buf.filetype {
580 // GoSave saves the current file (must be a go file) and runs goimports or gofmt
581 // depending on the user's configuration
582 func (v *View) GoSave() {
583 if settings.GoImports == true {
584 messenger.Message("Running goimports...")
585 err := goimports(v.buf.path)
589 messenger.Message("Saved " + v.buf.path)
592 } else if settings.GoFmt == true {
593 messenger.Message("Running gofmt...")
594 err := gofmt(v.buf.path)
598 messenger.Message("Saved " + v.buf.path)
607 // Find opens a prompt and searches forward for the input
608 func (v *View) Find() bool {
609 if v.cursor.HasSelection() {
610 searchStart = v.cursor.curSelection[1]
612 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
618 // FindNext searches forwards for the last used search term
619 func (v *View) FindNext() bool {
620 if v.cursor.HasSelection() {
621 searchStart = v.cursor.curSelection[1]
623 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
625 messenger.Message("Find: " + lastSearch)
626 Search(lastSearch, v, true)
630 // FindPrevious searches backwards for the last used search term
631 func (v *View) FindPrevious() bool {
632 if v.cursor.HasSelection() {
633 searchStart = v.cursor.curSelection[0]
635 searchStart = ToCharPos(v.cursor.x, v.cursor.y, v.buf)
637 messenger.Message("Find: " + lastSearch)
638 Search(lastSearch, v, false)
642 // Undo undoes the last action
643 func (v *View) Undo() bool {
648 // Redo redoes the last action
649 func (v *View) Redo() bool {
654 // Copy the selection to the system clipboard
655 func (v *View) Copy() bool {
656 if v.cursor.HasSelection() {
657 clipboard.WriteAll(v.cursor.GetSelection())
663 // CutLine cuts the current line to the clipboard
664 func (v *View) CutLine() bool {
665 v.cursor.SelectLine()
666 if v.freshClip == true {
668 if v.cursor.HasSelection() {
669 if clip, err := clipboard.ReadAll(); err != nil {
672 clipboard.WriteAll(clip + v.cursor.GetSelection())
675 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
679 v.lastCutTime = time.Now()
680 v.cursor.DeleteSelection()
681 v.cursor.ResetSelection()
685 // Cut the selection to the system clipboard
686 func (v *View) Cut() bool {
687 if v.cursor.HasSelection() {
688 clipboard.WriteAll(v.cursor.GetSelection())
689 v.cursor.DeleteSelection()
690 v.cursor.ResetSelection()
696 // Paste whatever is in the system clipboard into the buffer
697 // Delete and paste if the user has a selection
698 func (v *View) Paste() bool {
699 if v.cursor.HasSelection() {
700 v.cursor.DeleteSelection()
701 v.cursor.ResetSelection()
703 clip, _ := clipboard.ReadAll()
704 v.eh.Insert(v.cursor.Loc(), clip)
705 v.cursor.SetLoc(v.cursor.Loc() + Count(clip))
710 // SelectAll selects the entire buffer
711 func (v *View) SelectAll() bool {
712 v.cursor.curSelection[1] = 0
713 v.cursor.curSelection[0] = v.buf.Len()
714 // Put the cursor at the beginning
720 // OpenFile opens a new file in the buffer
721 func (v *View) OpenFile() bool {
722 if v.CanClose("Continue? (yes, no, save) ") {
723 filename, canceled := messenger.Prompt("File to open: ")
727 home, _ := homedir.Dir()
728 filename = strings.Replace(filename, "~", home, 1)
729 file, err := ioutil.ReadFile(filename)
732 messenger.Error(err.Error())
735 buf := NewBuffer(string(file), filename)
741 // Start moves the viewport to the start of the buffer
742 func (v *View) Start() bool {
747 // End moves the viewport to the end of the buffer
748 func (v *View) End() bool {
749 if v.height > len(v.buf.lines) {
752 v.topline = len(v.buf.lines) - v.height
757 // PageUp scrolls the view up a page
758 func (v *View) PageUp() bool {
759 if v.topline > v.height {
767 // PageDown scrolls the view down a page
768 func (v *View) PageDown() bool {
769 if len(v.buf.lines)-(v.topline+v.height) > v.height {
770 v.ScrollDown(v.height)
771 } else if len(v.buf.lines) >= v.height {
772 v.topline = len(v.buf.lines) - v.height
777 // HalfPageUp scrolls the view up half a page
778 func (v *View) HalfPageUp() bool {
779 if v.topline > v.height/2 {
780 v.ScrollUp(v.height / 2)
787 // HalfPageDown scrolls the view down half a page
788 func (v *View) HalfPageDown() bool {
789 if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
790 v.ScrollDown(v.height / 2)
792 if len(v.buf.lines) >= v.height {
793 v.topline = len(v.buf.lines) - v.height
799 // ToggleRuler turns line numbers off and on
800 func (v *View) ToggleRuler() bool {
801 if settings.Ruler == false {
802 settings.Ruler = true
804 settings.Ruler = false
814 // gofmt runs gofmt on a file
815 func gofmt(file string) error {
816 cmd := exec.Command("gofmt", "-w", file)
820 return errors.New("Check syntax ") //TODO: highlight or display locations
825 // goimports runs goimports on a file
826 func goimports(file string) error {
827 cmd := exec.Command("goimports", "-w", file)
831 return errors.New("Check syntax ") //TODO: highlight or display locations