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 "Paste": (*View).Paste,
58 "SelectAll": (*View).SelectAll,
59 "OpenFile": (*View).OpenFile,
60 "Start": (*View).Start,
62 "PageUp": (*View).PageUp,
63 "PageDown": (*View).PageDown,
64 "HalfPageUp": (*View).HalfPageUp,
65 "HalfPageDown": (*View).HalfPageDown,
66 "StartOfLine": (*View).StartOfLine,
67 "EndOfLine": (*View).EndOfLine,
68 "ToggleHelp": (*View).ToggleHelp,
69 "ToggleRuler": (*View).ToggleRuler,
70 "JumpLine": (*View).JumpLine,
71 "ClearStatus": (*View).ClearStatus,
72 "ShellMode": (*View).ShellMode,
73 "CommandMode": (*View).CommandMode,
77 var bindingKeys = map[string]tcell.Key{
79 "Down": tcell.KeyDown,
80 "Right": tcell.KeyRight,
81 "Left": tcell.KeyLeft,
82 "UpLeft": tcell.KeyUpLeft,
83 "UpRight": tcell.KeyUpRight,
84 "DownLeft": tcell.KeyDownLeft,
85 "DownRight": tcell.KeyDownRight,
86 "Center": tcell.KeyCenter,
87 "PageUp": tcell.KeyPgUp,
88 "PageDown": tcell.KeyPgDn,
89 "Home": tcell.KeyHome,
91 "Insert": tcell.KeyInsert,
92 "Delete": tcell.KeyDelete,
93 "Help": tcell.KeyHelp,
94 "Exit": tcell.KeyExit,
95 "Clear": tcell.KeyClear,
96 "Cancel": tcell.KeyCancel,
97 "Print": tcell.KeyPrint,
98 "Pause": tcell.KeyPause,
99 "Backtab": tcell.KeyBacktab,
164 "CtrlSpace": tcell.KeyCtrlSpace,
165 "CtrlA": tcell.KeyCtrlA,
166 "CtrlB": tcell.KeyCtrlB,
167 "CtrlC": tcell.KeyCtrlC,
168 "CtrlD": tcell.KeyCtrlD,
169 "CtrlE": tcell.KeyCtrlE,
170 "CtrlF": tcell.KeyCtrlF,
171 "CtrlG": tcell.KeyCtrlG,
172 "CtrlH": tcell.KeyCtrlH,
173 "CtrlI": tcell.KeyCtrlI,
174 "CtrlJ": tcell.KeyCtrlJ,
175 "CtrlK": tcell.KeyCtrlK,
176 "CtrlL": tcell.KeyCtrlL,
177 "CtrlM": tcell.KeyCtrlM,
178 "CtrlN": tcell.KeyCtrlN,
179 "CtrlO": tcell.KeyCtrlO,
180 "CtrlP": tcell.KeyCtrlP,
181 "CtrlQ": tcell.KeyCtrlQ,
182 "CtrlR": tcell.KeyCtrlR,
183 "CtrlS": tcell.KeyCtrlS,
184 "CtrlT": tcell.KeyCtrlT,
185 "CtrlU": tcell.KeyCtrlU,
186 "CtrlV": tcell.KeyCtrlV,
187 "CtrlW": tcell.KeyCtrlW,
188 "CtrlX": tcell.KeyCtrlX,
189 "CtrlY": tcell.KeyCtrlY,
190 "CtrlZ": tcell.KeyCtrlZ,
191 "CtrlLeftSq": tcell.KeyCtrlLeftSq,
192 "CtrlBackslash": tcell.KeyCtrlBackslash,
193 "CtrlRightSq": tcell.KeyCtrlRightSq,
194 "CtrlCarat": tcell.KeyCtrlCarat,
195 "CtrlUnderscore": tcell.KeyCtrlUnderscore,
196 "Backspace": tcell.KeyBackspace,
199 "Escape": tcell.KeyEscape,
200 "Enter": tcell.KeyEnter,
201 "Backspace2": tcell.KeyBackspace2,
203 // I renamed these keys to PageUp and PageDown but I don't want to break someone's keybindings
204 "PgUp": tcell.KeyPgUp,
205 "PgDown": tcell.KeyPgDn,
208 // The Key struct holds the data for a keypress (keycode + modifiers)
211 modifiers tcell.ModMask
215 // InitBindings initializes the keybindings for micro
216 func InitBindings() {
217 bindings = make(map[Key][]func(*View) bool)
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 parseBindings(defaults)
237 parseBindings(parsed)
240 func parseBindings(userBindings map[string]string) {
241 for k, v := range userBindings {
246 // findKey will find binding Key 'b' using string 'k'
247 func findKey(k string) (b Key, ok bool) {
248 modifiers := tcell.ModNone
250 // First, we'll strip off all the modifiers in the name and add them to the
255 case strings.HasPrefix(k, "-"):
256 // We optionally support dashes between modifiers
258 case strings.HasPrefix(k, "Ctrl"):
260 modifiers |= tcell.ModCtrl
261 case strings.HasPrefix(k, "Alt"):
263 modifiers |= tcell.ModAlt
264 case strings.HasPrefix(k, "Shift"):
266 modifiers |= tcell.ModShift
272 // Control is handled specially, since some character codes in bindingKeys
273 // are different when Control is depressed. We should check for Control keys
275 if modifiers&tcell.ModCtrl != 0 {
276 // see if the key is in bindingKeys with the Ctrl prefix.
277 if code, ok := bindingKeys["Ctrl"+k]; ok {
278 // It is, we're done.
281 modifiers: modifiers,
287 // See if we can find the key in bindingKeys
288 if code, ok := bindingKeys[k]; ok {
291 modifiers: modifiers,
296 // If we were given one character, then we've got a rune.
299 keyCode: tcell.KeyRune,
300 modifiers: modifiers,
305 // We don't know what happened.
309 // findAction will find 'action' using string 'v'
310 func findAction(v string) (action func(*View) bool) {
311 action, ok := bindingActions[v]
313 // If the user seems to be binding a function that doesn't exist
314 // We hope that it's a lua function that exists and bind it to that
315 action = LuaFunctionBinding(v)
320 // BindKey takes a key and an action and binds the two together
321 func BindKey(k, v string) {
322 key, ok := findKey(k)
326 if v == "ToggleHelp" {
330 actionNames := strings.Split(v, ",")
331 actions := make([]func(*View) bool, 0, len(actionNames))
332 for _, actionName := range actionNames {
333 actions = append(actions, findAction(actionName))
336 bindings[key] = actions
339 // DefaultBindings returns a map containing micro's default keybindings
340 func DefaultBindings() map[string]string {
341 return map[string]string{
343 "Down": "CursorDown",
344 "Right": "CursorRight",
345 "Left": "CursorLeft",
346 "ShiftUp": "SelectUp",
347 "ShiftDown": "SelectDown",
348 "ShiftLeft": "SelectLeft",
349 "ShiftRight": "SelectRight",
350 "AltLeft": "WordLeft",
351 "AltRight": "WordRight",
352 "AltShiftRight": "SelectWordRight",
353 "AltShiftLeft": "SelectWordLeft",
354 "CtrlLeft": "StartOfLine",
355 "CtrlRight": "EndOfLine",
356 "CtrlShiftLeft": "SelectToStartOfLine",
357 "CtrlShiftRight": "SelectToEndOfLine",
358 "CtrlUp": "CursorStart",
359 "CtrlDown": "CursorEnd",
360 "CtrlShiftUp": "SelectToStart",
361 "CtrlShiftDown": "SelectToEnd",
362 "Enter": "InsertEnter",
363 "Space": "InsertSpace",
364 "Backspace": "Backspace",
365 "Backspace2": "Backspace",
366 "Alt-Backspace": "DeleteWordLeft",
367 "Alt-Backspace2": "DeleteWordLeft",
373 "CtrlP": "FindPrevious",
379 "CtrlD": "DuplicateLine",
381 "CtrlA": "SelectAll",
384 "PageUp": "CursorPageUp",
385 "PageDown": "CursorPageDown",
386 "CtrlG": "ToggleHelp",
387 "CtrlR": "ToggleRuler",
390 "Esc": "ClearStatus",
391 "CtrlB": "ShellMode",
393 "CtrlE": "CommandMode",
395 // Emacs-style keybindings
396 "Alt-f": "WordRight",
398 "Alt-a": "StartOfLine",
399 "Alt-e": "EndOfLine",
401 "Alt-n": "CursorDown",
405 // CursorUp moves the cursor up
406 func (v *View) CursorUp() bool {
407 if v.Cursor.HasSelection() {
408 v.Cursor.Loc = v.Cursor.CurSelection[0]
409 v.Cursor.ResetSelection()
415 // CursorDown moves the cursor down
416 func (v *View) CursorDown() bool {
417 if v.Cursor.HasSelection() {
418 v.Cursor.Loc = v.Cursor.CurSelection[1]
419 v.Cursor.ResetSelection()
425 // CursorLeft moves the cursor left
426 func (v *View) CursorLeft() bool {
427 if v.Cursor.HasSelection() {
428 v.Cursor.Loc = v.Cursor.CurSelection[0]
429 v.Cursor.ResetSelection()
436 // CursorRight moves the cursor right
437 func (v *View) CursorRight() bool {
438 if v.Cursor.HasSelection() {
439 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
440 v.Cursor.ResetSelection()
447 // WordRight moves the cursor one word to the right
448 func (v *View) WordRight() bool {
453 // WordLeft moves the cursor one word to the left
454 func (v *View) WordLeft() bool {
459 // SelectUp selects up one line
460 func (v *View) SelectUp() bool {
461 if !v.Cursor.HasSelection() {
462 v.Cursor.OrigSelection[0] = v.Cursor.Loc
465 v.Cursor.SelectTo(v.Cursor.Loc)
469 // SelectDown selects down one line
470 func (v *View) SelectDown() bool {
471 if !v.Cursor.HasSelection() {
472 v.Cursor.OrigSelection[0] = v.Cursor.Loc
475 v.Cursor.SelectTo(v.Cursor.Loc)
479 // SelectLeft selects the character to the left of the cursor
480 func (v *View) SelectLeft() bool {
482 count := v.Buf.End().Move(-1, v.Buf)
483 if loc.GreaterThan(count) {
486 if !v.Cursor.HasSelection() {
487 v.Cursor.OrigSelection[0] = loc
490 v.Cursor.SelectTo(v.Cursor.Loc)
494 // SelectRight selects the character to the right of the cursor
495 func (v *View) SelectRight() bool {
497 count := v.Buf.End().Move(-1, v.Buf)
498 if loc.GreaterThan(count) {
501 if !v.Cursor.HasSelection() {
502 v.Cursor.OrigSelection[0] = loc
505 v.Cursor.SelectTo(v.Cursor.Loc)
509 // SelectWordRight selects the word to the right of the cursor
510 func (v *View) SelectWordRight() bool {
511 if !v.Cursor.HasSelection() {
512 v.Cursor.OrigSelection[0] = v.Cursor.Loc
515 v.Cursor.SelectTo(v.Cursor.Loc)
519 // SelectWordLeft selects the word to the left of the cursor
520 func (v *View) SelectWordLeft() bool {
521 if !v.Cursor.HasSelection() {
522 v.Cursor.OrigSelection[0] = v.Cursor.Loc
525 v.Cursor.SelectTo(v.Cursor.Loc)
529 // StartOfLine moves the cursor to the start of the line
530 func (v *View) StartOfLine() bool {
535 // EndOfLine moves the cursor to the end of the line
536 func (v *View) EndOfLine() bool {
541 // SelectToStartOfLine selects to the start of the current line
542 func (v *View) SelectToStartOfLine() bool {
543 if !v.Cursor.HasSelection() {
544 v.Cursor.OrigSelection[0] = v.Cursor.Loc
547 v.Cursor.SelectTo(v.Cursor.Loc)
551 // SelectToEndOfLine selects to the end of the current line
552 func (v *View) SelectToEndOfLine() bool {
553 if !v.Cursor.HasSelection() {
554 v.Cursor.OrigSelection[0] = v.Cursor.Loc
557 v.Cursor.SelectTo(v.Cursor.Loc)
561 // CursorStart moves the cursor to the start of the buffer
562 func (v *View) CursorStart() bool {
568 // CursorEnd moves the cursor to the end of the buffer
569 func (v *View) CursorEnd() bool {
570 v.Cursor.Loc = v.Buf.End()
574 // SelectToStart selects the text from the cursor to the start of the buffer
575 func (v *View) SelectToStart() bool {
576 if !v.Cursor.HasSelection() {
577 v.Cursor.OrigSelection[0] = v.Cursor.Loc
580 v.Cursor.SelectTo(v.Buf.Start())
584 // SelectToEnd selects the text from the cursor to the end of the buffer
585 func (v *View) SelectToEnd() bool {
586 if !v.Cursor.HasSelection() {
587 v.Cursor.OrigSelection[0] = v.Cursor.Loc
590 v.Cursor.SelectTo(v.Buf.End())
594 // InsertSpace inserts a space
595 func (v *View) InsertSpace() bool {
596 if v.Cursor.HasSelection() {
597 v.Cursor.DeleteSelection()
598 v.Cursor.ResetSelection()
600 v.Buf.Insert(v.Cursor.Loc, " ")
605 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
606 func (v *View) InsertEnter() bool {
608 if v.Cursor.HasSelection() {
609 v.Cursor.DeleteSelection()
610 v.Cursor.ResetSelection()
613 v.Buf.Insert(v.Cursor.Loc, "\n")
614 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
617 if settings["autoindent"].(bool) {
618 v.Buf.Insert(v.Cursor.Loc, ws)
619 for i := 0; i < len(ws); i++ {
623 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
627 // Backspace deletes the previous character
628 func (v *View) Backspace() bool {
629 // Delete a character
630 if v.Cursor.HasSelection() {
631 v.Cursor.DeleteSelection()
632 v.Cursor.ResetSelection()
633 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
634 // We have to do something a bit hacky here because we want to
635 // delete the line by first moving left and then deleting backwards
636 // but the undo redo would place the cursor in the wrong place
637 // So instead we move left, save the position, move back, delete
638 // and restore the position
640 // If the user is using spaces instead of tabs and they are deleting
641 // whitespace at the start of the line, we should delete as if its a
642 // tab (tabSize number of spaces)
643 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
644 tabSize := int(settings["tabsize"].(float64))
645 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
647 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
648 cx, cy := v.Cursor.X, v.Cursor.Y
650 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
651 v.Cursor.X, v.Cursor.Y = cx, cy
654 cx, cy := v.Cursor.X, v.Cursor.Y
657 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
658 v.Cursor.X, v.Cursor.Y = cx, cy
661 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
665 // DeleteWordRight deletes the word to the right of the cursor
666 func (v *View) DeleteWordRight() bool {
668 if v.Cursor.HasSelection() {
669 v.Cursor.DeleteSelection()
670 v.Cursor.ResetSelection()
675 // DeleteWordLeft deletes the word to the left of the cursor
676 func (v *View) DeleteWordLeft() bool {
678 if v.Cursor.HasSelection() {
679 v.Cursor.DeleteSelection()
680 v.Cursor.ResetSelection()
685 // Delete deletes the next character
686 func (v *View) Delete() bool {
687 if v.Cursor.HasSelection() {
688 v.Cursor.DeleteSelection()
689 v.Cursor.ResetSelection()
692 if loc.LessThan(v.Buf.End()) {
693 v.Buf.Remove(loc, loc.Move(1, v.Buf))
699 // InsertTab inserts a tab or spaces
700 func (v *View) InsertTab() bool {
702 if v.Cursor.HasSelection() {
703 v.Cursor.DeleteSelection()
704 v.Cursor.ResetSelection()
706 if settings["tabstospaces"].(bool) {
707 tabSize := int(settings["tabsize"].(float64))
708 v.Buf.Insert(v.Cursor.Loc, Spaces(tabSize))
709 for i := 0; i < tabSize; i++ {
713 v.Buf.Insert(v.Cursor.Loc, "\t")
719 // Save the buffer to disk
720 func (v *View) Save() bool {
722 // We can't save the help text
725 // If this is an empty buffer, ask for a filename
726 if v.Buf.Path == "" {
727 filename, canceled := messenger.Prompt("Filename: ", "Save")
729 v.Buf.Path = filename
730 v.Buf.Name = filename
737 if strings.HasSuffix(err.Error(), "permission denied") {
738 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
740 err = v.Buf.SaveWithSudo()
742 messenger.Error(err.Error())
749 messenger.Error(err.Error())
752 messenger.Message("Saved " + v.Buf.Path)
757 // Find opens a prompt and searches forward for the input
758 func (v *View) Find() bool {
759 if v.Cursor.HasSelection() {
760 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
762 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
768 // FindNext searches forwards for the last used search term
769 func (v *View) FindNext() bool {
770 if v.Cursor.HasSelection() {
771 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
773 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
775 messenger.Message("Finding: " + lastSearch)
776 Search(lastSearch, v, true)
780 // FindPrevious searches backwards for the last used search term
781 func (v *View) FindPrevious() bool {
782 if v.Cursor.HasSelection() {
783 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
785 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
787 messenger.Message("Finding: " + lastSearch)
788 Search(lastSearch, v, false)
792 // Undo undoes the last action
793 func (v *View) Undo() bool {
795 messenger.Message("Undid action")
799 // Redo redoes the last action
800 func (v *View) Redo() bool {
802 messenger.Message("Redid action")
806 // Copy the selection to the system clipboard
807 func (v *View) Copy() bool {
808 if v.Cursor.HasSelection() {
809 clipboard.WriteAll(v.Cursor.GetSelection())
811 messenger.Message("Copied selection")
816 // CutLine cuts the current line to the clipboard
817 func (v *View) CutLine() bool {
818 v.Cursor.SelectLine()
819 if !v.Cursor.HasSelection() {
822 if v.freshClip == true {
823 if v.Cursor.HasSelection() {
824 if clip, err := clipboard.ReadAll(); err != nil {
827 clipboard.WriteAll(clip + v.Cursor.GetSelection())
830 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
834 v.lastCutTime = time.Now()
835 v.Cursor.DeleteSelection()
836 v.Cursor.ResetSelection()
837 messenger.Message("Cut line")
841 // Cut the selection to the system clipboard
842 func (v *View) Cut() bool {
843 if v.Cursor.HasSelection() {
844 clipboard.WriteAll(v.Cursor.GetSelection())
845 v.Cursor.DeleteSelection()
846 v.Cursor.ResetSelection()
848 messenger.Message("Cut selection")
853 // DuplicateLine duplicates the current line
854 func (v *View) DuplicateLine() bool {
856 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
858 messenger.Message("Duplicated line")
862 // Paste whatever is in the system clipboard into the buffer
863 // Delete and paste if the user has a selection
864 func (v *View) Paste() bool {
865 if v.Cursor.HasSelection() {
866 v.Cursor.DeleteSelection()
867 v.Cursor.ResetSelection()
869 clip, _ := clipboard.ReadAll()
870 v.Buf.Insert(v.Cursor.Loc, clip)
871 v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
873 messenger.Message("Pasted clipboard")
877 // SelectAll selects the entire buffer
878 func (v *View) SelectAll() bool {
879 v.Cursor.CurSelection[0] = v.Buf.Start()
880 v.Cursor.CurSelection[1] = v.Buf.End()
881 // Put the cursor at the beginning
887 // OpenFile opens a new file in the buffer
888 func (v *View) OpenFile() bool {
889 if v.CanClose("Continue? (yes, no, save) ") {
890 filename, canceled := messenger.Prompt("File to open: ", "Open")
894 home, _ := homedir.Dir()
895 filename = strings.Replace(filename, "~", home, 1)
896 file, err := ioutil.ReadFile(filename)
899 messenger.Error(err.Error())
902 buf := NewBuffer(file, filename)
908 // Start moves the viewport to the start of the buffer
909 func (v *View) Start() bool {
914 // End moves the viewport to the end of the buffer
915 func (v *View) End() bool {
916 if v.height > v.Buf.NumLines {
919 v.Topline = v.Buf.NumLines - v.height
924 // PageUp scrolls the view up a page
925 func (v *View) PageUp() bool {
926 if v.Topline > v.height {
934 // PageDown scrolls the view down a page
935 func (v *View) PageDown() bool {
936 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
937 v.ScrollDown(v.height)
938 } else if v.Buf.NumLines >= v.height {
939 v.Topline = v.Buf.NumLines - v.height
944 // CursorPageUp places the cursor a page up
945 func (v *View) CursorPageUp() bool {
946 if v.Cursor.HasSelection() {
947 v.Cursor.Loc = v.Cursor.CurSelection[0]
948 v.Cursor.ResetSelection()
950 v.Cursor.UpN(v.height)
954 // CursorPageDown places the cursor a page up
955 func (v *View) CursorPageDown() bool {
956 if v.Cursor.HasSelection() {
957 v.Cursor.Loc = v.Cursor.CurSelection[1]
958 v.Cursor.ResetSelection()
960 v.Cursor.DownN(v.height)
964 // HalfPageUp scrolls the view up half a page
965 func (v *View) HalfPageUp() bool {
966 if v.Topline > v.height/2 {
967 v.ScrollUp(v.height / 2)
974 // HalfPageDown scrolls the view down half a page
975 func (v *View) HalfPageDown() bool {
976 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
977 v.ScrollDown(v.height / 2)
979 if v.Buf.NumLines >= v.height {
980 v.Topline = v.Buf.NumLines - v.height
986 // ToggleRuler turns line numbers off and on
987 func (v *View) ToggleRuler() bool {
988 if settings["ruler"] == false {
989 settings["ruler"] = true
990 messenger.Message("Enabled ruler")
992 settings["ruler"] = false
993 messenger.Message("Disabled ruler")
998 // JumpLine jumps to a line and moves the view accordingly.
999 func (v *View) JumpLine() bool {
1000 // Prompt for line number
1001 linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber")
1005 lineint, err := strconv.Atoi(linestring)
1006 lineint = lineint - 1 // fix offset
1008 messenger.Error(err) // return errors
1011 // Move cursor and view if possible.
1012 if lineint < v.Buf.NumLines {
1014 v.Cursor.Y = lineint
1017 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1021 // ClearStatus clears the messenger bar
1022 func (v *View) ClearStatus() bool {
1023 messenger.Message("")
1027 // ToggleHelp toggles the help screen
1028 func (v *View) ToggleHelp() bool {
1030 v.lastBuffer = v.Buf
1031 helpBuffer := NewBuffer([]byte(helpTxt), "help.md")
1032 helpBuffer.Name = "Help"
1034 v.OpenBuffer(helpBuffer)
1036 v.OpenBuffer(v.lastBuffer)
1042 // ShellMode opens a terminal to run a shell command
1043 func (v *View) ShellMode() bool {
1044 input, canceled := messenger.Prompt("$ ", "Shell")
1046 // The true here is for openTerm to make the command interactive
1047 HandleShellCommand(input, true)
1052 // CommandMode lets the user enter a command
1053 func (v *View) CommandMode() bool {
1054 input, canceled := messenger.Prompt("> ", "Command")
1056 HandleCommand(input)
1061 // Quit quits the editor
1062 // This behavior needs to be changed and should really only quit the editor if this
1064 // However, since micro only supports one view for now, it doesn't really matter
1065 func (v *View) Quit() bool {
1067 return v.ToggleHelp()
1069 // Make sure not to quit if there are unsaved changes
1070 if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
1071 views[mainView].CloseBuffer()
1078 // None is no action