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.SetLoc(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.SetLoc(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.SetLoc(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.SetLoc(v.Cursor.CurSelection[1] - 1)
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 loc := v.Cursor.Loc()
462 if !v.Cursor.HasSelection() {
463 v.Cursor.OrigSelection[0] = loc
466 v.Cursor.SelectTo(v.Cursor.Loc())
470 // SelectDown selects down one line
471 func (v *View) SelectDown() bool {
472 loc := v.Cursor.Loc()
473 if !v.Cursor.HasSelection() {
474 v.Cursor.OrigSelection[0] = loc
477 v.Cursor.SelectTo(v.Cursor.Loc())
481 // SelectLeft selects the character to the left of the cursor
482 func (v *View) SelectLeft() bool {
483 loc := v.Cursor.Loc()
484 count := v.Buf.Len() - 1
488 if !v.Cursor.HasSelection() {
489 v.Cursor.OrigSelection[0] = loc
492 v.Cursor.SelectTo(v.Cursor.Loc())
496 // SelectRight selects the character to the right of the cursor
497 func (v *View) SelectRight() bool {
498 loc := v.Cursor.Loc()
499 count := v.Buf.Len() - 1
503 if !v.Cursor.HasSelection() {
504 v.Cursor.OrigSelection[0] = loc
507 v.Cursor.SelectTo(v.Cursor.Loc())
511 // SelectWordRight selects the word to the right of the cursor
512 func (v *View) SelectWordRight() bool {
513 loc := v.Cursor.Loc()
514 if !v.Cursor.HasSelection() {
515 v.Cursor.OrigSelection[0] = loc
518 v.Cursor.SelectTo(v.Cursor.Loc())
522 // SelectWordLeft selects the word to the left of the cursor
523 func (v *View) SelectWordLeft() bool {
524 loc := v.Cursor.Loc()
525 if !v.Cursor.HasSelection() {
526 v.Cursor.OrigSelection[0] = loc
529 v.Cursor.SelectTo(v.Cursor.Loc())
533 // StartOfLine moves the cursor to the start of the line
534 func (v *View) StartOfLine() bool {
539 // EndOfLine moves the cursor to the end of the line
540 func (v *View) EndOfLine() bool {
545 // SelectToStartOfLine selects to the start of the current line
546 func (v *View) SelectToStartOfLine() bool {
547 loc := v.Cursor.Loc()
548 if !v.Cursor.HasSelection() {
549 v.Cursor.OrigSelection[0] = 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 loc := v.Cursor.Loc()
559 if !v.Cursor.HasSelection() {
560 v.Cursor.OrigSelection[0] = loc
563 v.Cursor.SelectTo(v.Cursor.Loc())
567 // CursorStart moves the cursor to the start of the buffer
568 func (v *View) CursorStart() bool {
574 // CursorEnd moves the cursor to the end of the buffer
575 func (v *View) CursorEnd() bool {
576 v.Cursor.SetLoc(v.Buf.Len())
580 // SelectToStart selects the text from the cursor to the start of the buffer
581 func (v *View) SelectToStart() bool {
582 loc := v.Cursor.Loc()
583 if !v.Cursor.HasSelection() {
584 v.Cursor.OrigSelection[0] = loc
591 // SelectToEnd selects the text from the cursor to the end of the buffer
592 func (v *View) SelectToEnd() bool {
593 loc := v.Cursor.Loc()
594 if !v.Cursor.HasSelection() {
595 v.Cursor.OrigSelection[0] = loc
598 v.Cursor.SelectTo(v.Buf.Len())
602 // InsertSpace inserts a space
603 func (v *View) InsertSpace() bool {
604 if v.Cursor.HasSelection() {
605 v.Cursor.DeleteSelection()
606 v.Cursor.ResetSelection()
608 v.Buf.Insert(v.Cursor.Loc(), " ")
613 // InsertEnter inserts a newline plus possible some whitespace if autoindent is on
614 func (v *View) InsertEnter() bool {
616 if v.Cursor.HasSelection() {
617 v.Cursor.DeleteSelection()
618 v.Cursor.ResetSelection()
621 v.Buf.Insert(v.Cursor.Loc(), "\n")
622 ws := GetLeadingWhitespace(v.Buf.Lines[v.Cursor.Y])
625 if settings["autoindent"].(bool) {
626 v.Buf.Insert(v.Cursor.Loc(), ws)
627 for i := 0; i < len(ws); i++ {
631 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
635 // Backspace deletes the previous character
636 func (v *View) Backspace() bool {
637 // Delete a character
638 if v.Cursor.HasSelection() {
639 v.Cursor.DeleteSelection()
640 v.Cursor.ResetSelection()
641 } else if v.Cursor.Loc() > 0 {
642 // We have to do something a bit hacky here because we want to
643 // delete the line by first moving left and then deleting backwards
644 // but the undo redo would place the cursor in the wrong place
645 // So instead we move left, save the position, move back, delete
646 // and restore the position
648 // If the user is using spaces instead of tabs and they are deleting
649 // whitespace at the start of the line, we should delete as if its a
650 // tab (tabSize number of spaces)
651 lineStart := v.Buf.Lines[v.Cursor.Y][:v.Cursor.X]
652 tabSize := int(settings["tabsize"].(float64))
653 if settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
654 loc := v.Cursor.Loc()
655 v.Cursor.SetLoc(loc - tabSize)
656 cx, cy := v.Cursor.X, v.Cursor.Y
658 v.Buf.Remove(loc-tabSize, loc)
659 v.Cursor.X, v.Cursor.Y = cx, cy
662 cx, cy := v.Cursor.X, v.Cursor.Y
664 loc := v.Cursor.Loc()
665 v.Buf.Remove(loc-1, loc)
666 v.Cursor.X, v.Cursor.Y = cx, cy
669 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
673 // DeleteWordRight deletes the word to the right of the cursor
674 func (v *View) DeleteWordRight() bool {
676 if v.Cursor.HasSelection() {
677 v.Cursor.DeleteSelection()
678 v.Cursor.ResetSelection()
683 // DeleteWordLeft deletes the word to the left of the cursor
684 func (v *View) DeleteWordLeft() bool {
686 if v.Cursor.HasSelection() {
687 v.Cursor.DeleteSelection()
688 v.Cursor.ResetSelection()
693 // Delete deletes the next character
694 func (v *View) Delete() bool {
695 if v.Cursor.HasSelection() {
696 v.Cursor.DeleteSelection()
697 v.Cursor.ResetSelection()
699 loc := v.Cursor.Loc()
700 if loc < v.Buf.Len() {
701 v.Buf.Remove(loc, loc+1)
707 // InsertTab inserts a tab or spaces
708 func (v *View) InsertTab() bool {
710 if v.Cursor.HasSelection() {
711 v.Cursor.DeleteSelection()
712 v.Cursor.ResetSelection()
714 if settings["tabstospaces"].(bool) {
715 tabSize := int(settings["tabsize"].(float64))
716 v.Buf.Insert(v.Cursor.Loc(), Spaces(tabSize))
717 for i := 0; i < tabSize; i++ {
721 v.Buf.Insert(v.Cursor.Loc(), "\t")
727 // Save the buffer to disk
728 func (v *View) Save() bool {
730 // We can't save the help text
733 // If this is an empty buffer, ask for a filename
734 if v.Buf.Path == "" {
735 filename, canceled := messenger.Prompt("Filename: ", "Save")
737 v.Buf.Path = filename
738 v.Buf.Name = filename
745 messenger.Error(err.Error())
747 messenger.Message("Saved " + v.Buf.Path)
752 // Find opens a prompt and searches forward for the input
753 func (v *View) Find() bool {
754 if v.Cursor.HasSelection() {
755 searchStart = v.Cursor.CurSelection[1]
757 searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
763 // FindNext searches forwards for the last used search term
764 func (v *View) FindNext() bool {
765 if v.Cursor.HasSelection() {
766 searchStart = v.Cursor.CurSelection[1]
768 searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
770 messenger.Message("Finding: " + lastSearch)
771 Search(lastSearch, v, true)
775 // FindPrevious searches backwards for the last used search term
776 func (v *View) FindPrevious() bool {
777 if v.Cursor.HasSelection() {
778 searchStart = v.Cursor.CurSelection[0]
780 searchStart = ToCharPos(v.Cursor.X, v.Cursor.Y, v.Buf)
782 messenger.Message("Finding: " + lastSearch)
783 Search(lastSearch, v, false)
787 // Undo undoes the last action
788 func (v *View) Undo() bool {
790 messenger.Message("Undid action")
794 // Redo redoes the last action
795 func (v *View) Redo() bool {
797 messenger.Message("Redid action")
801 // Copy the selection to the system clipboard
802 func (v *View) Copy() bool {
803 if v.Cursor.HasSelection() {
804 clipboard.WriteAll(v.Cursor.GetSelection())
806 messenger.Message("Copied selection")
811 // CutLine cuts the current line to the clipboard
812 func (v *View) CutLine() bool {
813 v.Cursor.SelectLine()
814 if !v.Cursor.HasSelection() {
817 if v.freshClip == true {
818 if v.Cursor.HasSelection() {
819 if clip, err := clipboard.ReadAll(); err != nil {
822 clipboard.WriteAll(clip + v.Cursor.GetSelection())
825 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
829 v.lastCutTime = time.Now()
830 v.Cursor.DeleteSelection()
831 v.Cursor.ResetSelection()
832 messenger.Message("Cut line")
836 // Cut the selection to the system clipboard
837 func (v *View) Cut() bool {
838 if v.Cursor.HasSelection() {
839 clipboard.WriteAll(v.Cursor.GetSelection())
840 v.Cursor.DeleteSelection()
841 v.Cursor.ResetSelection()
843 messenger.Message("Cut selection")
848 // DuplicateLine duplicates the current line
849 func (v *View) DuplicateLine() bool {
851 v.Buf.Insert(v.Cursor.Loc(), "\n"+v.Buf.Lines[v.Cursor.Y])
853 messenger.Message("Duplicated line")
857 // Paste whatever is in the system clipboard into the buffer
858 // Delete and paste if the user has a selection
859 func (v *View) Paste() bool {
860 if v.Cursor.HasSelection() {
861 v.Cursor.DeleteSelection()
862 v.Cursor.ResetSelection()
864 clip, _ := clipboard.ReadAll()
865 v.Buf.Insert(v.Cursor.Loc(), clip)
866 v.Cursor.SetLoc(v.Cursor.Loc() + Count(clip))
868 messenger.Message("Pasted clipboard")
872 // SelectAll selects the entire buffer
873 func (v *View) SelectAll() bool {
874 v.Cursor.CurSelection[0] = 0
875 v.Cursor.CurSelection[1] = v.Buf.Len()
876 // Put the cursor at the beginning
882 // OpenFile opens a new file in the buffer
883 func (v *View) OpenFile() bool {
884 if v.CanClose("Continue? (yes, no, save) ") {
885 filename, canceled := messenger.Prompt("File to open: ", "Open")
889 home, _ := homedir.Dir()
890 filename = strings.Replace(filename, "~", home, 1)
891 file, err := ioutil.ReadFile(filename)
894 messenger.Error(err.Error())
897 buf := NewBuffer(string(file), filename)
903 // Start moves the viewport to the start of the buffer
904 func (v *View) Start() bool {
909 // End moves the viewport to the end of the buffer
910 func (v *View) End() bool {
911 if v.height > v.Buf.NumLines {
914 v.Topline = v.Buf.NumLines - v.height
919 // PageUp scrolls the view up a page
920 func (v *View) PageUp() bool {
921 if v.Topline > v.height {
929 // PageDown scrolls the view down a page
930 func (v *View) PageDown() bool {
931 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
932 v.ScrollDown(v.height)
933 } else if v.Buf.NumLines >= v.height {
934 v.Topline = v.Buf.NumLines - v.height
939 // CursorPageUp places the cursor a page up
940 func (v *View) CursorPageUp() bool {
941 if v.Cursor.HasSelection() {
942 v.Cursor.SetLoc(v.Cursor.CurSelection[0])
943 v.Cursor.ResetSelection()
945 v.Cursor.UpN(v.height)
949 // CursorPageDown places the cursor a page up
950 func (v *View) CursorPageDown() bool {
951 if v.Cursor.HasSelection() {
952 v.Cursor.SetLoc(v.Cursor.CurSelection[1])
953 v.Cursor.ResetSelection()
955 v.Cursor.DownN(v.height)
959 // HalfPageUp scrolls the view up half a page
960 func (v *View) HalfPageUp() bool {
961 if v.Topline > v.height/2 {
962 v.ScrollUp(v.height / 2)
969 // HalfPageDown scrolls the view down half a page
970 func (v *View) HalfPageDown() bool {
971 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
972 v.ScrollDown(v.height / 2)
974 if v.Buf.NumLines >= v.height {
975 v.Topline = v.Buf.NumLines - v.height
981 // ToggleRuler turns line numbers off and on
982 func (v *View) ToggleRuler() bool {
983 if settings["ruler"] == false {
984 settings["ruler"] = true
985 messenger.Message("Enabled ruler")
987 settings["ruler"] = false
988 messenger.Message("Disabled ruler")
993 // JumpLine jumps to a line and moves the view accordingly.
994 func (v *View) JumpLine() bool {
995 // Prompt for line number
996 linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber")
1000 lineint, err := strconv.Atoi(linestring)
1001 lineint = lineint - 1 // fix offset
1003 messenger.Error(err) // return errors
1006 // Move cursor and view if possible.
1007 if lineint < v.Buf.NumLines {
1009 v.Cursor.Y = lineint
1012 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1016 // ClearStatus clears the messenger bar
1017 func (v *View) ClearStatus() bool {
1018 messenger.Message("")
1022 // ToggleHelp toggles the help screen
1023 func (v *View) ToggleHelp() bool {
1025 v.lastBuffer = v.Buf
1026 helpBuffer := NewBuffer(helpTxt, "help.md")
1027 helpBuffer.Name = "Help"
1029 v.OpenBuffer(helpBuffer)
1031 v.OpenBuffer(v.lastBuffer)
1037 // ShellMode opens a terminal to run a shell command
1038 func (v *View) ShellMode() bool {
1039 input, canceled := messenger.Prompt("$ ", "Shell")
1041 // The true here is for openTerm to make the command interactive
1042 HandleShellCommand(input, true)
1047 // CommandMode lets the user enter a command
1048 func (v *View) CommandMode() bool {
1049 input, canceled := messenger.Prompt("> ", "Command")
1051 HandleCommand(input)
1056 // Quit quits the editor
1057 // This behavior needs to be changed and should really only quit the editor if this
1059 // However, since micro only supports one view for now, it doesn't really matter
1060 func (v *View) Quit() bool {
1062 return v.ToggleHelp()
1064 // Make sure not to quit if there are unsaved changes
1065 if views[mainView].CanClose("Quit anyway? (yes, no, save) ") {
1066 views[mainView].CloseBuffer()
1073 // None is no action