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 "LastTab": (*View).LastTab,
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",
389 "PageUp": "CursorPageUp",
390 "PageDown": "CursorPageDown",
391 "CtrlG": "ToggleHelp",
392 "CtrlR": "ToggleRuler",
395 "Esc": "ClearStatus",
396 "CtrlB": "ShellMode",
398 "CtrlE": "CommandMode",
400 // Emacs-style keybindings
401 "Alt-f": "WordRight",
403 "Alt-a": "StartOfLine",
404 "Alt-e": "EndOfLine",
406 "Alt-n": "CursorDown",
410 // CursorUp moves the cursor up
411 func (v *View) CursorUp() bool {
412 if v.Cursor.HasSelection() {
413 v.Cursor.Loc = v.Cursor.CurSelection[0]
414 v.Cursor.ResetSelection()
420 // CursorDown moves the cursor down
421 func (v *View) CursorDown() bool {
422 if v.Cursor.HasSelection() {
423 v.Cursor.Loc = v.Cursor.CurSelection[1]
424 v.Cursor.ResetSelection()
430 // CursorLeft moves the cursor left
431 func (v *View) CursorLeft() bool {
432 if v.Cursor.HasSelection() {
433 v.Cursor.Loc = v.Cursor.CurSelection[0]
434 v.Cursor.ResetSelection()
441 // CursorRight moves the cursor right
442 func (v *View) CursorRight() bool {
443 if v.Cursor.HasSelection() {
444 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
445 v.Cursor.ResetSelection()
452 // WordRight moves the cursor one word to the right
453 func (v *View) WordRight() bool {
458 // WordLeft moves the cursor one word to the left
459 func (v *View) WordLeft() bool {
464 // SelectUp selects up one line
465 func (v *View) SelectUp() bool {
466 if !v.Cursor.HasSelection() {
467 v.Cursor.OrigSelection[0] = v.Cursor.Loc
470 v.Cursor.SelectTo(v.Cursor.Loc)
474 // SelectDown selects down one line
475 func (v *View) SelectDown() bool {
476 if !v.Cursor.HasSelection() {
477 v.Cursor.OrigSelection[0] = v.Cursor.Loc
480 v.Cursor.SelectTo(v.Cursor.Loc)
484 // SelectLeft selects the character to the left of the cursor
485 func (v *View) SelectLeft() bool {
487 count := v.Buf.End().Move(-1, v.Buf)
488 if loc.GreaterThan(count) {
491 if !v.Cursor.HasSelection() {
492 v.Cursor.OrigSelection[0] = loc
495 v.Cursor.SelectTo(v.Cursor.Loc)
499 // SelectRight selects the character to the right of the cursor
500 func (v *View) SelectRight() bool {
502 count := v.Buf.End().Move(-1, v.Buf)
503 if loc.GreaterThan(count) {
506 if !v.Cursor.HasSelection() {
507 v.Cursor.OrigSelection[0] = loc
510 v.Cursor.SelectTo(v.Cursor.Loc)
514 // SelectWordRight selects the word to the right of the cursor
515 func (v *View) SelectWordRight() bool {
516 if !v.Cursor.HasSelection() {
517 v.Cursor.OrigSelection[0] = v.Cursor.Loc
520 v.Cursor.SelectTo(v.Cursor.Loc)
524 // SelectWordLeft selects the word to the left of the cursor
525 func (v *View) SelectWordLeft() bool {
526 if !v.Cursor.HasSelection() {
527 v.Cursor.OrigSelection[0] = v.Cursor.Loc
530 v.Cursor.SelectTo(v.Cursor.Loc)
534 // StartOfLine moves the cursor to the start of the line
535 func (v *View) StartOfLine() bool {
540 // EndOfLine moves the cursor to the end of the line
541 func (v *View) EndOfLine() bool {
546 // SelectToStartOfLine selects to the start of the current line
547 func (v *View) SelectToStartOfLine() bool {
548 if !v.Cursor.HasSelection() {
549 v.Cursor.OrigSelection[0] = v.Cursor.Loc
552 v.Cursor.SelectTo(v.Cursor.Loc)
556 // SelectToEndOfLine selects to the end of the current line
557 func (v *View) SelectToEndOfLine() bool {
558 if !v.Cursor.HasSelection() {
559 v.Cursor.OrigSelection[0] = v.Cursor.Loc
562 v.Cursor.SelectTo(v.Cursor.Loc)
566 // CursorStart moves the cursor to the start of the buffer
567 func (v *View) CursorStart() bool {
573 // CursorEnd moves the cursor to the end of the buffer
574 func (v *View) CursorEnd() bool {
575 v.Cursor.Loc = v.Buf.End()
579 // SelectToStart selects the text from the cursor to the start of the buffer
580 func (v *View) SelectToStart() bool {
581 if !v.Cursor.HasSelection() {
582 v.Cursor.OrigSelection[0] = v.Cursor.Loc
585 v.Cursor.SelectTo(v.Buf.Start())
589 // SelectToEnd selects the text from the cursor to the end of the buffer
590 func (v *View) SelectToEnd() bool {
591 if !v.Cursor.HasSelection() {
592 v.Cursor.OrigSelection[0] = v.Cursor.Loc
595 v.Cursor.SelectTo(v.Buf.End())
599 // InsertSpace inserts a space
600 func (v *View) InsertSpace() bool {
601 if v.Cursor.HasSelection() {
602 v.Cursor.DeleteSelection()
603 v.Cursor.ResetSelection()
605 v.Buf.Insert(v.Cursor.Loc, " ")
610 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
611 func (v *View) InsertEnter() bool {
613 if v.Cursor.HasSelection() {
614 v.Cursor.DeleteSelection()
615 v.Cursor.ResetSelection()
618 v.Buf.Insert(v.Cursor.Loc, "\n")
619 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
622 if settings["autoindent"].(bool) {
623 v.Buf.Insert(v.Cursor.Loc, ws)
624 for i := 0; i < len(ws); i++ {
628 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
632 // Backspace deletes the previous character
633 func (v *View) Backspace() bool {
634 // Delete a character
635 if v.Cursor.HasSelection() {
636 v.Cursor.DeleteSelection()
637 v.Cursor.ResetSelection()
638 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
639 // We have to do something a bit hacky here because we want to
640 // delete the line by first moving left and then deleting backwards
641 // but the undo redo would place the cursor in the wrong place
642 // So instead we move left, save the position, move back, delete
643 // and restore the position
645 // If the user is using spaces instead of tabs and they are deleting
646 // whitespace at the start of the line, we should delete as if its a
647 // tab (tabSize number of spaces)
648 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
649 tabSize := int(settings["tabsize"].(float64))
650 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
652 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
653 cx, cy := v.Cursor.X, v.Cursor.Y
655 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
656 v.Cursor.X, v.Cursor.Y = cx, cy
659 cx, cy := v.Cursor.X, v.Cursor.Y
662 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
663 v.Cursor.X, v.Cursor.Y = cx, cy
666 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
670 // DeleteWordRight deletes the word to the right of the cursor
671 func (v *View) DeleteWordRight() bool {
673 if v.Cursor.HasSelection() {
674 v.Cursor.DeleteSelection()
675 v.Cursor.ResetSelection()
680 // DeleteWordLeft deletes the word to the left of the cursor
681 func (v *View) DeleteWordLeft() bool {
683 if v.Cursor.HasSelection() {
684 v.Cursor.DeleteSelection()
685 v.Cursor.ResetSelection()
690 // Delete deletes the next character
691 func (v *View) Delete() bool {
692 if v.Cursor.HasSelection() {
693 v.Cursor.DeleteSelection()
694 v.Cursor.ResetSelection()
697 if loc.LessThan(v.Buf.End()) {
698 v.Buf.Remove(loc, loc.Move(1, v.Buf))
704 // InsertTab inserts a tab or spaces
705 func (v *View) InsertTab() bool {
707 if v.Cursor.HasSelection() {
708 v.Cursor.DeleteSelection()
709 v.Cursor.ResetSelection()
711 if settings["tabstospaces"].(bool) {
712 tabSize := int(settings["tabsize"].(float64))
713 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
714 for i := 0; i < tabSize; i++ {
718 v.Buf.Insert(v.Cursor.Loc, "\t")
724 // Save the buffer to disk
725 func (v *View) Save() bool {
727 // We can't save the help text
730 // If this is an empty buffer, ask for a filename
731 if v.Buf.Path == "" {
732 filename, canceled := messenger.Prompt("Filename: ", "Save")
734 v.Buf.Path = filename
735 v.Buf.Name = filename
742 if strings.HasSuffix(err.Error(), "permission denied") {
743 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
745 err = v.Buf.SaveWithSudo()
747 messenger.Error(err.Error())
754 messenger.Error(err.Error())
757 messenger.Message("Saved " + v.Buf.Path)
762 // Find opens a prompt and searches forward for the input
763 func (v *View) Find() bool {
764 if v.Cursor.HasSelection() {
765 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
767 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
773 // FindNext searches forwards for the last used search term
774 func (v *View) FindNext() bool {
775 if v.Cursor.HasSelection() {
776 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
778 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
780 messenger.Message("Finding: " + lastSearch)
781 Search(lastSearch, v, true)
785 // FindPrevious searches backwards for the last used search term
786 func (v *View) FindPrevious() bool {
787 if v.Cursor.HasSelection() {
788 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
790 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
792 messenger.Message("Finding: " + lastSearch)
793 Search(lastSearch, v, false)
797 // Undo undoes the last action
798 func (v *View) Undo() bool {
800 messenger.Message("Undid action")
804 // Redo redoes the last action
805 func (v *View) Redo() bool {
807 messenger.Message("Redid action")
811 // Copy the selection to the system clipboard
812 func (v *View) Copy() bool {
813 if v.Cursor.HasSelection() {
814 clipboard.WriteAll(v.Cursor.GetSelection())
816 messenger.Message("Copied selection")
821 // CutLine cuts the current line to the clipboard
822 func (v *View) CutLine() bool {
823 v.Cursor.SelectLine()
824 if !v.Cursor.HasSelection() {
827 if v.freshClip == true {
828 if v.Cursor.HasSelection() {
829 if clip, err := clipboard.ReadAll(); err != nil {
832 clipboard.WriteAll(clip + v.Cursor.GetSelection())
835 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
839 v.lastCutTime = time.Now()
840 v.Cursor.DeleteSelection()
841 v.Cursor.ResetSelection()
842 messenger.Message("Cut line")
846 // Cut the selection to the system clipboard
847 func (v *View) Cut() bool {
848 if v.Cursor.HasSelection() {
849 clipboard.WriteAll(v.Cursor.GetSelection())
850 v.Cursor.DeleteSelection()
851 v.Cursor.ResetSelection()
853 messenger.Message("Cut selection")
858 // DuplicateLine duplicates the current line
859 func (v *View) DuplicateLine() bool {
861 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
863 messenger.Message("Duplicated line")
867 // DeleteLine deletes the current line
868 func (v *View) DeleteLine() bool {
869 v.Cursor.SelectLine()
870 if !v.Cursor.HasSelection() {
873 v.Cursor.DeleteSelection()
874 v.Cursor.ResetSelection()
875 messenger.Message("Deleted line")
879 // Paste whatever is in the system clipboard into the buffer
880 // Delete and paste if the user has a selection
881 func (v *View) Paste() bool {
882 if v.Cursor.HasSelection() {
883 v.Cursor.DeleteSelection()
884 v.Cursor.ResetSelection()
886 clip, _ := clipboard.ReadAll()
887 v.Buf.Insert(v.Cursor.Loc, clip)
888 v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
890 messenger.Message("Pasted clipboard")
894 // SelectAll selects the entire buffer
895 func (v *View) SelectAll() bool {
896 v.Cursor.CurSelection[0] = v.Buf.Start()
897 v.Cursor.CurSelection[1] = v.Buf.End()
898 // Put the cursor at the beginning
904 // OpenFile opens a new file in the buffer
905 func (v *View) OpenFile() bool {
906 if v.CanClose("Continue? (yes, no, save) ") {
907 filename, canceled := messenger.Prompt("File to open: ", "Open")
911 home, _ := homedir.Dir()
912 filename = strings.Replace(filename, "~", home, 1)
913 file, err := ioutil.ReadFile(filename)
916 messenger.Error(err.Error())
919 buf := NewBuffer(file, filename)
926 // Start moves the viewport to the start of the buffer
927 func (v *View) Start() bool {
932 // End moves the viewport to the end of the buffer
933 func (v *View) End() bool {
934 if v.height > v.Buf.NumLines {
937 v.Topline = v.Buf.NumLines - v.height
942 // PageUp scrolls the view up a page
943 func (v *View) PageUp() bool {
944 if v.Topline > v.height {
952 // PageDown scrolls the view down a page
953 func (v *View) PageDown() bool {
954 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
955 v.ScrollDown(v.height)
956 } else if v.Buf.NumLines >= v.height {
957 v.Topline = v.Buf.NumLines - v.height
962 // CursorPageUp places the cursor a page up
963 func (v *View) CursorPageUp() bool {
964 if v.Cursor.HasSelection() {
965 v.Cursor.Loc = v.Cursor.CurSelection[0]
966 v.Cursor.ResetSelection()
968 v.Cursor.UpN(v.height)
972 // CursorPageDown places the cursor a page up
973 func (v *View) CursorPageDown() bool {
974 if v.Cursor.HasSelection() {
975 v.Cursor.Loc = v.Cursor.CurSelection[1]
976 v.Cursor.ResetSelection()
978 v.Cursor.DownN(v.height)
982 // HalfPageUp scrolls the view up half a page
983 func (v *View) HalfPageUp() bool {
984 if v.Topline > v.height/2 {
985 v.ScrollUp(v.height / 2)
992 // HalfPageDown scrolls the view down half a page
993 func (v *View) HalfPageDown() bool {
994 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
995 v.ScrollDown(v.height / 2)
997 if v.Buf.NumLines >= v.height {
998 v.Topline = v.Buf.NumLines - v.height
1004 // ToggleRuler turns line numbers off and on
1005 func (v *View) ToggleRuler() bool {
1006 if settings["ruler"] == false {
1007 settings["ruler"] = true
1008 messenger.Message("Enabled ruler")
1010 settings["ruler"] = false
1011 messenger.Message("Disabled ruler")
1016 // JumpLine jumps to a line and moves the view accordingly.
1017 func (v *View) JumpLine() bool {
1018 // Prompt for line number
1019 linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber")
1023 lineint, err := strconv.Atoi(linestring)
1024 lineint = lineint - 1 // fix offset
1026 messenger.Error(err) // return errors
1029 // Move cursor and view if possible.
1030 if lineint < v.Buf.NumLines {
1032 v.Cursor.Y = lineint
1035 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1039 // ClearStatus clears the messenger bar
1040 func (v *View) ClearStatus() bool {
1041 messenger.Message("")
1045 // ToggleHelp toggles the help screen
1046 func (v *View) ToggleHelp() bool {
1048 v.lastBuffer = v.Buf
1049 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
1050 helpBuffer.Name = "Help"
1052 v.OpenBuffer(helpBuffer)
1054 v.OpenBuffer(v.lastBuffer)
1060 // ShellMode opens a terminal to run a shell command
1061 func (v *View) ShellMode() bool {
1062 input, canceled := messenger.Prompt("$ ", "Shell")
1064 // The true here is for openTerm to make the command interactive
1065 HandleShellCommand(input, true)
1070 // CommandMode lets the user enter a command
1071 func (v *View) CommandMode() bool {
1072 input, canceled := messenger.Prompt("> ", "Command")
1074 HandleCommand(input)
1079 // Quit quits the editor
1080 // This behavior needs to be changed and should really only quit the editor if this
1082 // However, since micro only supports one view for now, it doesn't really matter
1083 func (v *View) Quit() bool {
1085 return v.ToggleHelp()
1087 // Make sure not to quit if there are unsaved changes
1088 if v.CanClose("Quit anyway? (yes, no, save) ") {
1091 if len(tabs[v.TabNum].views) == 1 {
1092 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1093 for i, t := range tabs {
1096 if curTab >= len(tabs) {
1101 tab.views[tab.curView].Resize(screen.Size())
1112 func (v *View) AddTab() bool {
1113 tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1114 tab.SetNum(len(tabs))
1115 tabs = append(tabs, tab)
1120 func (v *View) LastTab() bool {
1127 func (v *View) NextTab() bool {
1128 if curTab < len(tabs)-1 {
1134 // None is no action