11 "github.com/mitchellh/go-homedir"
12 "github.com/zyedidia/clipboard"
13 "github.com/zyedidia/tcell"
16 var bindings map[Key][]func(*View) bool
17 var helpBinding string
19 var bindingActions = map[string]func(*View) bool{
20 "CursorUp": (*View).CursorUp,
21 "CursorDown": (*View).CursorDown,
22 "CursorPageUp": (*View).CursorPageUp,
23 "CursorPageDown": (*View).CursorPageDown,
24 "CursorLeft": (*View).CursorLeft,
25 "CursorRight": (*View).CursorRight,
26 "CursorStart": (*View).CursorStart,
27 "CursorEnd": (*View).CursorEnd,
28 "SelectToStart": (*View).SelectToStart,
29 "SelectToEnd": (*View).SelectToEnd,
30 "SelectUp": (*View).SelectUp,
31 "SelectDown": (*View).SelectDown,
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 "DeleteWordRight": (*View).DeleteWordRight,
39 "DeleteWordLeft": (*View).DeleteWordLeft,
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 "DuplicateLine": (*View).DuplicateLine,
57 "DeleteLine": (*View).DeleteLine,
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 "ToggleHelp": (*View).ToggleHelp,
70 "ToggleRuler": (*View).ToggleRuler,
71 "JumpLine": (*View).JumpLine,
72 "ClearStatus": (*View).ClearStatus,
73 "ShellMode": (*View).ShellMode,
74 "CommandMode": (*View).CommandMode,
76 "AddTab": (*View).AddTab,
77 "PreviousTab": (*View).PreviousTab,
78 "NextTab": (*View).NextTab,
81 var bindingKeys = map[string]tcell.Key{
83 "Down": tcell.KeyDown,
84 "Right": tcell.KeyRight,
85 "Left": tcell.KeyLeft,
86 "UpLeft": tcell.KeyUpLeft,
87 "UpRight": tcell.KeyUpRight,
88 "DownLeft": tcell.KeyDownLeft,
89 "DownRight": tcell.KeyDownRight,
90 "Center": tcell.KeyCenter,
91 "PageUp": tcell.KeyPgUp,
92 "PageDown": tcell.KeyPgDn,
93 "Home": tcell.KeyHome,
95 "Insert": tcell.KeyInsert,
96 "Delete": tcell.KeyDelete,
97 "Help": tcell.KeyHelp,
98 "Exit": tcell.KeyExit,
99 "Clear": tcell.KeyClear,
100 "Cancel": tcell.KeyCancel,
101 "Print": tcell.KeyPrint,
102 "Pause": tcell.KeyPause,
103 "Backtab": tcell.KeyBacktab,
168 "CtrlSpace": tcell.KeyCtrlSpace,
169 "CtrlA": tcell.KeyCtrlA,
170 "CtrlB": tcell.KeyCtrlB,
171 "CtrlC": tcell.KeyCtrlC,
172 "CtrlD": tcell.KeyCtrlD,
173 "CtrlE": tcell.KeyCtrlE,
174 "CtrlF": tcell.KeyCtrlF,
175 "CtrlG": tcell.KeyCtrlG,
176 "CtrlH": tcell.KeyCtrlH,
177 "CtrlI": tcell.KeyCtrlI,
178 "CtrlJ": tcell.KeyCtrlJ,
179 "CtrlK": tcell.KeyCtrlK,
180 "CtrlL": tcell.KeyCtrlL,
181 "CtrlM": tcell.KeyCtrlM,
182 "CtrlN": tcell.KeyCtrlN,
183 "CtrlO": tcell.KeyCtrlO,
184 "CtrlP": tcell.KeyCtrlP,
185 "CtrlQ": tcell.KeyCtrlQ,
186 "CtrlR": tcell.KeyCtrlR,
187 "CtrlS": tcell.KeyCtrlS,
188 "CtrlT": tcell.KeyCtrlT,
189 "CtrlU": tcell.KeyCtrlU,
190 "CtrlV": tcell.KeyCtrlV,
191 "CtrlW": tcell.KeyCtrlW,
192 "CtrlX": tcell.KeyCtrlX,
193 "CtrlY": tcell.KeyCtrlY,
194 "CtrlZ": tcell.KeyCtrlZ,
195 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
196 "CtrlBackslash": tcell.KeyCtrlBackslash,
197 "CtrlRightSq": tcell.KeyCtrlRightSq,
198 "CtrlCarat": tcell.KeyCtrlCarat,
199 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
200 "Backspace": tcell.KeyBackspace,
203 "Escape": tcell.KeyEscape,
204 "Enter": tcell.KeyEnter,
205 "Backspace2": tcell.KeyBackspace2,
207 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
208 "PgUp": tcell.KeyPgUp,
209 "PgDown": tcell.KeyPgDn,
212 // The Key struct holds the data for a keypress (keycode + modifiers)
215 modifiers tcell.ModMask
219 // InitBindings initializes the keybindings for micro
220 func InitBindings() {
221 bindings = make(map[Key][]func(*View) bool)
223 var parsed map[string]string
224 defaults := DefaultBindings()
226 filename := configDir + "/bindings.json"
227 if _, e := os.Stat(filename); e == nil {
228 input, err := ioutil.ReadFile(filename)
230 TermMessage("Error reading bindings.json file: " + err.Error())
234 err = json.Unmarshal(input, &parsed)
236 TermMessage("Error reading bindings.json:", err.Error())
240 parseBindings(defaults)
241 parseBindings(parsed)
244 func parseBindings(userBindings map[string]string) {
245 for k, v := range userBindings {
250 // findKey will find binding Key 'b' using string 'k'
251 func findKey(k string) (b Key, ok bool) {
252 modifiers := tcell.ModNone
254 // First, we'll strip off all the modifiers in the name and add them to the
259 case strings.HasPrefix(k, "-"):
260 // We optionally support dashes between modifiers
262 case strings.HasPrefix(k, "Ctrl"):
264 modifiers |= tcell.ModCtrl
265 case strings.HasPrefix(k, "Alt"):
267 modifiers |= tcell.ModAlt
268 case strings.HasPrefix(k, "Shift"):
270 modifiers |= tcell.ModShift
276 // Control is handled specially, since some character codes in bindingKeys
277 // are different when Control is depressed. We should check for Control keys
279 if modifiers&tcell.ModCtrl != 0 {
280 // see if the key is in bindingKeys with the Ctrl prefix.
281 if code, ok := bindingKeys["Ctrl"+k]; ok {
282 // It is, we're done.
285 modifiers: modifiers,
291 // See if we can find the key in bindingKeys
292 if code, ok := bindingKeys[k]; ok {
295 modifiers: modifiers,
300 // If we were given one character, then we've got a rune.
303 keyCode: tcell.KeyRune,
304 modifiers: modifiers,
309 // We don't know what happened.
313 // findAction will find 'action' using string 'v'
314 func findAction(v string) (action func(*View) bool) {
315 action, ok := bindingActions[v]
317 // If the user seems to be binding a function that doesn't exist
318 // We hope that it's a lua function that exists and bind it to that
319 action = LuaFunctionBinding(v)
324 // BindKey takes a key and an action and binds the two together
325 func BindKey(k, v string) {
326 key, ok := findKey(k)
330 if v == "ToggleHelp" {
334 actionNames := strings.Split(v, ",")
335 actions := make([]func(*View) bool, 0, len(actionNames))
336 for _, actionName := range actionNames {
337 actions = append(actions, findAction(actionName))
340 bindings[key] = actions
343 // DefaultBindings returns a map containing micro's default keybindings
344 func DefaultBindings() map[string]string {
345 return map[string]string{
347 "Down": "CursorDown",
348 "Right": "CursorRight",
349 "Left": "CursorLeft",
350 "ShiftUp": "SelectUp",
351 "ShiftDown": "SelectDown",
352 "ShiftLeft": "SelectLeft",
353 "ShiftRight": "SelectRight",
354 "AltLeft": "WordLeft",
355 "AltRight": "WordRight",
356 "AltShiftRight": "SelectWordRight",
357 "AltShiftLeft": "SelectWordLeft",
358 "CtrlLeft": "StartOfLine",
359 "CtrlRight": "EndOfLine",
360 "CtrlShiftLeft": "SelectToStartOfLine",
361 "CtrlShiftRight": "SelectToEndOfLine",
362 "CtrlUp": "CursorStart",
363 "CtrlDown": "CursorEnd",
364 "CtrlShiftUp": "SelectToStart",
365 "CtrlShiftDown": "SelectToEnd",
366 "Enter": "InsertEnter",
367 "Space": "InsertSpace",
368 "Backspace": "Backspace",
369 "Backspace2": "Backspace",
370 "Alt-Backspace": "DeleteWordLeft",
371 "Alt-Backspace2": "DeleteWordLeft",
377 "CtrlP": "FindPrevious",
383 "CtrlD": "DuplicateLine",
385 "CtrlA": "SelectAll",
387 "CtrlRightSq": "PreviousTab",
388 "CtrlBackslash": "NextTab",
391 "PageUp": "CursorPageUp",
392 "PageDown": "CursorPageDown",
393 "CtrlG": "ToggleHelp",
394 "CtrlR": "ToggleRuler",
397 "Esc": "ClearStatus",
398 "CtrlB": "ShellMode",
400 "CtrlE": "CommandMode",
402 // Emacs-style keybindings
403 "Alt-f": "WordRight",
405 "Alt-a": "StartOfLine",
406 "Alt-e": "EndOfLine",
408 "Alt-n": "CursorDown",
412 // CursorUp moves the cursor up
413 func (v *View) CursorUp() bool {
414 if v.Cursor.HasSelection() {
415 v.Cursor.Loc = v.Cursor.CurSelection[0]
416 v.Cursor.ResetSelection()
422 // CursorDown moves the cursor down
423 func (v *View) CursorDown() bool {
424 if v.Cursor.HasSelection() {
425 v.Cursor.Loc = v.Cursor.CurSelection[1]
426 v.Cursor.ResetSelection()
432 // CursorLeft moves the cursor left
433 func (v *View) CursorLeft() bool {
434 if v.Cursor.HasSelection() {
435 v.Cursor.Loc = v.Cursor.CurSelection[0]
436 v.Cursor.ResetSelection()
443 // CursorRight moves the cursor right
444 func (v *View) CursorRight() bool {
445 if v.Cursor.HasSelection() {
446 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
447 v.Cursor.ResetSelection()
454 // WordRight moves the cursor one word to the right
455 func (v *View) WordRight() bool {
460 // WordLeft moves the cursor one word to the left
461 func (v *View) WordLeft() bool {
466 // SelectUp selects up one line
467 func (v *View) SelectUp() bool {
468 if !v.Cursor.HasSelection() {
469 v.Cursor.OrigSelection[0] = v.Cursor.Loc
472 v.Cursor.SelectTo(v.Cursor.Loc)
476 // SelectDown selects down one line
477 func (v *View) SelectDown() bool {
478 if !v.Cursor.HasSelection() {
479 v.Cursor.OrigSelection[0] = v.Cursor.Loc
482 v.Cursor.SelectTo(v.Cursor.Loc)
486 // SelectLeft selects the character to the left of the cursor
487 func (v *View) SelectLeft() bool {
489 count := v.Buf.End().Move(-1, v.Buf)
490 if loc.GreaterThan(count) {
493 if !v.Cursor.HasSelection() {
494 v.Cursor.OrigSelection[0] = loc
497 v.Cursor.SelectTo(v.Cursor.Loc)
501 // SelectRight selects the character to the right of the cursor
502 func (v *View) SelectRight() bool {
504 count := v.Buf.End().Move(-1, v.Buf)
505 if loc.GreaterThan(count) {
508 if !v.Cursor.HasSelection() {
509 v.Cursor.OrigSelection[0] = loc
512 v.Cursor.SelectTo(v.Cursor.Loc)
516 // SelectWordRight selects the word to the right of the cursor
517 func (v *View) SelectWordRight() bool {
518 if !v.Cursor.HasSelection() {
519 v.Cursor.OrigSelection[0] = v.Cursor.Loc
522 v.Cursor.SelectTo(v.Cursor.Loc)
526 // SelectWordLeft selects the word to the left of the cursor
527 func (v *View) SelectWordLeft() bool {
528 if !v.Cursor.HasSelection() {
529 v.Cursor.OrigSelection[0] = v.Cursor.Loc
532 v.Cursor.SelectTo(v.Cursor.Loc)
536 // StartOfLine moves the cursor to the start of the line
537 func (v *View) StartOfLine() bool {
542 // EndOfLine moves the cursor to the end of the line
543 func (v *View) EndOfLine() bool {
548 // SelectToStartOfLine selects to the start of the current line
549 func (v *View) SelectToStartOfLine() bool {
550 if !v.Cursor.HasSelection() {
551 v.Cursor.OrigSelection[0] = v.Cursor.Loc
554 v.Cursor.SelectTo(v.Cursor.Loc)
558 // SelectToEndOfLine selects to the end of the current line
559 func (v *View) SelectToEndOfLine() bool {
560 if !v.Cursor.HasSelection() {
561 v.Cursor.OrigSelection[0] = v.Cursor.Loc
564 v.Cursor.SelectTo(v.Cursor.Loc)
568 // CursorStart moves the cursor to the start of the buffer
569 func (v *View) CursorStart() bool {
575 // CursorEnd moves the cursor to the end of the buffer
576 func (v *View) CursorEnd() bool {
577 v.Cursor.Loc = v.Buf.End()
581 // SelectToStart selects the text from the cursor to the start of the buffer
582 func (v *View) SelectToStart() bool {
583 if !v.Cursor.HasSelection() {
584 v.Cursor.OrigSelection[0] = v.Cursor.Loc
587 v.Cursor.SelectTo(v.Buf.Start())
591 // SelectToEnd selects the text from the cursor to the end of the buffer
592 func (v *View) SelectToEnd() bool {
593 if !v.Cursor.HasSelection() {
594 v.Cursor.OrigSelection[0] = v.Cursor.Loc
597 v.Cursor.SelectTo(v.Buf.End())
601 // InsertSpace inserts a space
602 func (v *View) InsertSpace() bool {
603 if v.Cursor.HasSelection() {
604 v.Cursor.DeleteSelection()
605 v.Cursor.ResetSelection()
607 v.Buf.Insert(v.Cursor.Loc, " ")
612 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
613 func (v *View) InsertEnter() bool {
615 if v.Cursor.HasSelection() {
616 v.Cursor.DeleteSelection()
617 v.Cursor.ResetSelection()
620 v.Buf.Insert(v.Cursor.Loc, "\n")
621 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
624 if settings["autoindent"].(bool) {
625 v.Buf.Insert(v.Cursor.Loc, ws)
626 for i := 0; i < len(ws); i++ {
630 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
634 // Backspace deletes the previous character
635 func (v *View) Backspace() bool {
636 // Delete a character
637 if v.Cursor.HasSelection() {
638 v.Cursor.DeleteSelection()
639 v.Cursor.ResetSelection()
640 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
641 // We have to do something a bit hacky here because we want to
642 // delete the line by first moving left and then deleting backwards
643 // but the undo redo would place the cursor in the wrong place
644 // So instead we move left, save the position, move back, delete
645 // and restore the position
647 // If the user is using spaces instead of tabs and they are deleting
648 // whitespace at the start of the line, we should delete as if its a
649 // tab (tabSize number of spaces)
650 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
651 tabSize := int(settings["tabsize"].(float64))
652 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
654 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
655 cx, cy := v.Cursor.X, v.Cursor.Y
657 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
658 v.Cursor.X, v.Cursor.Y = cx, cy
661 cx, cy := v.Cursor.X, v.Cursor.Y
664 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
665 v.Cursor.X, v.Cursor.Y = cx, cy
668 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
672 // DeleteWordRight deletes the word to the right of the cursor
673 func (v *View) DeleteWordRight() bool {
675 if v.Cursor.HasSelection() {
676 v.Cursor.DeleteSelection()
677 v.Cursor.ResetSelection()
682 // DeleteWordLeft deletes the word to the left of the cursor
683 func (v *View) DeleteWordLeft() bool {
685 if v.Cursor.HasSelection() {
686 v.Cursor.DeleteSelection()
687 v.Cursor.ResetSelection()
692 // Delete deletes the next character
693 func (v *View) Delete() bool {
694 if v.Cursor.HasSelection() {
695 v.Cursor.DeleteSelection()
696 v.Cursor.ResetSelection()
699 if loc.LessThan(v.Buf.End()) {
700 v.Buf.Remove(loc, loc.Move(1, v.Buf))
706 // InsertTab inserts a tab or spaces
707 func (v *View) InsertTab() bool {
709 if v.Cursor.HasSelection() {
710 v.Cursor.DeleteSelection()
711 v.Cursor.ResetSelection()
713 if settings["tabstospaces"].(bool) {
714 tabSize := int(settings["tabsize"].(float64))
715 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
716 for i := 0; i < tabSize; i++ {
720 v.Buf.Insert(v.Cursor.Loc, "\t")
726 // Save the buffer to disk
727 func (v *View) Save() bool {
729 // We can't save the help text
732 // If this is an empty buffer, ask for a filename
733 if v.Buf.Path == "" {
734 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
736 v.Buf.Path = filename
737 v.Buf.Name = filename
744 if strings.HasSuffix(err.Error(), "permission denied") {
745 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
747 err = v.Buf.SaveWithSudo()
749 messenger.Error(err.Error())
752 messenger.Message("Saved " + v.Buf.Path)
757 messenger.Error(err.Error())
760 messenger.Message("Saved " + v.Buf.Path)
765 // Find opens a prompt and searches forward for the input
766 func (v *View) Find() bool {
767 if v.Cursor.HasSelection() {
768 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
770 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
776 // FindNext searches forwards for the last used search term
777 func (v *View) FindNext() bool {
778 if v.Cursor.HasSelection() {
779 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
781 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
783 messenger.Message("Finding: " + lastSearch)
784 Search(lastSearch, v, true)
788 // FindPrevious searches backwards for the last used search term
789 func (v *View) FindPrevious() bool {
790 if v.Cursor.HasSelection() {
791 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
793 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
795 messenger.Message("Finding: " + lastSearch)
796 Search(lastSearch, v, false)
800 // Undo undoes the last action
801 func (v *View) Undo() bool {
803 messenger.Message("Undid action")
807 // Redo redoes the last action
808 func (v *View) Redo() bool {
810 messenger.Message("Redid action")
814 // Copy the selection to the system clipboard
815 func (v *View) Copy() bool {
816 if v.Cursor.HasSelection() {
817 clipboard.WriteAll(v.Cursor.GetSelection())
819 messenger.Message("Copied selection")
824 // CutLine cuts the current line to the clipboard
825 func (v *View) CutLine() bool {
826 v.Cursor.SelectLine()
827 if !v.Cursor.HasSelection() {
830 if v.freshClip == true {
831 if v.Cursor.HasSelection() {
832 if clip, err := clipboard.ReadAll(); err != nil {
835 clipboard.WriteAll(clip + v.Cursor.GetSelection())
838 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
842 v.lastCutTime = time.Now()
843 v.Cursor.DeleteSelection()
844 v.Cursor.ResetSelection()
845 messenger.Message("Cut line")
849 // Cut the selection to the system clipboard
850 func (v *View) Cut() bool {
851 if v.Cursor.HasSelection() {
852 clipboard.WriteAll(v.Cursor.GetSelection())
853 v.Cursor.DeleteSelection()
854 v.Cursor.ResetSelection()
856 messenger.Message("Cut selection")
861 // DuplicateLine duplicates the current line
862 func (v *View) DuplicateLine() bool {
864 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
866 messenger.Message("Duplicated line")
870 // DeleteLine deletes the current line
871 func (v *View) DeleteLine() bool {
872 v.Cursor.SelectLine()
873 if !v.Cursor.HasSelection() {
876 v.Cursor.DeleteSelection()
877 v.Cursor.ResetSelection()
878 messenger.Message("Deleted line")
882 // Paste whatever is in the system clipboard into the buffer
883 // Delete and paste if the user has a selection
884 func (v *View) Paste() bool {
885 if v.Cursor.HasSelection() {
886 v.Cursor.DeleteSelection()
887 v.Cursor.ResetSelection()
889 clip, _ := clipboard.ReadAll()
890 v.Buf.Insert(v.Cursor.Loc, clip)
891 v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
893 messenger.Message("Pasted clipboard")
897 // SelectAll selects the entire buffer
898 func (v *View) SelectAll() bool {
899 v.Cursor.CurSelection[0] = v.Buf.Start()
900 v.Cursor.CurSelection[1] = v.Buf.End()
901 // Put the cursor at the beginning
907 // OpenFile opens a new file in the buffer
908 func (v *View) OpenFile() bool {
909 if v.CanClose("Continue? (yes, no, save) ") {
910 filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
914 home, _ := homedir.Dir()
915 filename = strings.Replace(filename, "~", home, 1)
916 file, err := ioutil.ReadFile(filename)
920 // File does not exist -- create an empty buffer with that name
921 buf = NewBuffer([]byte{}, filename)
923 buf = NewBuffer(file, filename)
931 // Start moves the viewport to the start of the buffer
932 func (v *View) Start() bool {
937 // End moves the viewport to the end of the buffer
938 func (v *View) End() bool {
939 if v.height > v.Buf.NumLines {
942 v.Topline = v.Buf.NumLines - v.height
947 // PageUp scrolls the view up a page
948 func (v *View) PageUp() bool {
949 if v.Topline > v.height {
957 // PageDown scrolls the view down a page
958 func (v *View) PageDown() bool {
959 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
960 v.ScrollDown(v.height)
961 } else if v.Buf.NumLines >= v.height {
962 v.Topline = v.Buf.NumLines - v.height
967 // CursorPageUp places the cursor a page up
968 func (v *View) CursorPageUp() bool {
969 if v.Cursor.HasSelection() {
970 v.Cursor.Loc = v.Cursor.CurSelection[0]
971 v.Cursor.ResetSelection()
973 v.Cursor.UpN(v.height)
977 // CursorPageDown places the cursor a page up
978 func (v *View) CursorPageDown() bool {
979 if v.Cursor.HasSelection() {
980 v.Cursor.Loc = v.Cursor.CurSelection[1]
981 v.Cursor.ResetSelection()
983 v.Cursor.DownN(v.height)
987 // HalfPageUp scrolls the view up half a page
988 func (v *View) HalfPageUp() bool {
989 if v.Topline > v.height/2 {
990 v.ScrollUp(v.height / 2)
997 // HalfPageDown scrolls the view down half a page
998 func (v *View) HalfPageDown() bool {
999 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
1000 v.ScrollDown(v.height / 2)
1002 if v.Buf.NumLines >= v.height {
1003 v.Topline = v.Buf.NumLines - v.height
1009 // ToggleRuler turns line numbers off and on
1010 func (v *View) ToggleRuler() bool {
1011 if settings["ruler"] == false {
1012 settings["ruler"] = true
1013 messenger.Message("Enabled ruler")
1015 settings["ruler"] = false
1016 messenger.Message("Disabled ruler")
1021 // JumpLine jumps to a line and moves the view accordingly.
1022 func (v *View) JumpLine() bool {
1023 // Prompt for line number
1024 linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
1028 lineint, err := strconv.Atoi(linestring)
1029 lineint = lineint - 1 // fix offset
1031 messenger.Error(err) // return errors
1034 // Move cursor and view if possible.
1035 if lineint < v.Buf.NumLines && lineint >= 0 {
1037 v.Cursor.Y = lineint
1040 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1044 // ClearStatus clears the messenger bar
1045 func (v *View) ClearStatus() bool {
1046 messenger.Message("")
1050 // ToggleHelp toggles the help screen
1051 func (v *View) ToggleHelp() bool {
1053 v.lastBuffer = v.Buf
1054 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
1055 helpBuffer.Name = "Help"
1057 v.OpenBuffer(helpBuffer)
1059 v.OpenBuffer(v.lastBuffer)
1065 // ShellMode opens a terminal to run a shell command
1066 func (v *View) ShellMode() bool {
1067 input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
1069 // The true here is for openTerm to make the command interactive
1070 HandleShellCommand(input, true)
1075 // CommandMode lets the user enter a command
1076 func (v *View) CommandMode() bool {
1077 input, canceled := messenger.Prompt("> ", "Command", NoCompletion)
1079 HandleCommand(input)
1084 // Quit quits the editor
1085 // This behavior needs to be changed and should really only quit the editor if this
1087 // However, since micro only supports one view for now, it doesn't really matter
1088 func (v *View) Quit() bool {
1090 return v.ToggleHelp()
1092 // Make sure not to quit if there are unsaved changes
1093 if v.CanClose("Quit anyway? (yes, no, save) ") {
1095 if len(tabs[curTab].views) > 1 {
1097 if v.splitChild != nil {
1099 view.splitParent = v.splitParent
1100 } else if v.splitParent != nil {
1101 view = v.splitParent
1102 v.splitParent.splitChild = nil
1104 view.x, view.y = view.splitOrigPos[0], view.splitOrigPos[1]
1105 view.widthPercent, view.heightPercent = view.splitOrigDimensions[0], view.splitOrigDimensions[1]
1106 view.Resize(screen.Size())
1107 if settings["syntax"].(bool) {
1108 view.matches = Match(view)
1110 tabs[curTab].views = tabs[curTab].views[:v.Num+copy(tabs[curTab].views[v.Num:], tabs[curTab].views[v.Num+1:])]
1111 for i, v := range tabs[curTab].views {
1114 tabs[curTab].curView = view.Num
1115 } else if len(tabs) > 1 {
1116 if len(tabs[v.TabNum].views) == 1 {
1117 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1118 for i, t := range tabs {
1121 if curTab >= len(tabs) {
1125 CurView().Resize(screen.Size())
1126 CurView().matches = Match(CurView())
1137 // AddTab adds a new tab with an empty buffer
1138 func (v *View) AddTab() bool {
1139 tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1140 tab.SetNum(len(tabs))
1141 tabs = append(tabs, tab)
1144 for _, t := range tabs {
1145 for _, v := range t.views {
1146 v.Resize(screen.Size())
1153 // PreviousTab switches to the previous tab in the tab list
1154 func (v *View) PreviousTab() bool {
1157 } else if curTab == 0 {
1158 curTab = len(tabs) - 1
1163 // NextTab switches to the next tab in the tab list
1164 func (v *View) NextTab() bool {
1165 if curTab < len(tabs)-1 {
1167 } else if curTab == len(tabs)-1 {
1173 // None is no action