9 "github.com/yuin/gopher-lua"
10 "github.com/zyedidia/clipboard"
13 // PreActionCall executes the lua pre callback if possible
14 func PreActionCall(funcName string, view *View) bool {
16 for pl := range loadedPlugins {
17 ret, err := Call(pl+".pre"+funcName, view)
18 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
22 if ret == lua.LFalse {
29 // PostActionCall executes the lua plugin callback if possible
30 func PostActionCall(funcName string, view *View) bool {
32 for pl := range loadedPlugins {
33 ret, err := Call(pl+".on"+funcName, view)
34 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
38 if ret == lua.LFalse {
45 func (v *View) deselect(index int) bool {
46 if v.Cursor.HasSelection() {
47 v.Cursor.Loc = v.Cursor.CurSelection[index]
48 v.Cursor.ResetSelection()
54 // Center centers the view on the cursor
55 func (v *View) Center(usePlugin bool) bool {
56 if usePlugin && !PreActionCall("Center", v) {
60 v.Topline = v.Cursor.Y - v.Height/2
61 if v.Topline+v.Height > v.Buf.NumLines {
62 v.Topline = v.Buf.NumLines - v.Height
69 return PostActionCall("Center", v)
74 // CursorUp moves the cursor up
75 func (v *View) CursorUp(usePlugin bool) bool {
76 if usePlugin && !PreActionCall("CursorUp", v) {
84 return PostActionCall("CursorUp", v)
89 // CursorDown moves the cursor down
90 func (v *View) CursorDown(usePlugin bool) bool {
91 if usePlugin && !PreActionCall("CursorDown", v) {
99 return PostActionCall("CursorDown", v)
104 // CursorLeft moves the cursor left
105 func (v *View) CursorLeft(usePlugin bool) bool {
106 if usePlugin && !PreActionCall("CursorLeft", v) {
110 if v.Cursor.HasSelection() {
111 v.Cursor.Loc = v.Cursor.CurSelection[0]
112 v.Cursor.ResetSelection()
118 return PostActionCall("CursorLeft", v)
123 // CursorRight moves the cursor right
124 func (v *View) CursorRight(usePlugin bool) bool {
125 if usePlugin && !PreActionCall("CursorRight", v) {
129 if v.Cursor.HasSelection() {
130 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
131 v.Cursor.ResetSelection()
137 return PostActionCall("CursorRight", v)
142 // WordRight moves the cursor one word to the right
143 func (v *View) WordRight(usePlugin bool) bool {
144 if usePlugin && !PreActionCall("WordRight", v) {
151 return PostActionCall("WordRight", v)
156 // WordLeft moves the cursor one word to the left
157 func (v *View) WordLeft(usePlugin bool) bool {
158 if usePlugin && !PreActionCall("WordLeft", v) {
165 return PostActionCall("WordLeft", v)
170 // SelectUp selects up one line
171 func (v *View) SelectUp(usePlugin bool) bool {
172 if usePlugin && !PreActionCall("SelectUp", v) {
176 if !v.Cursor.HasSelection() {
177 v.Cursor.OrigSelection[0] = v.Cursor.Loc
180 v.Cursor.SelectTo(v.Cursor.Loc)
183 return PostActionCall("SelectUp", v)
188 // SelectDown selects down one line
189 func (v *View) SelectDown(usePlugin bool) bool {
190 if usePlugin && !PreActionCall("SelectDown", v) {
194 if !v.Cursor.HasSelection() {
195 v.Cursor.OrigSelection[0] = v.Cursor.Loc
198 v.Cursor.SelectTo(v.Cursor.Loc)
201 return PostActionCall("SelectDown", v)
206 // SelectLeft selects the character to the left of the cursor
207 func (v *View) SelectLeft(usePlugin bool) bool {
208 if usePlugin && !PreActionCall("SelectLeft", v) {
213 count := v.Buf.End().Move(-1, v.Buf)
214 if loc.GreaterThan(count) {
217 if !v.Cursor.HasSelection() {
218 v.Cursor.OrigSelection[0] = loc
221 v.Cursor.SelectTo(v.Cursor.Loc)
224 return PostActionCall("SelectLeft", v)
229 // SelectRight selects the character to the right of the cursor
230 func (v *View) SelectRight(usePlugin bool) bool {
231 if usePlugin && !PreActionCall("SelectRight", v) {
236 count := v.Buf.End().Move(-1, v.Buf)
237 if loc.GreaterThan(count) {
240 if !v.Cursor.HasSelection() {
241 v.Cursor.OrigSelection[0] = loc
244 v.Cursor.SelectTo(v.Cursor.Loc)
247 return PostActionCall("SelectRight", v)
252 // SelectWordRight selects the word to the right of the cursor
253 func (v *View) SelectWordRight(usePlugin bool) bool {
254 if usePlugin && !PreActionCall("SelectWordRight", v) {
258 if !v.Cursor.HasSelection() {
259 v.Cursor.OrigSelection[0] = v.Cursor.Loc
262 v.Cursor.SelectTo(v.Cursor.Loc)
265 return PostActionCall("SelectWordRight", v)
270 // SelectWordLeft selects the word to the left of the cursor
271 func (v *View) SelectWordLeft(usePlugin bool) bool {
272 if usePlugin && !PreActionCall("SelectWordLeft", v) {
276 if !v.Cursor.HasSelection() {
277 v.Cursor.OrigSelection[0] = v.Cursor.Loc
280 v.Cursor.SelectTo(v.Cursor.Loc)
283 return PostActionCall("SelectWordLeft", v)
288 // StartOfLine moves the cursor to the start of the line
289 func (v *View) StartOfLine(usePlugin bool) bool {
290 if usePlugin && !PreActionCall("StartOfLine", v) {
299 return PostActionCall("StartOfLine", v)
304 // EndOfLine moves the cursor to the end of the line
305 func (v *View) EndOfLine(usePlugin bool) bool {
306 if usePlugin && !PreActionCall("EndOfLine", v) {
315 return PostActionCall("EndOfLine", v)
320 // SelectToStartOfLine selects to the start of the current line
321 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
322 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
326 if !v.Cursor.HasSelection() {
327 v.Cursor.OrigSelection[0] = v.Cursor.Loc
330 v.Cursor.SelectTo(v.Cursor.Loc)
333 return PostActionCall("SelectToStartOfLine", v)
338 // SelectToEndOfLine selects to the end of the current line
339 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
340 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
344 if !v.Cursor.HasSelection() {
345 v.Cursor.OrigSelection[0] = v.Cursor.Loc
348 v.Cursor.SelectTo(v.Cursor.Loc)
351 return PostActionCall("SelectToEndOfLine", v)
356 // CursorStart moves the cursor to the start of the buffer
357 func (v *View) CursorStart(usePlugin bool) bool {
358 if usePlugin && !PreActionCall("CursorStart", v) {
368 return PostActionCall("CursorStart", v)
373 // CursorEnd moves the cursor to the end of the buffer
374 func (v *View) CursorEnd(usePlugin bool) bool {
375 if usePlugin && !PreActionCall("CursorEnd", v) {
381 v.Cursor.Loc = v.Buf.End()
384 return PostActionCall("CursorEnd", v)
389 // SelectToStart selects the text from the cursor to the start of the buffer
390 func (v *View) SelectToStart(usePlugin bool) bool {
391 if usePlugin && !PreActionCall("SelectToStart", v) {
395 if !v.Cursor.HasSelection() {
396 v.Cursor.OrigSelection[0] = v.Cursor.Loc
399 v.Cursor.SelectTo(v.Buf.Start())
402 return PostActionCall("SelectToStart", v)
407 // SelectToEnd selects the text from the cursor to the end of the buffer
408 func (v *View) SelectToEnd(usePlugin bool) bool {
409 if usePlugin && !PreActionCall("SelectToEnd", v) {
413 if !v.Cursor.HasSelection() {
414 v.Cursor.OrigSelection[0] = v.Cursor.Loc
417 v.Cursor.SelectTo(v.Buf.End())
420 return PostActionCall("SelectToEnd", v)
425 // InsertSpace inserts a space
426 func (v *View) InsertSpace(usePlugin bool) bool {
427 if usePlugin && !PreActionCall("InsertSpace", v) {
431 if v.Cursor.HasSelection() {
432 v.Cursor.DeleteSelection()
433 v.Cursor.ResetSelection()
435 v.Buf.Insert(v.Cursor.Loc, " ")
439 return PostActionCall("InsertSpace", v)
444 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
445 func (v *View) InsertNewline(usePlugin bool) bool {
446 if usePlugin && !PreActionCall("InsertNewline", v) {
451 if v.Cursor.HasSelection() {
452 v.Cursor.DeleteSelection()
453 v.Cursor.ResetSelection()
456 v.Buf.Insert(v.Cursor.Loc, "\n")
457 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
460 if v.Buf.Settings["autoindent"].(bool) {
461 v.Buf.Insert(v.Cursor.Loc, ws)
462 for i := 0; i < len(ws); i++ {
466 // Remove the whitespaces if keepautoindent setting is off
467 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) && !v.Buf.Settings["keepautoindent"].(bool) {
468 line := v.Buf.Line(v.Cursor.Y - 1)
469 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
472 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
475 return PostActionCall("InsertNewline", v)
480 // Backspace deletes the previous character
481 func (v *View) Backspace(usePlugin bool) bool {
482 if usePlugin && !PreActionCall("Backspace", v) {
486 // Delete a character
487 if v.Cursor.HasSelection() {
488 v.Cursor.DeleteSelection()
489 v.Cursor.ResetSelection()
490 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
491 // We have to do something a bit hacky here because we want to
492 // delete the line by first moving left and then deleting backwards
493 // but the undo redo would place the cursor in the wrong place
494 // So instead we move left, save the position, move back, delete
495 // and restore the position
497 // If the user is using spaces instead of tabs and they are deleting
498 // whitespace at the start of the line, we should delete as if it's a
499 // tab (tabSize number of spaces)
500 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
501 tabSize := int(v.Buf.Settings["tabsize"].(float64))
502 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
504 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
505 cx, cy := v.Cursor.X, v.Cursor.Y
507 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
508 v.Cursor.X, v.Cursor.Y = cx, cy
511 cx, cy := v.Cursor.X, v.Cursor.Y
514 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
515 v.Cursor.X, v.Cursor.Y = cx, cy
518 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
521 return PostActionCall("Backspace", v)
526 // DeleteWordRight deletes the word to the right of the cursor
527 func (v *View) DeleteWordRight(usePlugin bool) bool {
528 if usePlugin && !PreActionCall("DeleteWordRight", v) {
532 v.SelectWordRight(false)
533 if v.Cursor.HasSelection() {
534 v.Cursor.DeleteSelection()
535 v.Cursor.ResetSelection()
539 return PostActionCall("DeleteWordRight", v)
544 // DeleteWordLeft deletes the word to the left of the cursor
545 func (v *View) DeleteWordLeft(usePlugin bool) bool {
546 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
550 v.SelectWordLeft(false)
551 if v.Cursor.HasSelection() {
552 v.Cursor.DeleteSelection()
553 v.Cursor.ResetSelection()
557 return PostActionCall("DeleteWordLeft", v)
562 // Delete deletes the next character
563 func (v *View) Delete(usePlugin bool) bool {
564 if usePlugin && !PreActionCall("Delete", v) {
568 if v.Cursor.HasSelection() {
569 v.Cursor.DeleteSelection()
570 v.Cursor.ResetSelection()
573 if loc.LessThan(v.Buf.End()) {
574 v.Buf.Remove(loc, loc.Move(1, v.Buf))
579 return PostActionCall("Delete", v)
584 // IndentSelection indents the current selection
585 func (v *View) IndentSelection(usePlugin bool) bool {
586 if usePlugin && !PreActionCall("IndentSelection", v) {
590 if v.Cursor.HasSelection() {
591 startY := v.Cursor.CurSelection[0].Y
592 endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
593 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
594 for y := startY; y <= endY; y++ {
595 tabsize := len(v.Buf.IndentString())
596 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
597 if y == startY && v.Cursor.CurSelection[0].X > 0 {
598 v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(tabsize, v.Buf))
601 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
607 return PostActionCall("IndentSelection", v)
614 // OutdentLine moves the current line back one indentation
615 func (v *View) OutdentLine(usePlugin bool) bool {
616 if usePlugin && !PreActionCall("OutdentLine", v) {
620 if v.Cursor.HasSelection() {
624 for x := 0; x < len(v.Buf.IndentString()); x++ {
625 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
628 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
634 return PostActionCall("OutdentLine", v)
639 // OutdentSelection takes the current selection and moves it back one indent level
640 func (v *View) OutdentSelection(usePlugin bool) bool {
641 if usePlugin && !PreActionCall("OutdentSelection", v) {
645 if v.Cursor.HasSelection() {
646 startY := v.Cursor.CurSelection[0].Y
647 endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
648 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
649 for y := startY; y <= endY; y++ {
650 for x := 0; x < len(v.Buf.IndentString()); x++ {
651 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
654 v.Buf.Remove(Loc{0, y}, Loc{1, y})
655 if y == startY && v.Cursor.CurSelection[0].X > 0 {
656 v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
659 v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
666 return PostActionCall("OutdentSelection", v)
673 // InsertTab inserts a tab or spaces
674 func (v *View) InsertTab(usePlugin bool) bool {
675 if usePlugin && !PreActionCall("InsertTab", v) {
679 if v.Cursor.HasSelection() {
683 tabBytes := len(v.Buf.IndentString())
684 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
685 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
686 for i := 0; i < bytesUntilIndent; i++ {
691 return PostActionCall("InsertTab", v)
696 // Save the buffer to disk
697 func (v *View) Save(usePlugin bool) bool {
698 if usePlugin && !PreActionCall("Save", v) {
702 if v.Type == vtHelp {
703 // We can't save the help text
706 // If this is an empty buffer, ask for a filename
707 if v.Buf.Path == "" {
712 if strings.HasSuffix(err.Error(), "permission denied") {
713 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
715 err = v.Buf.SaveWithSudo()
717 messenger.Error(err.Error())
720 messenger.Message("Saved " + v.Buf.Path)
725 messenger.Error(err.Error())
728 messenger.Message("Saved " + v.Buf.Path)
732 return PostActionCall("Save", v)
737 // SaveAs saves the buffer to disk with the given name
738 func (v *View) SaveAs(usePlugin bool) bool {
739 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
741 // the filename might or might not be quoted, so unquote first then join the strings.
742 filename = strings.Join(SplitCommandArgs(filename), " ")
743 v.Buf.Path = filename
744 v.Buf.name = filename
752 // Find opens a prompt and searches forward for the input
753 func (v *View) Find(usePlugin bool) bool {
754 if usePlugin && !PreActionCall("Find", v) {
759 if v.Cursor.HasSelection() {
760 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
761 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
762 searchStr = v.Cursor.GetSelection()
764 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
766 BeginSearch(searchStr)
769 return PostActionCall("Find", v)
774 // FindNext searches forwards for the last used search term
775 func (v *View) FindNext(usePlugin bool) bool {
776 if usePlugin && !PreActionCall("FindNext", v) {
780 if v.Cursor.HasSelection() {
781 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
782 lastSearch = v.Cursor.GetSelection()
784 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
786 if lastSearch == "" {
789 messenger.Message("Finding: " + lastSearch)
790 Search(lastSearch, v, true)
793 return PostActionCall("FindNext", v)
798 // FindPrevious searches backwards for the last used search term
799 func (v *View) FindPrevious(usePlugin bool) bool {
800 if usePlugin && !PreActionCall("FindPrevious", v) {
804 if v.Cursor.HasSelection() {
805 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
807 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
809 messenger.Message("Finding: " + lastSearch)
810 Search(lastSearch, v, false)
813 return PostActionCall("FindPrevious", v)
818 // Undo undoes the last action
819 func (v *View) Undo(usePlugin bool) bool {
820 if usePlugin && !PreActionCall("Undo", v) {
825 messenger.Message("Undid action")
828 return PostActionCall("Undo", v)
833 // Redo redoes the last action
834 func (v *View) Redo(usePlugin bool) bool {
835 if usePlugin && !PreActionCall("Redo", v) {
840 messenger.Message("Redid action")
843 return PostActionCall("Redo", v)
848 // Copy the selection to the system clipboard
849 func (v *View) Copy(usePlugin bool) bool {
850 if usePlugin && !PreActionCall("Copy", v) {
854 if v.Cursor.HasSelection() {
855 clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
857 messenger.Message("Copied selection")
861 return PostActionCall("Copy", v)
866 // CutLine cuts the current line to the clipboard
867 func (v *View) CutLine(usePlugin bool) bool {
868 if usePlugin && !PreActionCall("CutLine", v) {
872 v.Cursor.SelectLine()
873 if !v.Cursor.HasSelection() {
876 if v.freshClip == true {
877 if v.Cursor.HasSelection() {
878 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
881 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
884 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
888 v.lastCutTime = time.Now()
889 v.Cursor.DeleteSelection()
890 v.Cursor.ResetSelection()
891 messenger.Message("Cut line")
894 return PostActionCall("CutLine", v)
899 // Cut the selection to the system clipboard
900 func (v *View) Cut(usePlugin bool) bool {
901 if usePlugin && !PreActionCall("Cut", v) {
905 if v.Cursor.HasSelection() {
906 clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
907 v.Cursor.DeleteSelection()
908 v.Cursor.ResetSelection()
910 messenger.Message("Cut selection")
913 return PostActionCall("Cut", v)
921 // DuplicateLine duplicates the current line or selection
922 func (v *View) DuplicateLine(usePlugin bool) bool {
923 if usePlugin && !PreActionCall("DuplicateLine", v) {
927 if v.Cursor.HasSelection() {
928 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
931 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
935 messenger.Message("Duplicated line")
938 return PostActionCall("DuplicateLine", v)
943 // DeleteLine deletes the current line
944 func (v *View) DeleteLine(usePlugin bool) bool {
945 if usePlugin && !PreActionCall("DeleteLine", v) {
949 v.Cursor.SelectLine()
950 if !v.Cursor.HasSelection() {
953 v.Cursor.DeleteSelection()
954 v.Cursor.ResetSelection()
955 messenger.Message("Deleted line")
958 return PostActionCall("DeleteLine", v)
963 // MoveLinesUp moves up the current line or selected lines if any
964 func (v *View) MoveLinesUp(usePlugin bool) bool {
965 if usePlugin && !PreActionCall("MoveLinesUp", v) {
969 if v.Cursor.HasSelection() {
970 if v.Cursor.CurSelection[0].Y == 0 {
971 messenger.Message("Can not move further up")
975 v.Cursor.CurSelection[0].Y,
976 v.Cursor.CurSelection[1].Y,
979 v.Cursor.CurSelection[0].Y -= 1
980 v.Cursor.CurSelection[1].Y -= 1
981 messenger.Message("Moved up selected line(s)")
983 if v.Cursor.Loc.Y == 0 {
984 messenger.Message("Can not move further up")
992 messenger.Message("Moved up current line")
994 v.Buf.IsModified = true
997 return PostActionCall("MoveLinesUp", v)
1002 // MoveLinesDown moves down the current line or selected lines if any
1003 func (v *View) MoveLinesDown(usePlugin bool) bool {
1004 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1008 if v.Cursor.HasSelection() {
1009 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1010 messenger.Message("Can not move further down")
1013 v.Buf.MoveLinesDown(
1014 v.Cursor.CurSelection[0].Y,
1015 v.Cursor.CurSelection[1].Y,
1018 v.Cursor.CurSelection[0].Y += 1
1019 v.Cursor.CurSelection[1].Y += 1
1020 messenger.Message("Moved down selected line(s)")
1022 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1023 messenger.Message("Can not move further down")
1026 v.Buf.MoveLinesDown(
1031 messenger.Message("Moved down current line")
1033 v.Buf.IsModified = true
1036 return PostActionCall("MoveLinesDown", v)
1041 // Paste whatever is in the system clipboard into the buffer
1042 // Delete and paste if the user has a selection
1043 func (v *View) Paste(usePlugin bool) bool {
1044 if usePlugin && !PreActionCall("Paste", v) {
1048 clip, _ := clipboard.ReadAll("clipboard")
1052 return PostActionCall("Paste", v)
1057 // PastePrimary pastes from the primary clipboard (only use on linux)
1058 func (v *View) PastePrimary(usePlugin bool) bool {
1059 if usePlugin && !PreActionCall("Paste", v) {
1063 clip, _ := clipboard.ReadAll("primary")
1067 return PostActionCall("Paste", v)
1072 // SelectAll selects the entire buffer
1073 func (v *View) SelectAll(usePlugin bool) bool {
1074 if usePlugin && !PreActionCall("SelectAll", v) {
1078 v.Cursor.SetSelectionStart(v.Buf.Start())
1079 v.Cursor.SetSelectionEnd(v.Buf.End())
1080 // Put the cursor at the beginning
1085 return PostActionCall("SelectAll", v)
1090 // OpenFile opens a new file in the buffer
1091 func (v *View) OpenFile(usePlugin bool) bool {
1092 if usePlugin && !PreActionCall("OpenFile", v) {
1097 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1099 HandleCommand(input)
1101 return PostActionCall("OpenFile", v)
1108 // Start moves the viewport to the start of the buffer
1109 func (v *View) Start(usePlugin bool) bool {
1110 if usePlugin && !PreActionCall("Start", v) {
1117 return PostActionCall("Start", v)
1122 // End moves the viewport to the end of the buffer
1123 func (v *View) End(usePlugin bool) bool {
1124 if usePlugin && !PreActionCall("End", v) {
1128 if v.Height > v.Buf.NumLines {
1131 v.Topline = v.Buf.NumLines - v.Height
1135 return PostActionCall("End", v)
1140 // PageUp scrolls the view up a page
1141 func (v *View) PageUp(usePlugin bool) bool {
1142 if usePlugin && !PreActionCall("PageUp", v) {
1146 if v.Topline > v.Height {
1147 v.ScrollUp(v.Height)
1153 return PostActionCall("PageUp", v)
1158 // PageDown scrolls the view down a page
1159 func (v *View) PageDown(usePlugin bool) bool {
1160 if usePlugin && !PreActionCall("PageDown", v) {
1164 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1165 v.ScrollDown(v.Height)
1166 } else if v.Buf.NumLines >= v.Height {
1167 v.Topline = v.Buf.NumLines - v.Height
1171 return PostActionCall("PageDown", v)
1176 // CursorPageUp places the cursor a page up
1177 func (v *View) CursorPageUp(usePlugin bool) bool {
1178 if usePlugin && !PreActionCall("CursorPageUp", v) {
1184 if v.Cursor.HasSelection() {
1185 v.Cursor.Loc = v.Cursor.CurSelection[0]
1186 v.Cursor.ResetSelection()
1188 v.Cursor.UpN(v.Height)
1191 return PostActionCall("CursorPageUp", v)
1196 // CursorPageDown places the cursor a page up
1197 func (v *View) CursorPageDown(usePlugin bool) bool {
1198 if usePlugin && !PreActionCall("CursorPageDown", v) {
1204 if v.Cursor.HasSelection() {
1205 v.Cursor.Loc = v.Cursor.CurSelection[1]
1206 v.Cursor.ResetSelection()
1208 v.Cursor.DownN(v.Height)
1211 return PostActionCall("CursorPageDown", v)
1216 // HalfPageUp scrolls the view up half a page
1217 func (v *View) HalfPageUp(usePlugin bool) bool {
1218 if usePlugin && !PreActionCall("HalfPageUp", v) {
1222 if v.Topline > v.Height/2 {
1223 v.ScrollUp(v.Height / 2)
1229 return PostActionCall("HalfPageUp", v)
1234 // HalfPageDown scrolls the view down half a page
1235 func (v *View) HalfPageDown(usePlugin bool) bool {
1236 if usePlugin && !PreActionCall("HalfPageDown", v) {
1240 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1241 v.ScrollDown(v.Height / 2)
1243 if v.Buf.NumLines >= v.Height {
1244 v.Topline = v.Buf.NumLines - v.Height
1249 return PostActionCall("HalfPageDown", v)
1254 // ToggleRuler turns line numbers off and on
1255 func (v *View) ToggleRuler(usePlugin bool) bool {
1256 if usePlugin && !PreActionCall("ToggleRuler", v) {
1260 if v.Buf.Settings["ruler"] == false {
1261 v.Buf.Settings["ruler"] = true
1262 messenger.Message("Enabled ruler")
1264 v.Buf.Settings["ruler"] = false
1265 messenger.Message("Disabled ruler")
1269 return PostActionCall("ToggleRuler", v)
1274 // JumpLine jumps to a line and moves the view accordingly.
1275 func (v *View) JumpLine(usePlugin bool) bool {
1276 if usePlugin && !PreActionCall("JumpLine", v) {
1280 // Prompt for line number
1281 linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1285 lineint, err := strconv.Atoi(linestring)
1286 lineint = lineint - 1 // fix offset
1288 messenger.Error(err) // return errors
1291 // Move cursor and view if possible.
1292 if lineint < v.Buf.NumLines && lineint >= 0 {
1294 v.Cursor.Y = lineint
1297 return PostActionCall("JumpLine", v)
1301 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1305 // ClearStatus clears the messenger bar
1306 func (v *View) ClearStatus(usePlugin bool) bool {
1307 if usePlugin && !PreActionCall("ClearStatus", v) {
1311 messenger.Message("")
1314 return PostActionCall("ClearStatus", v)
1319 // ToggleHelp toggles the help screen
1320 func (v *View) ToggleHelp(usePlugin bool) bool {
1321 if usePlugin && !PreActionCall("ToggleHelp", v) {
1325 if v.Type != vtHelp {
1326 // Open the default help
1333 return PostActionCall("ToggleHelp", v)
1338 // ShellMode opens a terminal to run a shell command
1339 func (v *View) ShellMode(usePlugin bool) bool {
1340 if usePlugin && !PreActionCall("ShellMode", v) {
1344 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1346 // The true here is for openTerm to make the command interactive
1347 HandleShellCommand(input, true, true)
1349 return PostActionCall("ShellMode", v)
1355 // CommandMode lets the user enter a command
1356 func (v *View) CommandMode(usePlugin bool) bool {
1357 if usePlugin && !PreActionCall("CommandMode", v) {
1361 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1363 HandleCommand(input)
1365 return PostActionCall("CommandMode", v)
1372 // Escape leaves current mode
1373 func (v *View) Escape(usePlugin bool) bool {
1374 // check if user is searching, or the last search is still active
1375 if searching || lastSearch != "" {
1379 // check if a prompt is shown, hide it and don't quit
1380 if messenger.hasPrompt {
1381 messenger.Reset() // FIXME
1388 // Quit this will close the current tab or view that is open
1389 func (v *View) Quit(usePlugin bool) bool {
1390 if usePlugin && !PreActionCall("Quit", v) {
1394 // Make sure not to quit if there are unsaved changes
1397 if len(tabs[curTab].views) > 1 {
1398 v.splitNode.Delete()
1399 tabs[v.TabNum].Cleanup()
1400 tabs[v.TabNum].Resize()
1401 } else if len(tabs) > 1 {
1402 if len(tabs[v.TabNum].views) == 1 {
1403 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1404 for i, t := range tabs {
1407 if curTab >= len(tabs) {
1411 CurView().ToggleTabbar()
1412 CurView().matches = Match(CurView())
1417 PostActionCall("Quit", v)
1426 return PostActionCall("Quit", v)
1431 // QuitAll quits the whole editor; all splits and tabs
1432 func (v *View) QuitAll(usePlugin bool) bool {
1433 if usePlugin && !PreActionCall("QuitAll", v) {
1438 for _, tab := range tabs {
1439 for _, v := range tab.views {
1447 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1448 should_quit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1451 for _, tab := range tabs {
1452 for _, v := range tab.views {
1458 PostActionCall("QuitAll", v)
1469 // AddTab adds a new tab with an empty buffer
1470 func (v *View) AddTab(usePlugin bool) bool {
1471 if usePlugin && !PreActionCall("AddTab", v) {
1475 tab := NewTabFromView(NewView(NewBuffer(strings.NewReader(""), "")))
1476 tab.SetNum(len(tabs))
1477 tabs = append(tabs, tab)
1478 curTab = len(tabs) - 1
1480 for _, t := range tabs {
1481 for _, v := range t.views {
1488 return PostActionCall("AddTab", v)
1493 // PreviousTab switches to the previous tab in the tab list
1494 func (v *View) PreviousTab(usePlugin bool) bool {
1495 if usePlugin && !PreActionCall("PreviousTab", v) {
1501 } else if curTab == 0 {
1502 curTab = len(tabs) - 1
1506 return PostActionCall("PreviousTab", v)
1511 // NextTab switches to the next tab in the tab list
1512 func (v *View) NextTab(usePlugin bool) bool {
1513 if usePlugin && !PreActionCall("NextTab", v) {
1517 if curTab < len(tabs)-1 {
1519 } else if curTab == len(tabs)-1 {
1524 return PostActionCall("NextTab", v)
1529 // VSplitBinding opens an empty vertical split
1530 func (v *View) VSplitBinding(usePlugin bool) bool {
1531 if usePlugin && !PreActionCall("VSplit", v) {
1535 v.VSplit(NewBuffer(strings.NewReader(""), ""))
1538 return PostActionCall("VSplit", v)
1543 // HSplitBinding opens an empty horizontal split
1544 func (v *View) HSplitBinding(usePlugin bool) bool {
1545 if usePlugin && !PreActionCall("HSplit", v) {
1549 v.HSplit(NewBuffer(strings.NewReader(""), ""))
1552 return PostActionCall("HSplit", v)
1557 // Unsplit closes all splits in the current tab except the active one
1558 func (v *View) Unsplit(usePlugin bool) bool {
1559 if usePlugin && !PreActionCall("Unsplit", v) {
1563 curView := tabs[curTab].CurView
1564 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1565 view := tabs[curTab].views[i]
1566 if view != nil && view.Num != curView {
1568 // messenger.Message("Quit ", view.Buf.Path)
1573 return PostActionCall("Unsplit", v)
1578 // NextSplit changes the view to the next split
1579 func (v *View) NextSplit(usePlugin bool) bool {
1580 if usePlugin && !PreActionCall("NextSplit", v) {
1585 if tab.CurView < len(tab.views)-1 {
1592 return PostActionCall("NextSplit", v)
1597 // PreviousSplit changes the view to the previous split
1598 func (v *View) PreviousSplit(usePlugin bool) bool {
1599 if usePlugin && !PreActionCall("PreviousSplit", v) {
1604 if tab.CurView > 0 {
1607 tab.CurView = len(tab.views) - 1
1611 return PostActionCall("PreviousSplit", v)
1616 var curMacro []interface{}
1617 var recordingMacro bool
1619 // ToggleMacro toggles recording of a macro
1620 func (v *View) ToggleMacro(usePlugin bool) bool {
1621 if usePlugin && !PreActionCall("ToggleMacro", v) {
1625 recordingMacro = !recordingMacro
1628 curMacro = []interface{}{}
1629 messenger.Message("Recording")
1631 messenger.Message("Stopped recording")
1635 return PostActionCall("ToggleMacro", v)
1640 // PlayMacro plays back the most recently recorded macro
1641 func (v *View) PlayMacro(usePlugin bool) bool {
1642 if usePlugin && !PreActionCall("PlayMacro", v) {
1646 for _, action := range curMacro {
1647 switch t := action.(type) {
1649 // Insert a character
1650 if v.Cursor.HasSelection() {
1651 v.Cursor.DeleteSelection()
1652 v.Cursor.ResetSelection()
1654 v.Buf.Insert(v.Cursor.Loc, string(t))
1657 for pl := range loadedPlugins {
1658 _, err := Call(pl+".onRune", string(t), v)
1659 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1663 case func(*View, bool) bool:
1669 return PostActionCall("PlayMacro", v)
1674 // None is no action