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()
114 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
115 tabmovement := v.Buf.Settings["tabmovement"].(bool)
116 if tabstospaces && tabmovement {
117 tabsize := int(v.Buf.Settings["tabsize"].(float64))
118 line := v.Buf.Line(v.Cursor.Y)
119 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
120 for i := 0; i < tabsize; i++ {
132 return PostActionCall("CursorLeft", v)
137 // CursorRight moves the cursor right
138 func (v *View) CursorRight(usePlugin bool) bool {
139 if usePlugin && !PreActionCall("CursorRight", v) {
143 if v.Cursor.HasSelection() {
144 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
145 v.Cursor.ResetSelection()
147 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
148 tabmovement := v.Buf.Settings["tabmovement"].(bool)
149 if tabstospaces && tabmovement {
150 tabsize := int(v.Buf.Settings["tabsize"].(float64))
151 line := v.Buf.Line(v.Cursor.Y)
152 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
153 for i := 0; i < tabsize; i++ {
165 return PostActionCall("CursorRight", v)
170 // WordRight moves the cursor one word to the right
171 func (v *View) WordRight(usePlugin bool) bool {
172 if usePlugin && !PreActionCall("WordRight", v) {
179 return PostActionCall("WordRight", v)
184 // WordLeft moves the cursor one word to the left
185 func (v *View) WordLeft(usePlugin bool) bool {
186 if usePlugin && !PreActionCall("WordLeft", v) {
193 return PostActionCall("WordLeft", v)
198 // SelectUp selects up one line
199 func (v *View) SelectUp(usePlugin bool) bool {
200 if usePlugin && !PreActionCall("SelectUp", v) {
204 if !v.Cursor.HasSelection() {
205 v.Cursor.OrigSelection[0] = v.Cursor.Loc
208 v.Cursor.SelectTo(v.Cursor.Loc)
211 return PostActionCall("SelectUp", v)
216 // SelectDown selects down one line
217 func (v *View) SelectDown(usePlugin bool) bool {
218 if usePlugin && !PreActionCall("SelectDown", v) {
222 if !v.Cursor.HasSelection() {
223 v.Cursor.OrigSelection[0] = v.Cursor.Loc
226 v.Cursor.SelectTo(v.Cursor.Loc)
229 return PostActionCall("SelectDown", v)
234 // SelectLeft selects the character to the left of the cursor
235 func (v *View) SelectLeft(usePlugin bool) bool {
236 if usePlugin && !PreActionCall("SelectLeft", v) {
241 count := v.Buf.End().Move(-1, v.Buf)
242 if loc.GreaterThan(count) {
245 if !v.Cursor.HasSelection() {
246 v.Cursor.OrigSelection[0] = loc
249 v.Cursor.SelectTo(v.Cursor.Loc)
252 return PostActionCall("SelectLeft", v)
257 // SelectRight selects the character to the right of the cursor
258 func (v *View) SelectRight(usePlugin bool) bool {
259 if usePlugin && !PreActionCall("SelectRight", v) {
264 count := v.Buf.End().Move(-1, v.Buf)
265 if loc.GreaterThan(count) {
268 if !v.Cursor.HasSelection() {
269 v.Cursor.OrigSelection[0] = loc
272 v.Cursor.SelectTo(v.Cursor.Loc)
275 return PostActionCall("SelectRight", v)
280 // SelectWordRight selects the word to the right of the cursor
281 func (v *View) SelectWordRight(usePlugin bool) bool {
282 if usePlugin && !PreActionCall("SelectWordRight", v) {
286 if !v.Cursor.HasSelection() {
287 v.Cursor.OrigSelection[0] = v.Cursor.Loc
290 v.Cursor.SelectTo(v.Cursor.Loc)
293 return PostActionCall("SelectWordRight", v)
298 // SelectWordLeft selects the word to the left of the cursor
299 func (v *View) SelectWordLeft(usePlugin bool) bool {
300 if usePlugin && !PreActionCall("SelectWordLeft", v) {
304 if !v.Cursor.HasSelection() {
305 v.Cursor.OrigSelection[0] = v.Cursor.Loc
308 v.Cursor.SelectTo(v.Cursor.Loc)
311 return PostActionCall("SelectWordLeft", v)
316 // StartOfLine moves the cursor to the start of the line
317 func (v *View) StartOfLine(usePlugin bool) bool {
318 if usePlugin && !PreActionCall("StartOfLine", v) {
327 return PostActionCall("StartOfLine", v)
332 // EndOfLine moves the cursor to the end of the line
333 func (v *View) EndOfLine(usePlugin bool) bool {
334 if usePlugin && !PreActionCall("EndOfLine", v) {
343 return PostActionCall("EndOfLine", v)
348 // SelectToStartOfLine selects to the start of the current line
349 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
350 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
354 if !v.Cursor.HasSelection() {
355 v.Cursor.OrigSelection[0] = v.Cursor.Loc
358 v.Cursor.SelectTo(v.Cursor.Loc)
361 return PostActionCall("SelectToStartOfLine", v)
366 // SelectToEndOfLine selects to the end of the current line
367 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
368 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
372 if !v.Cursor.HasSelection() {
373 v.Cursor.OrigSelection[0] = v.Cursor.Loc
376 v.Cursor.SelectTo(v.Cursor.Loc)
379 return PostActionCall("SelectToEndOfLine", v)
384 // CursorStart moves the cursor to the start of the buffer
385 func (v *View) CursorStart(usePlugin bool) bool {
386 if usePlugin && !PreActionCall("CursorStart", v) {
396 return PostActionCall("CursorStart", v)
401 // CursorEnd moves the cursor to the end of the buffer
402 func (v *View) CursorEnd(usePlugin bool) bool {
403 if usePlugin && !PreActionCall("CursorEnd", v) {
409 v.Cursor.Loc = v.Buf.End()
412 return PostActionCall("CursorEnd", v)
417 // SelectToStart selects the text from the cursor to the start of the buffer
418 func (v *View) SelectToStart(usePlugin bool) bool {
419 if usePlugin && !PreActionCall("SelectToStart", v) {
423 if !v.Cursor.HasSelection() {
424 v.Cursor.OrigSelection[0] = v.Cursor.Loc
427 v.Cursor.SelectTo(v.Buf.Start())
430 return PostActionCall("SelectToStart", v)
435 // SelectToEnd selects the text from the cursor to the end of the buffer
436 func (v *View) SelectToEnd(usePlugin bool) bool {
437 if usePlugin && !PreActionCall("SelectToEnd", v) {
441 if !v.Cursor.HasSelection() {
442 v.Cursor.OrigSelection[0] = v.Cursor.Loc
445 v.Cursor.SelectTo(v.Buf.End())
448 return PostActionCall("SelectToEnd", v)
453 // InsertSpace inserts a space
454 func (v *View) InsertSpace(usePlugin bool) bool {
455 if usePlugin && !PreActionCall("InsertSpace", v) {
459 if v.Cursor.HasSelection() {
460 v.Cursor.DeleteSelection()
461 v.Cursor.ResetSelection()
463 v.Buf.Insert(v.Cursor.Loc, " ")
467 return PostActionCall("InsertSpace", v)
472 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
473 func (v *View) InsertNewline(usePlugin bool) bool {
474 if usePlugin && !PreActionCall("InsertNewline", v) {
479 if v.Cursor.HasSelection() {
480 v.Cursor.DeleteSelection()
481 v.Cursor.ResetSelection()
484 v.Buf.Insert(v.Cursor.Loc, "\n")
485 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
488 if v.Buf.Settings["autoindent"].(bool) {
489 v.Buf.Insert(v.Cursor.Loc, ws)
490 for i := 0; i < len(ws); i++ {
494 // Remove the whitespaces if keepautoindent setting is off
495 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
496 line := v.Buf.Line(v.Cursor.Y - 1)
497 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
500 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
503 return PostActionCall("InsertNewline", v)
508 // Backspace deletes the previous character
509 func (v *View) Backspace(usePlugin bool) bool {
510 if usePlugin && !PreActionCall("Backspace", v) {
514 // Delete a character
515 if v.Cursor.HasSelection() {
516 v.Cursor.DeleteSelection()
517 v.Cursor.ResetSelection()
518 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
519 // We have to do something a bit hacky here because we want to
520 // delete the line by first moving left and then deleting backwards
521 // but the undo redo would place the cursor in the wrong place
522 // So instead we move left, save the position, move back, delete
523 // and restore the position
525 // If the user is using spaces instead of tabs and they are deleting
526 // whitespace at the start of the line, we should delete as if it's a
527 // tab (tabSize number of spaces)
528 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
529 tabSize := int(v.Buf.Settings["tabsize"].(float64))
530 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
532 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
533 cx, cy := v.Cursor.X, v.Cursor.Y
535 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
536 v.Cursor.X, v.Cursor.Y = cx, cy
539 cx, cy := v.Cursor.X, v.Cursor.Y
542 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
543 v.Cursor.X, v.Cursor.Y = cx, cy
546 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
549 return PostActionCall("Backspace", v)
554 // DeleteWordRight deletes the word to the right of the cursor
555 func (v *View) DeleteWordRight(usePlugin bool) bool {
556 if usePlugin && !PreActionCall("DeleteWordRight", v) {
560 v.SelectWordRight(false)
561 if v.Cursor.HasSelection() {
562 v.Cursor.DeleteSelection()
563 v.Cursor.ResetSelection()
567 return PostActionCall("DeleteWordRight", v)
572 // DeleteWordLeft deletes the word to the left of the cursor
573 func (v *View) DeleteWordLeft(usePlugin bool) bool {
574 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
578 v.SelectWordLeft(false)
579 if v.Cursor.HasSelection() {
580 v.Cursor.DeleteSelection()
581 v.Cursor.ResetSelection()
585 return PostActionCall("DeleteWordLeft", v)
590 // Delete deletes the next character
591 func (v *View) Delete(usePlugin bool) bool {
592 if usePlugin && !PreActionCall("Delete", v) {
596 if v.Cursor.HasSelection() {
597 v.Cursor.DeleteSelection()
598 v.Cursor.ResetSelection()
601 if loc.LessThan(v.Buf.End()) {
602 v.Buf.Remove(loc, loc.Move(1, v.Buf))
607 return PostActionCall("Delete", v)
612 // IndentSelection indents the current selection
613 func (v *View) IndentSelection(usePlugin bool) bool {
614 if usePlugin && !PreActionCall("IndentSelection", v) {
618 if v.Cursor.HasSelection() {
619 start := v.Cursor.CurSelection[0]
620 end := v.Cursor.CurSelection[1]
622 start, end = end, start
626 endY := end.Move(-1, v.Buf).Y
627 endX := end.Move(-1, v.Buf).X
628 for y := startY; y <= endY; y++ {
629 tabsize := len(v.Buf.IndentString())
630 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
631 if y == startY && start.X > 0 {
632 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
635 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
641 return PostActionCall("IndentSelection", v)
648 // OutdentLine moves the current line back one indentation
649 func (v *View) OutdentLine(usePlugin bool) bool {
650 if usePlugin && !PreActionCall("OutdentLine", v) {
654 if v.Cursor.HasSelection() {
658 for x := 0; x < len(v.Buf.IndentString()); x++ {
659 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
662 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
668 return PostActionCall("OutdentLine", v)
673 // OutdentSelection takes the current selection and moves it back one indent level
674 func (v *View) OutdentSelection(usePlugin bool) bool {
675 if usePlugin && !PreActionCall("OutdentSelection", v) {
679 if v.Cursor.HasSelection() {
680 start := v.Cursor.CurSelection[0]
681 end := v.Cursor.CurSelection[1]
683 start, end = end, start
687 endY := end.Move(-1, v.Buf).Y
688 endX := end.Move(-1, v.Buf).X
689 for y := startY; y <= endY; y++ {
690 for x := 0; x < len(v.Buf.IndentString()); x++ {
691 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
694 v.Buf.Remove(Loc{0, y}, Loc{1, y})
695 if y == startY && start.X > 0 {
696 v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
699 v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
706 return PostActionCall("OutdentSelection", v)
713 // InsertTab inserts a tab or spaces
714 func (v *View) InsertTab(usePlugin bool) bool {
715 if usePlugin && !PreActionCall("InsertTab", v) {
719 if v.Cursor.HasSelection() {
723 tabBytes := len(v.Buf.IndentString())
724 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
725 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
726 for i := 0; i < bytesUntilIndent; i++ {
731 return PostActionCall("InsertTab", v)
736 // SaveAll saves all open buffers
737 func (v *View) SaveAll(usePlugin bool) bool {
738 if usePlugin && !PreActionCall("SaveAll", v) {
742 for _, t := range tabs {
743 for _, v := range t.views {
749 return PostActionCall("SaveAll", v)
754 // Save the buffer to disk
755 func (v *View) Save(usePlugin bool) bool {
756 if usePlugin && !PreActionCall("Save", v) {
760 if v.Type == vtHelp {
761 // We can't save the help text
764 // If this is an empty buffer, ask for a filename
765 if v.Buf.Path == "" {
768 v.saveToFile(v.Buf.Path)
772 return PostActionCall("Save", v)
777 // This function saves the buffer to `filename` and changes the buffer's path and name
778 // to `filename` if the save is successful
779 func (v *View) saveToFile(filename string) {
780 err := v.Buf.SaveAs(filename)
782 if strings.HasSuffix(err.Error(), "permission denied") {
783 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
785 err = v.Buf.SaveAsWithSudo(filename)
787 messenger.Error(err.Error())
789 v.Buf.Path = filename
790 v.Buf.name = filename
791 messenger.Message("Saved " + filename)
797 messenger.Error(err.Error())
800 v.Buf.Path = filename
801 v.Buf.name = filename
802 messenger.Message("Saved " + filename)
806 // SaveAs saves the buffer to disk with the given name
807 func (v *View) SaveAs(usePlugin bool) bool {
808 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
810 // the filename might or might not be quoted, so unquote first then join the strings.
811 filename = strings.Join(SplitCommandArgs(filename), " ")
812 v.saveToFile(filename)
818 // Find opens a prompt and searches forward for the input
819 func (v *View) Find(usePlugin bool) bool {
820 if usePlugin && !PreActionCall("Find", v) {
825 if v.Cursor.HasSelection() {
826 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
827 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
828 searchStr = v.Cursor.GetSelection()
830 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
832 BeginSearch(searchStr)
835 return PostActionCall("Find", v)
840 // FindNext searches forwards for the last used search term
841 func (v *View) FindNext(usePlugin bool) bool {
842 if usePlugin && !PreActionCall("FindNext", v) {
846 if v.Cursor.HasSelection() {
847 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
848 // lastSearch = v.Cursor.GetSelection()
850 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
852 if lastSearch == "" {
855 messenger.Message("Finding: " + lastSearch)
856 Search(lastSearch, v, true)
859 return PostActionCall("FindNext", v)
864 // FindPrevious searches backwards for the last used search term
865 func (v *View) FindPrevious(usePlugin bool) bool {
866 if usePlugin && !PreActionCall("FindPrevious", v) {
870 if v.Cursor.HasSelection() {
871 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
873 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
875 messenger.Message("Finding: " + lastSearch)
876 Search(lastSearch, v, false)
879 return PostActionCall("FindPrevious", v)
884 // Undo undoes the last action
885 func (v *View) Undo(usePlugin bool) bool {
886 if usePlugin && !PreActionCall("Undo", v) {
891 messenger.Message("Undid action")
894 return PostActionCall("Undo", v)
899 // Redo redoes the last action
900 func (v *View) Redo(usePlugin bool) bool {
901 if usePlugin && !PreActionCall("Redo", v) {
906 messenger.Message("Redid action")
909 return PostActionCall("Redo", v)
914 // Copy the selection to the system clipboard
915 func (v *View) Copy(usePlugin bool) bool {
916 if usePlugin && !PreActionCall("Copy", v) {
920 if v.Cursor.HasSelection() {
921 v.Cursor.CopySelection("clipboard")
923 messenger.Message("Copied selection")
927 return PostActionCall("Copy", v)
932 // CutLine cuts the current line to the clipboard
933 func (v *View) CutLine(usePlugin bool) bool {
934 if usePlugin && !PreActionCall("CutLine", v) {
938 v.Cursor.SelectLine()
939 if !v.Cursor.HasSelection() {
942 if v.freshClip == true {
943 if v.Cursor.HasSelection() {
944 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
947 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
950 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
954 v.lastCutTime = time.Now()
955 v.Cursor.DeleteSelection()
956 v.Cursor.ResetSelection()
957 messenger.Message("Cut line")
960 return PostActionCall("CutLine", v)
965 // Cut the selection to the system clipboard
966 func (v *View) Cut(usePlugin bool) bool {
967 if usePlugin && !PreActionCall("Cut", v) {
971 if v.Cursor.HasSelection() {
972 v.Cursor.CopySelection("clipboard")
973 v.Cursor.DeleteSelection()
974 v.Cursor.ResetSelection()
976 messenger.Message("Cut selection")
979 return PostActionCall("Cut", v)
987 // DuplicateLine duplicates the current line or selection
988 func (v *View) DuplicateLine(usePlugin bool) bool {
989 if usePlugin && !PreActionCall("DuplicateLine", v) {
993 if v.Cursor.HasSelection() {
994 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
997 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1001 messenger.Message("Duplicated line")
1004 return PostActionCall("DuplicateLine", v)
1009 // DeleteLine deletes the current line
1010 func (v *View) DeleteLine(usePlugin bool) bool {
1011 if usePlugin && !PreActionCall("DeleteLine", v) {
1015 v.Cursor.SelectLine()
1016 if !v.Cursor.HasSelection() {
1019 v.Cursor.DeleteSelection()
1020 v.Cursor.ResetSelection()
1021 messenger.Message("Deleted line")
1024 return PostActionCall("DeleteLine", v)
1029 // MoveLinesUp moves up the current line or selected lines if any
1030 func (v *View) MoveLinesUp(usePlugin bool) bool {
1031 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1035 if v.Cursor.HasSelection() {
1036 if v.Cursor.CurSelection[0].Y == 0 {
1037 messenger.Message("Can not move further up")
1041 v.Cursor.CurSelection[0].Y,
1042 v.Cursor.CurSelection[1].Y,
1045 v.Cursor.CurSelection[0].Y -= 1
1046 v.Cursor.CurSelection[1].Y -= 1
1047 messenger.Message("Moved up selected line(s)")
1049 if v.Cursor.Loc.Y == 0 {
1050 messenger.Message("Can not move further up")
1058 messenger.Message("Moved up current line")
1060 v.Buf.IsModified = true
1063 return PostActionCall("MoveLinesUp", v)
1068 // MoveLinesDown moves down the current line or selected lines if any
1069 func (v *View) MoveLinesDown(usePlugin bool) bool {
1070 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1074 if v.Cursor.HasSelection() {
1075 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1076 messenger.Message("Can not move further down")
1079 v.Buf.MoveLinesDown(
1080 v.Cursor.CurSelection[0].Y,
1081 v.Cursor.CurSelection[1].Y,
1084 v.Cursor.CurSelection[0].Y += 1
1085 v.Cursor.CurSelection[1].Y += 1
1086 messenger.Message("Moved down selected line(s)")
1088 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1089 messenger.Message("Can not move further down")
1092 v.Buf.MoveLinesDown(
1097 messenger.Message("Moved down current line")
1099 v.Buf.IsModified = true
1102 return PostActionCall("MoveLinesDown", v)
1107 // Paste whatever is in the system clipboard into the buffer
1108 // Delete and paste if the user has a selection
1109 func (v *View) Paste(usePlugin bool) bool {
1110 if usePlugin && !PreActionCall("Paste", v) {
1114 clip, _ := clipboard.ReadAll("clipboard")
1118 return PostActionCall("Paste", v)
1123 // PastePrimary pastes from the primary clipboard (only use on linux)
1124 func (v *View) PastePrimary(usePlugin bool) bool {
1125 if usePlugin && !PreActionCall("Paste", v) {
1129 clip, _ := clipboard.ReadAll("primary")
1133 return PostActionCall("Paste", v)
1138 // SelectAll selects the entire buffer
1139 func (v *View) SelectAll(usePlugin bool) bool {
1140 if usePlugin && !PreActionCall("SelectAll", v) {
1144 v.Cursor.SetSelectionStart(v.Buf.Start())
1145 v.Cursor.SetSelectionEnd(v.Buf.End())
1146 // Put the cursor at the beginning
1151 return PostActionCall("SelectAll", v)
1156 // OpenFile opens a new file in the buffer
1157 func (v *View) OpenFile(usePlugin bool) bool {
1158 if usePlugin && !PreActionCall("OpenFile", v) {
1163 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1165 HandleCommand(input)
1167 return PostActionCall("OpenFile", v)
1174 // Start moves the viewport to the start of the buffer
1175 func (v *View) Start(usePlugin bool) bool {
1176 if usePlugin && !PreActionCall("Start", v) {
1183 return PostActionCall("Start", v)
1188 // End moves the viewport to the end of the buffer
1189 func (v *View) End(usePlugin bool) bool {
1190 if usePlugin && !PreActionCall("End", v) {
1194 if v.Height > v.Buf.NumLines {
1197 v.Topline = v.Buf.NumLines - v.Height
1201 return PostActionCall("End", v)
1206 // PageUp scrolls the view up a page
1207 func (v *View) PageUp(usePlugin bool) bool {
1208 if usePlugin && !PreActionCall("PageUp", v) {
1212 if v.Topline > v.Height {
1213 v.ScrollUp(v.Height)
1219 return PostActionCall("PageUp", v)
1224 // PageDown scrolls the view down a page
1225 func (v *View) PageDown(usePlugin bool) bool {
1226 if usePlugin && !PreActionCall("PageDown", v) {
1230 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1231 v.ScrollDown(v.Height)
1232 } else if v.Buf.NumLines >= v.Height {
1233 v.Topline = v.Buf.NumLines - v.Height
1237 return PostActionCall("PageDown", v)
1242 // CursorPageUp places the cursor a page up
1243 func (v *View) CursorPageUp(usePlugin bool) bool {
1244 if usePlugin && !PreActionCall("CursorPageUp", v) {
1250 if v.Cursor.HasSelection() {
1251 v.Cursor.Loc = v.Cursor.CurSelection[0]
1252 v.Cursor.ResetSelection()
1254 v.Cursor.UpN(v.Height)
1257 return PostActionCall("CursorPageUp", v)
1262 // CursorPageDown places the cursor a page up
1263 func (v *View) CursorPageDown(usePlugin bool) bool {
1264 if usePlugin && !PreActionCall("CursorPageDown", v) {
1270 if v.Cursor.HasSelection() {
1271 v.Cursor.Loc = v.Cursor.CurSelection[1]
1272 v.Cursor.ResetSelection()
1274 v.Cursor.DownN(v.Height)
1277 return PostActionCall("CursorPageDown", v)
1282 // HalfPageUp scrolls the view up half a page
1283 func (v *View) HalfPageUp(usePlugin bool) bool {
1284 if usePlugin && !PreActionCall("HalfPageUp", v) {
1288 if v.Topline > v.Height/2 {
1289 v.ScrollUp(v.Height / 2)
1295 return PostActionCall("HalfPageUp", v)
1300 // HalfPageDown scrolls the view down half a page
1301 func (v *View) HalfPageDown(usePlugin bool) bool {
1302 if usePlugin && !PreActionCall("HalfPageDown", v) {
1306 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1307 v.ScrollDown(v.Height / 2)
1309 if v.Buf.NumLines >= v.Height {
1310 v.Topline = v.Buf.NumLines - v.Height
1315 return PostActionCall("HalfPageDown", v)
1320 // ToggleRuler turns line numbers off and on
1321 func (v *View) ToggleRuler(usePlugin bool) bool {
1322 if usePlugin && !PreActionCall("ToggleRuler", v) {
1326 if v.Buf.Settings["ruler"] == false {
1327 v.Buf.Settings["ruler"] = true
1328 messenger.Message("Enabled ruler")
1330 v.Buf.Settings["ruler"] = false
1331 messenger.Message("Disabled ruler")
1335 return PostActionCall("ToggleRuler", v)
1340 // JumpLine jumps to a line and moves the view accordingly.
1341 func (v *View) JumpLine(usePlugin bool) bool {
1342 if usePlugin && !PreActionCall("JumpLine", v) {
1346 // Prompt for line number
1347 linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1351 lineint, err := strconv.Atoi(linestring)
1352 lineint = lineint - 1 // fix offset
1354 messenger.Error(err) // return errors
1357 // Move cursor and view if possible.
1358 if lineint < v.Buf.NumLines && lineint >= 0 {
1360 v.Cursor.Y = lineint
1363 return PostActionCall("JumpLine", v)
1367 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1371 // ClearStatus clears the messenger bar
1372 func (v *View) ClearStatus(usePlugin bool) bool {
1373 if usePlugin && !PreActionCall("ClearStatus", v) {
1377 messenger.Message("")
1380 return PostActionCall("ClearStatus", v)
1385 // ToggleHelp toggles the help screen
1386 func (v *View) ToggleHelp(usePlugin bool) bool {
1387 if usePlugin && !PreActionCall("ToggleHelp", v) {
1391 if v.Type != vtHelp {
1392 // Open the default help
1399 return PostActionCall("ToggleHelp", v)
1404 // ShellMode opens a terminal to run a shell command
1405 func (v *View) ShellMode(usePlugin bool) bool {
1406 if usePlugin && !PreActionCall("ShellMode", v) {
1410 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1412 // The true here is for openTerm to make the command interactive
1413 HandleShellCommand(input, true, true)
1415 return PostActionCall("ShellMode", v)
1421 // CommandMode lets the user enter a command
1422 func (v *View) CommandMode(usePlugin bool) bool {
1423 if usePlugin && !PreActionCall("CommandMode", v) {
1427 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1429 HandleCommand(input)
1431 return PostActionCall("CommandMode", v)
1438 // Escape leaves current mode
1439 func (v *View) Escape(usePlugin bool) bool {
1440 // check if user is searching, or the last search is still active
1441 if searching || lastSearch != "" {
1445 // check if a prompt is shown, hide it and don't quit
1446 if messenger.hasPrompt {
1447 messenger.Reset() // FIXME
1454 // Quit this will close the current tab or view that is open
1455 func (v *View) Quit(usePlugin bool) bool {
1456 if usePlugin && !PreActionCall("Quit", v) {
1460 // Make sure not to quit if there are unsaved changes
1463 if len(tabs[curTab].views) > 1 {
1464 v.splitNode.Delete()
1465 tabs[v.TabNum].Cleanup()
1466 tabs[v.TabNum].Resize()
1467 } else if len(tabs) > 1 {
1468 if len(tabs[v.TabNum].views) == 1 {
1469 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1470 for i, t := range tabs {
1473 if curTab >= len(tabs) {
1477 CurView().ToggleTabbar()
1482 PostActionCall("Quit", v)
1491 return PostActionCall("Quit", v)
1496 // QuitAll quits the whole editor; all splits and tabs
1497 func (v *View) QuitAll(usePlugin bool) bool {
1498 if usePlugin && !PreActionCall("QuitAll", v) {
1503 for _, tab := range tabs {
1504 for _, v := range tab.views {
1512 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1513 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1516 for _, tab := range tabs {
1517 for _, v := range tab.views {
1523 PostActionCall("QuitAll", v)
1534 // AddTab adds a new tab with an empty buffer
1535 func (v *View) AddTab(usePlugin bool) bool {
1536 if usePlugin && !PreActionCall("AddTab", v) {
1540 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1541 tab.SetNum(len(tabs))
1542 tabs = append(tabs, tab)
1543 curTab = len(tabs) - 1
1545 for _, t := range tabs {
1546 for _, v := range t.views {
1553 return PostActionCall("AddTab", v)
1558 // PreviousTab switches to the previous tab in the tab list
1559 func (v *View) PreviousTab(usePlugin bool) bool {
1560 if usePlugin && !PreActionCall("PreviousTab", v) {
1566 } else if curTab == 0 {
1567 curTab = len(tabs) - 1
1571 return PostActionCall("PreviousTab", v)
1576 // NextTab switches to the next tab in the tab list
1577 func (v *View) NextTab(usePlugin bool) bool {
1578 if usePlugin && !PreActionCall("NextTab", v) {
1582 if curTab < len(tabs)-1 {
1584 } else if curTab == len(tabs)-1 {
1589 return PostActionCall("NextTab", v)
1594 // VSplitBinding opens an empty vertical split
1595 func (v *View) VSplitBinding(usePlugin bool) bool {
1596 if usePlugin && !PreActionCall("VSplit", v) {
1600 v.VSplit(NewBufferFromString("", ""))
1603 return PostActionCall("VSplit", v)
1608 // HSplitBinding opens an empty horizontal split
1609 func (v *View) HSplitBinding(usePlugin bool) bool {
1610 if usePlugin && !PreActionCall("HSplit", v) {
1614 v.HSplit(NewBufferFromString("", ""))
1617 return PostActionCall("HSplit", v)
1622 // Unsplit closes all splits in the current tab except the active one
1623 func (v *View) Unsplit(usePlugin bool) bool {
1624 if usePlugin && !PreActionCall("Unsplit", v) {
1628 curView := tabs[curTab].CurView
1629 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1630 view := tabs[curTab].views[i]
1631 if view != nil && view.Num != curView {
1633 // messenger.Message("Quit ", view.Buf.Path)
1638 return PostActionCall("Unsplit", v)
1643 // NextSplit changes the view to the next split
1644 func (v *View) NextSplit(usePlugin bool) bool {
1645 if usePlugin && !PreActionCall("NextSplit", v) {
1650 if tab.CurView < len(tab.views)-1 {
1657 return PostActionCall("NextSplit", v)
1662 // PreviousSplit changes the view to the previous split
1663 func (v *View) PreviousSplit(usePlugin bool) bool {
1664 if usePlugin && !PreActionCall("PreviousSplit", v) {
1669 if tab.CurView > 0 {
1672 tab.CurView = len(tab.views) - 1
1676 return PostActionCall("PreviousSplit", v)
1681 var curMacro []interface{}
1682 var recordingMacro bool
1684 // ToggleMacro toggles recording of a macro
1685 func (v *View) ToggleMacro(usePlugin bool) bool {
1686 if usePlugin && !PreActionCall("ToggleMacro", v) {
1690 recordingMacro = !recordingMacro
1693 curMacro = []interface{}{}
1694 messenger.Message("Recording")
1696 messenger.Message("Stopped recording")
1700 return PostActionCall("ToggleMacro", v)
1705 // PlayMacro plays back the most recently recorded macro
1706 func (v *View) PlayMacro(usePlugin bool) bool {
1707 if usePlugin && !PreActionCall("PlayMacro", v) {
1711 for _, action := range curMacro {
1712 switch t := action.(type) {
1714 // Insert a character
1715 if v.Cursor.HasSelection() {
1716 v.Cursor.DeleteSelection()
1717 v.Cursor.ResetSelection()
1719 v.Buf.Insert(v.Cursor.Loc, string(t))
1722 for pl := range loadedPlugins {
1723 _, err := Call(pl+".onRune", string(t), v)
1724 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1728 case func(*View, bool) bool:
1734 return PostActionCall("PlayMacro", v)
1739 // None is no action