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 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y - 1)) {
467 line := v.Buf.Line(v.Cursor.Y - 1)
468 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
471 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
474 return PostActionCall("InsertNewline", v)
479 // Backspace deletes the previous character
480 func (v *View) Backspace(usePlugin bool) bool {
481 if usePlugin && !PreActionCall("Backspace", v) {
485 // Delete a character
486 if v.Cursor.HasSelection() {
487 v.Cursor.DeleteSelection()
488 v.Cursor.ResetSelection()
489 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
490 // We have to do something a bit hacky here because we want to
491 // delete the line by first moving left and then deleting backwards
492 // but the undo redo would place the cursor in the wrong place
493 // So instead we move left, save the position, move back, delete
494 // and restore the position
496 // If the user is using spaces instead of tabs and they are deleting
497 // whitespace at the start of the line, we should delete as if it's a
498 // tab (tabSize number of spaces)
499 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
500 tabSize := int(v.Buf.Settings["tabsize"].(float64))
501 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
503 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
504 cx, cy := v.Cursor.X, v.Cursor.Y
506 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
507 v.Cursor.X, v.Cursor.Y = cx, cy
510 cx, cy := v.Cursor.X, v.Cursor.Y
513 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
514 v.Cursor.X, v.Cursor.Y = cx, cy
517 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
520 return PostActionCall("Backspace", v)
525 // DeleteWordRight deletes the word to the right of the cursor
526 func (v *View) DeleteWordRight(usePlugin bool) bool {
527 if usePlugin && !PreActionCall("DeleteWordRight", v) {
531 v.SelectWordRight(false)
532 if v.Cursor.HasSelection() {
533 v.Cursor.DeleteSelection()
534 v.Cursor.ResetSelection()
538 return PostActionCall("DeleteWordRight", v)
543 // DeleteWordLeft deletes the word to the left of the cursor
544 func (v *View) DeleteWordLeft(usePlugin bool) bool {
545 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
549 v.SelectWordLeft(false)
550 if v.Cursor.HasSelection() {
551 v.Cursor.DeleteSelection()
552 v.Cursor.ResetSelection()
556 return PostActionCall("DeleteWordLeft", v)
561 // Delete deletes the next character
562 func (v *View) Delete(usePlugin bool) bool {
563 if usePlugin && !PreActionCall("Delete", v) {
567 if v.Cursor.HasSelection() {
568 v.Cursor.DeleteSelection()
569 v.Cursor.ResetSelection()
572 if loc.LessThan(v.Buf.End()) {
573 v.Buf.Remove(loc, loc.Move(1, v.Buf))
578 return PostActionCall("Delete", v)
583 // IndentSelection indents the current selection
584 func (v *View) IndentSelection(usePlugin bool) bool {
585 if usePlugin && !PreActionCall("IndentSelection", v) {
589 if v.Cursor.HasSelection() {
590 startY := v.Cursor.CurSelection[0].Y
591 endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
592 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
593 for y := startY; y <= endY; y++ {
594 tabsize := len(v.Buf.IndentString())
595 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
596 if y == startY && v.Cursor.CurSelection[0].X > 0 {
597 v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(tabsize, v.Buf))
600 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
606 return PostActionCall("IndentSelection", v)
613 // OutdentLine moves the current line back one indentation
614 func (v *View) OutdentLine(usePlugin bool) bool {
615 if usePlugin && !PreActionCall("OutdentLine", v) {
619 if v.Cursor.HasSelection() {
623 for x := 0; x < len(v.Buf.IndentString()); x++ {
624 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
627 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
633 return PostActionCall("OutdentLine", v)
638 // OutdentSelection takes the current selection and moves it back one indent level
639 func (v *View) OutdentSelection(usePlugin bool) bool {
640 if usePlugin && !PreActionCall("OutdentSelection", v) {
644 if v.Cursor.HasSelection() {
645 startY := v.Cursor.CurSelection[0].Y
646 endY := v.Cursor.CurSelection[1].Move(-1, v.Buf).Y
647 endX := v.Cursor.CurSelection[1].Move(-1, v.Buf).X
648 for y := startY; y <= endY; y++ {
649 for x := 0; x < len(v.Buf.IndentString()); x++ {
650 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
653 v.Buf.Remove(Loc{0, y}, Loc{1, y})
654 if y == startY && v.Cursor.CurSelection[0].X > 0 {
655 v.Cursor.SetSelectionStart(v.Cursor.CurSelection[0].Move(-1, v.Buf))
658 v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
665 return PostActionCall("OutdentSelection", v)
672 // InsertTab inserts a tab or spaces
673 func (v *View) InsertTab(usePlugin bool) bool {
674 if usePlugin && !PreActionCall("InsertTab", v) {
678 if v.Cursor.HasSelection() {
682 tabBytes := len(v.Buf.IndentString())
683 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
684 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
685 for i := 0; i < bytesUntilIndent; i++ {
690 return PostActionCall("InsertTab", v)
695 // Save the buffer to disk
696 func (v *View) Save(usePlugin bool) bool {
697 if usePlugin && !PreActionCall("Save", v) {
701 if v.Type == vtHelp {
702 // We can't save the help text
705 // If this is an empty buffer, ask for a filename
706 if v.Buf.Path == "" {
707 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
709 // the filename might or might not be quoted, so unquote first then join the strings.
710 filename = strings.Join(SplitCommandArgs(filename), " ")
711 v.Buf.Path = filename
712 v.Buf.Name = filename
719 if strings.HasSuffix(err.Error(), "permission denied") {
720 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
722 err = v.Buf.SaveWithSudo()
724 messenger.Error(err.Error())
727 messenger.Message("Saved " + v.Buf.Path)
732 messenger.Error(err.Error())
735 messenger.Message("Saved " + v.Buf.Path)
739 return PostActionCall("Save", v)
744 // SaveAs saves the buffer to disk with the given name
745 func (v *View) SaveAs(usePlugin bool) bool {
746 filename, canceled := messenger.Prompt("Filename: ", "Save", NoCompletion)
748 // the filename might or might not be quoted, so unquote first then join the strings.
749 filename = strings.Join(SplitCommandArgs(filename), " ")
750 v.Buf.Path = filename
751 v.Buf.Name = filename
759 // Find opens a prompt and searches forward for the input
760 func (v *View) Find(usePlugin bool) bool {
761 if usePlugin && !PreActionCall("Find", v) {
766 if v.Cursor.HasSelection() {
767 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
768 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
769 searchStr = v.Cursor.GetSelection()
771 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
773 BeginSearch(searchStr)
776 return PostActionCall("Find", v)
781 // FindNext searches forwards for the last used search term
782 func (v *View) FindNext(usePlugin bool) bool {
783 if usePlugin && !PreActionCall("FindNext", v) {
787 if v.Cursor.HasSelection() {
788 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
789 lastSearch = v.Cursor.GetSelection()
791 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
793 if lastSearch == "" {
796 messenger.Message("Finding: " + lastSearch)
797 Search(lastSearch, v, true)
800 return PostActionCall("FindNext", v)
805 // FindPrevious searches backwards for the last used search term
806 func (v *View) FindPrevious(usePlugin bool) bool {
807 if usePlugin && !PreActionCall("FindPrevious", v) {
811 if v.Cursor.HasSelection() {
812 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
814 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
816 messenger.Message("Finding: " + lastSearch)
817 Search(lastSearch, v, false)
820 return PostActionCall("FindPrevious", v)
825 // Undo undoes the last action
826 func (v *View) Undo(usePlugin bool) bool {
827 if usePlugin && !PreActionCall("Undo", v) {
832 messenger.Message("Undid action")
835 return PostActionCall("Undo", v)
840 // Redo redoes the last action
841 func (v *View) Redo(usePlugin bool) bool {
842 if usePlugin && !PreActionCall("Redo", v) {
847 messenger.Message("Redid action")
850 return PostActionCall("Redo", v)
855 // Copy the selection to the system clipboard
856 func (v *View) Copy(usePlugin bool) bool {
857 if usePlugin && !PreActionCall("Copy", v) {
861 if v.Cursor.HasSelection() {
862 clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
864 messenger.Message("Copied selection")
868 return PostActionCall("Copy", v)
873 // CutLine cuts the current line to the clipboard
874 func (v *View) CutLine(usePlugin bool) bool {
875 if usePlugin && !PreActionCall("CutLine", v) {
879 v.Cursor.SelectLine()
880 if !v.Cursor.HasSelection() {
883 if v.freshClip == true {
884 if v.Cursor.HasSelection() {
885 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
888 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
891 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
895 v.lastCutTime = time.Now()
896 v.Cursor.DeleteSelection()
897 v.Cursor.ResetSelection()
898 messenger.Message("Cut line")
901 return PostActionCall("CutLine", v)
906 // Cut the selection to the system clipboard
907 func (v *View) Cut(usePlugin bool) bool {
908 if usePlugin && !PreActionCall("Cut", v) {
912 if v.Cursor.HasSelection() {
913 clipboard.WriteAll(v.Cursor.GetSelection(), "clipboard")
914 v.Cursor.DeleteSelection()
915 v.Cursor.ResetSelection()
917 messenger.Message("Cut selection")
920 return PostActionCall("Cut", v)
928 // DuplicateLine duplicates the current line or selection
929 func (v *View) DuplicateLine(usePlugin bool) bool {
930 if usePlugin && !PreActionCall("DuplicateLine", v) {
934 if v.Cursor.HasSelection() {
935 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
938 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
942 messenger.Message("Duplicated line")
945 return PostActionCall("DuplicateLine", v)
950 // DeleteLine deletes the current line
951 func (v *View) DeleteLine(usePlugin bool) bool {
952 if usePlugin && !PreActionCall("DeleteLine", v) {
956 v.Cursor.SelectLine()
957 if !v.Cursor.HasSelection() {
960 v.Cursor.DeleteSelection()
961 v.Cursor.ResetSelection()
962 messenger.Message("Deleted line")
965 return PostActionCall("DeleteLine", v)
970 // MoveLinesUp moves up the current line or selected lines if any
971 func (v *View) MoveLinesUp(usePlugin bool) bool {
972 if usePlugin && !PreActionCall("MoveLinesUp", v) {
976 if v.Cursor.HasSelection() {
977 if v.Cursor.CurSelection[0].Y == 0 {
978 messenger.Message("Can not move further up")
982 v.Cursor.CurSelection[0].Y,
983 v.Cursor.CurSelection[1].Y,
986 v.Cursor.CurSelection[0].Y -= 1
987 v.Cursor.CurSelection[1].Y -= 1
988 messenger.Message("Moved up selected line(s)")
990 if v.Cursor.Loc.Y == 0 {
991 messenger.Message("Can not move further up")
999 messenger.Message("Moved up current line")
1001 v.Buf.IsModified = true
1004 return PostActionCall("MoveLinesUp", v)
1009 // MoveLinesDown moves down the current line or selected lines if any
1010 func (v *View) MoveLinesDown(usePlugin bool) bool {
1011 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1015 if v.Cursor.HasSelection() {
1016 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1017 messenger.Message("Can not move further down")
1020 v.Buf.MoveLinesDown(
1021 v.Cursor.CurSelection[0].Y,
1022 v.Cursor.CurSelection[1].Y,
1025 v.Cursor.CurSelection[0].Y += 1
1026 v.Cursor.CurSelection[1].Y += 1
1027 messenger.Message("Moved down selected line(s)")
1029 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1030 messenger.Message("Can not move further down")
1033 v.Buf.MoveLinesDown(
1038 messenger.Message("Moved down current line")
1040 v.Buf.IsModified = true
1043 return PostActionCall("MoveLinesDown", v)
1048 // Paste whatever is in the system clipboard into the buffer
1049 // Delete and paste if the user has a selection
1050 func (v *View) Paste(usePlugin bool) bool {
1051 if usePlugin && !PreActionCall("Paste", v) {
1055 clip, _ := clipboard.ReadAll("clipboard")
1059 return PostActionCall("Paste", v)
1064 // PastePrimary pastes from the primary clipboard (only use on linux)
1065 func (v *View) PastePrimary(usePlugin bool) bool {
1066 if usePlugin && !PreActionCall("Paste", v) {
1070 clip, _ := clipboard.ReadAll("primary")
1074 return PostActionCall("Paste", v)
1079 // SelectAll selects the entire buffer
1080 func (v *View) SelectAll(usePlugin bool) bool {
1081 if usePlugin && !PreActionCall("SelectAll", v) {
1085 v.Cursor.SetSelectionStart(v.Buf.Start())
1086 v.Cursor.SetSelectionEnd(v.Buf.End())
1087 // Put the cursor at the beginning
1092 return PostActionCall("SelectAll", v)
1097 // OpenFile opens a new file in the buffer
1098 func (v *View) OpenFile(usePlugin bool) bool {
1099 if usePlugin && !PreActionCall("OpenFile", v) {
1104 filename, canceled := messenger.Prompt("File to open: ", "Open", FileCompletion)
1108 // the filename might or might not be quoted, so unquote first then join the strings.
1109 filename = strings.Join(SplitCommandArgs(filename), " ")
1114 return PostActionCall("OpenFile", v)
1121 // Start moves the viewport to the start of the buffer
1122 func (v *View) Start(usePlugin bool) bool {
1123 if usePlugin && !PreActionCall("Start", v) {
1130 return PostActionCall("Start", v)
1135 // End moves the viewport to the end of the buffer
1136 func (v *View) End(usePlugin bool) bool {
1137 if usePlugin && !PreActionCall("End", v) {
1141 if v.height > v.Buf.NumLines {
1144 v.Topline = v.Buf.NumLines - v.height
1148 return PostActionCall("End", v)
1153 // PageUp scrolls the view up a page
1154 func (v *View) PageUp(usePlugin bool) bool {
1155 if usePlugin && !PreActionCall("PageUp", v) {
1159 if v.Topline > v.height {
1160 v.ScrollUp(v.height)
1166 return PostActionCall("PageUp", v)
1171 // PageDown scrolls the view down a page
1172 func (v *View) PageDown(usePlugin bool) bool {
1173 if usePlugin && !PreActionCall("PageDown", v) {
1177 if v.Buf.NumLines-(v.Topline+v.height) > v.height {
1178 v.ScrollDown(v.height)
1179 } else if v.Buf.NumLines >= v.height {
1180 v.Topline = v.Buf.NumLines - v.height
1184 return PostActionCall("PageDown", v)
1189 // CursorPageUp places the cursor a page up
1190 func (v *View) CursorPageUp(usePlugin bool) bool {
1191 if usePlugin && !PreActionCall("CursorPageUp", v) {
1197 if v.Cursor.HasSelection() {
1198 v.Cursor.Loc = v.Cursor.CurSelection[0]
1199 v.Cursor.ResetSelection()
1201 v.Cursor.UpN(v.height)
1204 return PostActionCall("CursorPageUp", v)
1209 // CursorPageDown places the cursor a page up
1210 func (v *View) CursorPageDown(usePlugin bool) bool {
1211 if usePlugin && !PreActionCall("CursorPageDown", v) {
1217 if v.Cursor.HasSelection() {
1218 v.Cursor.Loc = v.Cursor.CurSelection[1]
1219 v.Cursor.ResetSelection()
1221 v.Cursor.DownN(v.height)
1224 return PostActionCall("CursorPageDown", v)
1229 // HalfPageUp scrolls the view up half a page
1230 func (v *View) HalfPageUp(usePlugin bool) bool {
1231 if usePlugin && !PreActionCall("HalfPageUp", v) {
1235 if v.Topline > v.height/2 {
1236 v.ScrollUp(v.height / 2)
1242 return PostActionCall("HalfPageUp", v)
1247 // HalfPageDown scrolls the view down half a page
1248 func (v *View) HalfPageDown(usePlugin bool) bool {
1249 if usePlugin && !PreActionCall("HalfPageDown", v) {
1253 if v.Buf.NumLines-(v.Topline+v.height) > v.height/2 {
1254 v.ScrollDown(v.height / 2)
1256 if v.Buf.NumLines >= v.height {
1257 v.Topline = v.Buf.NumLines - v.height
1262 return PostActionCall("HalfPageDown", v)
1267 // ToggleRuler turns line numbers off and on
1268 func (v *View) ToggleRuler(usePlugin bool) bool {
1269 if usePlugin && !PreActionCall("ToggleRuler", v) {
1273 if v.Buf.Settings["ruler"] == false {
1274 v.Buf.Settings["ruler"] = true
1275 messenger.Message("Enabled ruler")
1277 v.Buf.Settings["ruler"] = false
1278 messenger.Message("Disabled ruler")
1282 return PostActionCall("ToggleRuler", v)
1287 // JumpLine jumps to a line and moves the view accordingly.
1288 func (v *View) JumpLine(usePlugin bool) bool {
1289 if usePlugin && !PreActionCall("JumpLine", v) {
1293 // Prompt for line number
1294 linestring, canceled := messenger.Prompt("Jump to line # ", "LineNumber", NoCompletion)
1298 lineint, err := strconv.Atoi(linestring)
1299 lineint = lineint - 1 // fix offset
1301 messenger.Error(err) // return errors
1304 // Move cursor and view if possible.
1305 if lineint < v.Buf.NumLines && lineint >= 0 {
1307 v.Cursor.Y = lineint
1310 return PostActionCall("JumpLine", v)
1314 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1318 // ClearStatus clears the messenger bar
1319 func (v *View) ClearStatus(usePlugin bool) bool {
1320 if usePlugin && !PreActionCall("ClearStatus", v) {
1324 messenger.Message("")
1327 return PostActionCall("ClearStatus", v)
1332 // ToggleHelp toggles the help screen
1333 func (v *View) ToggleHelp(usePlugin bool) bool {
1334 if usePlugin && !PreActionCall("ToggleHelp", v) {
1338 if v.Type != vtHelp {
1339 // Open the default help
1346 return PostActionCall("ToggleHelp", v)
1351 // ShellMode opens a terminal to run a shell command
1352 func (v *View) ShellMode(usePlugin bool) bool {
1353 if usePlugin && !PreActionCall("ShellMode", v) {
1357 input, canceled := messenger.Prompt("$ ", "Shell", NoCompletion)
1359 // The true here is for openTerm to make the command interactive
1360 HandleShellCommand(input, true, true)
1362 return PostActionCall("ShellMode", v)
1368 // CommandMode lets the user enter a command
1369 func (v *View) CommandMode(usePlugin bool) bool {
1370 if usePlugin && !PreActionCall("CommandMode", v) {
1374 input, canceled := messenger.Prompt("> ", "Command", CommandCompletion)
1376 HandleCommand(input)
1378 return PostActionCall("CommandMode", v)
1385 // Escape leaves current mode / quits the editor
1386 func (v *View) Escape(usePlugin bool) bool {
1387 // check if user is searching, or the last search is still active
1388 if searching || lastSearch != "" {
1392 // check if a prompt is shown, hide it and don't quit
1393 if messenger.hasPrompt {
1394 messenger.Reset() // FIXME
1397 return v.Quit(usePlugin)
1400 // Quit quits the editor
1401 // This behavior needs to be changed and should really only quit the editor if this
1403 // However, since micro only supports one view for now, it doesn't really matter
1404 func (v *View) Quit(usePlugin bool) bool {
1405 if usePlugin && !PreActionCall("Quit", v) {
1409 // Make sure not to quit if there are unsaved changes
1412 if len(tabs[curTab].views) > 1 {
1413 v.splitNode.Delete()
1414 tabs[v.TabNum].Cleanup()
1415 tabs[v.TabNum].Resize()
1416 } else if len(tabs) > 1 {
1417 if len(tabs[v.TabNum].views) == 1 {
1418 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1419 for i, t := range tabs {
1422 if curTab >= len(tabs) {
1426 // CurView().Resize(screen.Size())
1427 CurView().ToggleTabbar()
1428 CurView().matches = Match(CurView())
1433 PostActionCall("Quit", v)
1442 return PostActionCall("Quit", v)
1447 // QuitAll quits the whole editor; all splits and tabs
1448 func (v *View) QuitAll(usePlugin bool) bool {
1449 if usePlugin && !PreActionCall("QuitAll", v) {
1454 for _, tab := range tabs {
1455 for _, v := range tab.views {
1463 for _, tab := range tabs {
1464 for _, v := range tab.views {
1470 PostActionCall("QuitAll", v)
1480 // AddTab adds a new tab with an empty buffer
1481 func (v *View) AddTab(usePlugin bool) bool {
1482 if usePlugin && !PreActionCall("AddTab", v) {
1486 tab := NewTabFromView(NewView(NewBuffer([]byte{}, "")))
1487 tab.SetNum(len(tabs))
1488 tabs = append(tabs, tab)
1491 for _, t := range tabs {
1492 for _, v := range t.views {
1499 return PostActionCall("AddTab", v)
1504 // PreviousTab switches to the previous tab in the tab list
1505 func (v *View) PreviousTab(usePlugin bool) bool {
1506 if usePlugin && !PreActionCall("PreviousTab", v) {
1512 } else if curTab == 0 {
1513 curTab = len(tabs) - 1
1517 return PostActionCall("PreviousTab", v)
1522 // NextTab switches to the next tab in the tab list
1523 func (v *View) NextTab(usePlugin bool) bool {
1524 if usePlugin && !PreActionCall("NextTab", v) {
1528 if curTab < len(tabs)-1 {
1530 } else if curTab == len(tabs)-1 {
1535 return PostActionCall("NextTab", v)
1540 // VSplitBinding opens an empty vertical split
1541 func (v *View) VSplitBinding(usePlugin bool) bool {
1542 if usePlugin && !PreActionCall("VSplit", v) {
1546 v.VSplit(NewBuffer([]byte{}, ""))
1549 return PostActionCall("VSplit", v)
1554 // HSplitBinding opens an empty horizontal split
1555 func (v *View) HSplitBinding(usePlugin bool) bool {
1556 if usePlugin && !PreActionCall("HSplit", v) {
1560 v.HSplit(NewBuffer([]byte{}, ""))
1563 return PostActionCall("HSplit", v)
1568 // Unsplit closes all splits in the current tab except the active one
1569 func (v *View) Unsplit(usePlugin bool) bool {
1570 if usePlugin && !PreActionCall("Unsplit", v) {
1574 curView := tabs[curTab].curView
1575 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1576 view := tabs[curTab].views[i]
1577 if view != nil && view.Num != curView {
1579 // messenger.Message("Quit ", view.Buf.Path)
1584 return PostActionCall("Unsplit", v)
1589 // NextSplit changes the view to the next split
1590 func (v *View) NextSplit(usePlugin bool) bool {
1591 if usePlugin && !PreActionCall("NextSplit", v) {
1596 if tab.curView < len(tab.views)-1 {
1603 return PostActionCall("NextSplit", v)
1608 // PreviousSplit changes the view to the previous split
1609 func (v *View) PreviousSplit(usePlugin bool) bool {
1610 if usePlugin && !PreActionCall("PreviousSplit", v) {
1615 if tab.curView > 0 {
1618 tab.curView = len(tab.views) - 1
1622 return PostActionCall("PreviousSplit", v)
1627 var curMacro []interface{}
1628 var recordingMacro bool
1630 // ToggleMacro toggles recording of a macro
1631 func (v *View) ToggleMacro(usePlugin bool) bool {
1632 if usePlugin && !PreActionCall("ToggleMacro", v) {
1636 recordingMacro = !recordingMacro
1639 curMacro = []interface{}{}
1640 messenger.Message("Recording")
1642 messenger.Message("Stopped recording")
1646 return PostActionCall("ToggleMacro", v)
1651 // PlayMacro plays back the most recently recorded macro
1652 func (v *View) PlayMacro(usePlugin bool) bool {
1653 if usePlugin && !PreActionCall("PlayMacro", v) {
1657 for _, action := range curMacro {
1658 switch t := action.(type) {
1660 // Insert a character
1661 if v.Cursor.HasSelection() {
1662 v.Cursor.DeleteSelection()
1663 v.Cursor.ResetSelection()
1665 v.Buf.Insert(v.Cursor.Loc, string(t))
1668 for _, pl := range loadedPlugins {
1669 _, err := Call(pl+".onRune", string(t), v)
1670 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1674 case func(*View, bool) bool:
1680 return PostActionCall("PlayMacro", v)
1685 // None is no action