12 "github.com/yuin/gopher-lua"
13 "github.com/zyedidia/clipboard"
14 "github.com/zyedidia/micro/cmd/micro/shellwords"
15 "github.com/zyedidia/tcell"
18 // PreActionCall executes the lua pre callback if possible
19 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
21 for pl := range loadedPlugins {
22 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
23 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
27 if ret == lua.LFalse {
34 // PostActionCall executes the lua plugin callback if possible
35 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
37 for pl := range loadedPlugins {
38 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
39 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
43 if ret == lua.LFalse {
50 func (v *View) deselect(index int) bool {
51 if v.Cursor.HasSelection() {
52 v.Cursor.Loc = v.Cursor.CurSelection[index]
53 v.Cursor.ResetSelection()
54 v.Cursor.StoreVisualX()
60 // MousePress is the event that should happen when a normal click happens
61 // This is almost always bound to left click
62 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
63 if usePlugin && !PreActionCall("MousePress", v, e) {
68 x -= v.lineNumOffset - v.leftCol + v.x
71 // This is usually bound to left click
72 v.MoveToMouseClick(x, y)
74 if len(v.Buf.cursors) > 1 {
75 for i := 1; i < len(v.Buf.cursors); i++ {
76 v.Buf.cursors[i] = nil
78 v.Buf.cursors = v.Buf.cursors[:1]
80 v.Cursor.ResetSelection()
83 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
86 v.lastClickTime = time.Now()
92 v.Cursor.CopySelection("primary")
95 v.lastClickTime = time.Now()
100 v.Cursor.SelectWord()
101 v.Cursor.CopySelection("primary")
104 v.doubleClick = false
105 v.tripleClick = false
106 v.lastClickTime = time.Now()
108 v.Cursor.OrigSelection[0] = v.Cursor.Loc
109 v.Cursor.CurSelection[0] = v.Cursor.Loc
110 v.Cursor.CurSelection[1] = v.Cursor.Loc
112 v.mouseReleased = false
113 } else if !v.mouseReleased {
115 v.Cursor.AddLineToSelection()
116 } else if v.doubleClick {
117 v.Cursor.AddWordToSelection()
119 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
120 v.Cursor.CopySelection("primary")
124 v.lastLoc = Loc{x, y}
127 PostActionCall("MousePress", v, e)
132 // ScrollUpAction scrolls the view up
133 func (v *View) ScrollUpAction(usePlugin bool) bool {
135 if usePlugin && !PreActionCall("ScrollUp", v) {
139 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
140 v.ScrollUp(scrollspeed)
143 PostActionCall("ScrollUp", v)
149 // ScrollDownAction scrolls the view up
150 func (v *View) ScrollDownAction(usePlugin bool) bool {
152 if usePlugin && !PreActionCall("ScrollDown", v) {
156 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
157 v.ScrollDown(scrollspeed)
160 PostActionCall("ScrollDown", v)
166 // Center centers the view on the cursor
167 func (v *View) Center(usePlugin bool) bool {
168 if usePlugin && !PreActionCall("Center", v) {
172 v.Topline = v.Cursor.Y - v.Height/2
173 if v.Topline+v.Height > v.Buf.NumLines {
174 v.Topline = v.Buf.NumLines - v.Height
181 return PostActionCall("Center", v)
186 // CursorUp moves the cursor up
187 func (v *View) CursorUp(usePlugin bool) bool {
188 if usePlugin && !PreActionCall("CursorUp", v) {
196 return PostActionCall("CursorUp", v)
201 // CursorDown moves the cursor down
202 func (v *View) CursorDown(usePlugin bool) bool {
203 if usePlugin && !PreActionCall("CursorDown", v) {
211 return PostActionCall("CursorDown", v)
216 // CursorLeft moves the cursor left
217 func (v *View) CursorLeft(usePlugin bool) bool {
218 if usePlugin && !PreActionCall("CursorLeft", v) {
222 if v.Cursor.HasSelection() {
223 v.Cursor.Loc = v.Cursor.CurSelection[0]
224 v.Cursor.ResetSelection()
225 v.Cursor.StoreVisualX()
227 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
228 tabmovement := v.Buf.Settings["tabmovement"].(bool)
229 if tabstospaces && tabmovement {
230 tabsize := int(v.Buf.Settings["tabsize"].(float64))
231 line := v.Buf.Line(v.Cursor.Y)
232 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
233 for i := 0; i < tabsize; i++ {
245 return PostActionCall("CursorLeft", v)
250 // CursorRight moves the cursor right
251 func (v *View) CursorRight(usePlugin bool) bool {
252 if usePlugin && !PreActionCall("CursorRight", v) {
256 if v.Cursor.HasSelection() {
257 v.Cursor.Loc = v.Cursor.CurSelection[1]
258 v.Cursor.ResetSelection()
259 v.Cursor.StoreVisualX()
261 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
262 tabmovement := v.Buf.Settings["tabmovement"].(bool)
263 if tabstospaces && tabmovement {
264 tabsize := int(v.Buf.Settings["tabsize"].(float64))
265 line := v.Buf.Line(v.Cursor.Y)
266 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
267 for i := 0; i < tabsize; i++ {
279 return PostActionCall("CursorRight", v)
284 // WordRight moves the cursor one word to the right
285 func (v *View) WordRight(usePlugin bool) bool {
286 if usePlugin && !PreActionCall("WordRight", v) {
293 return PostActionCall("WordRight", v)
298 // WordLeft moves the cursor one word to the left
299 func (v *View) WordLeft(usePlugin bool) bool {
300 if usePlugin && !PreActionCall("WordLeft", v) {
307 return PostActionCall("WordLeft", v)
312 // SelectUp selects up one line
313 func (v *View) SelectUp(usePlugin bool) bool {
314 if usePlugin && !PreActionCall("SelectUp", v) {
318 if !v.Cursor.HasSelection() {
319 v.Cursor.OrigSelection[0] = v.Cursor.Loc
322 v.Cursor.SelectTo(v.Cursor.Loc)
325 return PostActionCall("SelectUp", v)
330 // SelectDown selects down one line
331 func (v *View) SelectDown(usePlugin bool) bool {
332 if usePlugin && !PreActionCall("SelectDown", v) {
336 if !v.Cursor.HasSelection() {
337 v.Cursor.OrigSelection[0] = v.Cursor.Loc
340 v.Cursor.SelectTo(v.Cursor.Loc)
343 return PostActionCall("SelectDown", v)
348 // SelectLeft selects the character to the left of the cursor
349 func (v *View) SelectLeft(usePlugin bool) bool {
350 if usePlugin && !PreActionCall("SelectLeft", v) {
356 if loc.GreaterThan(count) {
359 if !v.Cursor.HasSelection() {
360 v.Cursor.OrigSelection[0] = loc
363 v.Cursor.SelectTo(v.Cursor.Loc)
366 return PostActionCall("SelectLeft", v)
371 // SelectRight selects the character to the right of the cursor
372 func (v *View) SelectRight(usePlugin bool) bool {
373 if usePlugin && !PreActionCall("SelectRight", v) {
379 if loc.GreaterThan(count) {
382 if !v.Cursor.HasSelection() {
383 v.Cursor.OrigSelection[0] = loc
386 v.Cursor.SelectTo(v.Cursor.Loc)
389 return PostActionCall("SelectRight", v)
394 // SelectWordRight selects the word to the right of the cursor
395 func (v *View) SelectWordRight(usePlugin bool) bool {
396 if usePlugin && !PreActionCall("SelectWordRight", v) {
400 if !v.Cursor.HasSelection() {
401 v.Cursor.OrigSelection[0] = v.Cursor.Loc
404 v.Cursor.SelectTo(v.Cursor.Loc)
407 return PostActionCall("SelectWordRight", v)
412 // SelectWordLeft selects the word to the left of the cursor
413 func (v *View) SelectWordLeft(usePlugin bool) bool {
414 if usePlugin && !PreActionCall("SelectWordLeft", v) {
418 if !v.Cursor.HasSelection() {
419 v.Cursor.OrigSelection[0] = v.Cursor.Loc
422 v.Cursor.SelectTo(v.Cursor.Loc)
425 return PostActionCall("SelectWordLeft", v)
430 // StartOfLine moves the cursor to the start of the line
431 func (v *View) StartOfLine(usePlugin bool) bool {
432 if usePlugin && !PreActionCall("StartOfLine", v) {
441 return PostActionCall("StartOfLine", v)
446 // EndOfLine moves the cursor to the end of the line
447 func (v *View) EndOfLine(usePlugin bool) bool {
448 if usePlugin && !PreActionCall("EndOfLine", v) {
457 return PostActionCall("EndOfLine", v)
462 // SelectLine selects the entire current line
463 func (v *View) SelectLine(usePlugin bool) bool {
464 if usePlugin && !PreActionCall("SelectLine", v) {
468 v.Cursor.SelectLine()
471 return PostActionCall("SelectLine", v)
476 // SelectToStartOfLine selects to the start of the current line
477 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
478 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
482 if !v.Cursor.HasSelection() {
483 v.Cursor.OrigSelection[0] = v.Cursor.Loc
486 v.Cursor.SelectTo(v.Cursor.Loc)
489 return PostActionCall("SelectToStartOfLine", v)
494 // SelectToEndOfLine selects to the end of the current line
495 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
496 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
500 if !v.Cursor.HasSelection() {
501 v.Cursor.OrigSelection[0] = v.Cursor.Loc
504 v.Cursor.SelectTo(v.Cursor.Loc)
507 return PostActionCall("SelectToEndOfLine", v)
512 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
513 func (v *View) ParagraphPrevious(usePlugin bool) bool {
514 if usePlugin && !PreActionCall("ParagraphPrevious", v) {
518 for line = v.Cursor.Y; line > 0; line-- {
519 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
525 // If no empty line found. move cursor to end of buffer
527 v.Cursor.Loc = v.Buf.Start()
531 return PostActionCall("ParagraphPrevious", v)
536 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
537 func (v *View) ParagraphNext(usePlugin bool) bool {
538 if usePlugin && !PreActionCall("ParagraphNext", v) {
543 for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
544 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
550 // If no empty line found. move cursor to end of buffer
551 if line == len(v.Buf.lines) {
552 v.Cursor.Loc = v.Buf.End()
556 return PostActionCall("ParagraphNext", v)
561 // Retab changes all tabs to spaces or all spaces to tabs depending
562 // on the user's settings
563 func (v *View) Retab(usePlugin bool) bool {
564 if usePlugin && !PreActionCall("Retab", v) {
568 toSpaces := v.Buf.Settings["tabstospaces"].(bool)
569 tabsize := int(v.Buf.Settings["tabsize"].(float64))
572 for i := 0; i < v.Buf.NumLines; i++ {
575 ws := GetLeadingWhitespace(l)
578 ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
580 ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
584 l = strings.TrimLeft(l, " \t")
585 v.Buf.lines[i].data = []byte(ws + l)
589 v.Buf.IsModified = dirty
592 return PostActionCall("Retab", v)
597 // CursorStart moves the cursor to the start of the buffer
598 func (v *View) CursorStart(usePlugin bool) bool {
599 if usePlugin && !PreActionCall("CursorStart", v) {
609 return PostActionCall("CursorStart", v)
614 // CursorEnd moves the cursor to the end of the buffer
615 func (v *View) CursorEnd(usePlugin bool) bool {
616 if usePlugin && !PreActionCall("CursorEnd", v) {
622 v.Cursor.Loc = v.Buf.End()
623 v.Cursor.StoreVisualX()
626 return PostActionCall("CursorEnd", v)
631 // SelectToStart selects the text from the cursor to the start of the buffer
632 func (v *View) SelectToStart(usePlugin bool) bool {
633 if usePlugin && !PreActionCall("SelectToStart", v) {
637 if !v.Cursor.HasSelection() {
638 v.Cursor.OrigSelection[0] = v.Cursor.Loc
641 v.Cursor.SelectTo(v.Buf.Start())
644 return PostActionCall("SelectToStart", v)
649 // SelectToEnd selects the text from the cursor to the end of the buffer
650 func (v *View) SelectToEnd(usePlugin bool) bool {
651 if usePlugin && !PreActionCall("SelectToEnd", v) {
655 if !v.Cursor.HasSelection() {
656 v.Cursor.OrigSelection[0] = v.Cursor.Loc
659 v.Cursor.SelectTo(v.Buf.End())
662 return PostActionCall("SelectToEnd", v)
667 // InsertSpace inserts a space
668 func (v *View) InsertSpace(usePlugin bool) bool {
669 if usePlugin && !PreActionCall("InsertSpace", v) {
673 if v.Cursor.HasSelection() {
674 v.Cursor.DeleteSelection()
675 v.Cursor.ResetSelection()
677 v.Buf.Insert(v.Cursor.Loc, " ")
681 return PostActionCall("InsertSpace", v)
686 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
687 func (v *View) InsertNewline(usePlugin bool) bool {
688 if usePlugin && !PreActionCall("InsertNewline", v) {
693 if v.Cursor.HasSelection() {
694 v.Cursor.DeleteSelection()
695 v.Cursor.ResetSelection()
698 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
700 v.Buf.Insert(v.Cursor.Loc, "\n")
703 if v.Buf.Settings["autoindent"].(bool) {
707 v.Buf.Insert(v.Cursor.Loc, ws)
708 // for i := 0; i < len(ws); i++ {
712 // Remove the whitespaces if keepautoindent setting is off
713 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
714 line := v.Buf.Line(v.Cursor.Y - 1)
715 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
718 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
721 return PostActionCall("InsertNewline", v)
726 // Backspace deletes the previous character
727 func (v *View) Backspace(usePlugin bool) bool {
728 if usePlugin && !PreActionCall("Backspace", v) {
732 // Delete a character
733 if v.Cursor.HasSelection() {
734 v.Cursor.DeleteSelection()
735 v.Cursor.ResetSelection()
736 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
737 // We have to do something a bit hacky here because we want to
738 // delete the line by first moving left and then deleting backwards
739 // but the undo redo would place the cursor in the wrong place
740 // So instead we move left, save the position, move back, delete
741 // and restore the position
743 // If the user is using spaces instead of tabs and they are deleting
744 // whitespace at the start of the line, we should delete as if it's a
745 // tab (tabSize number of spaces)
746 lineStart := sliceEnd(v.Buf.LineBytes(v.Cursor.Y), v.Cursor.X)
747 tabSize := int(v.Buf.Settings["tabsize"].(float64))
748 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && utf8.RuneCount(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
750 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
753 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
756 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
759 return PostActionCall("Backspace", v)
764 // DeleteWordRight deletes the word to the right of the cursor
765 func (v *View) DeleteWordRight(usePlugin bool) bool {
766 if usePlugin && !PreActionCall("DeleteWordRight", v) {
770 v.SelectWordRight(false)
771 if v.Cursor.HasSelection() {
772 v.Cursor.DeleteSelection()
773 v.Cursor.ResetSelection()
777 return PostActionCall("DeleteWordRight", v)
782 // DeleteWordLeft deletes the word to the left of the cursor
783 func (v *View) DeleteWordLeft(usePlugin bool) bool {
784 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
788 v.SelectWordLeft(false)
789 if v.Cursor.HasSelection() {
790 v.Cursor.DeleteSelection()
791 v.Cursor.ResetSelection()
795 return PostActionCall("DeleteWordLeft", v)
800 // Delete deletes the next character
801 func (v *View) Delete(usePlugin bool) bool {
802 if usePlugin && !PreActionCall("Delete", v) {
806 if v.Cursor.HasSelection() {
807 v.Cursor.DeleteSelection()
808 v.Cursor.ResetSelection()
811 if loc.LessThan(v.Buf.End()) {
812 v.Buf.Remove(loc, loc.Move(1, v.Buf))
817 return PostActionCall("Delete", v)
822 // IndentSelection indents the current selection
823 func (v *View) IndentSelection(usePlugin bool) bool {
824 if usePlugin && !PreActionCall("IndentSelection", v) {
828 if v.Cursor.HasSelection() {
829 start := v.Cursor.CurSelection[0]
830 end := v.Cursor.CurSelection[1]
832 start, end = end, start
833 v.Cursor.SetSelectionStart(start)
834 v.Cursor.SetSelectionEnd(end)
838 endY := end.Move(-1, v.Buf).Y
839 endX := end.Move(-1, v.Buf).X
840 tabsize := len(v.Buf.IndentString())
841 for y := startY; y <= endY; y++ {
842 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
843 if y == startY && start.X > 0 {
844 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
847 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
853 return PostActionCall("IndentSelection", v)
860 // OutdentLine moves the current line back one indentation
861 func (v *View) OutdentLine(usePlugin bool) bool {
862 if usePlugin && !PreActionCall("OutdentLine", v) {
866 if v.Cursor.HasSelection() {
870 for x := 0; x < len(v.Buf.IndentString()); x++ {
871 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
874 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
879 return PostActionCall("OutdentLine", v)
884 // OutdentSelection takes the current selection and moves it back one indent level
885 func (v *View) OutdentSelection(usePlugin bool) bool {
886 if usePlugin && !PreActionCall("OutdentSelection", v) {
890 if v.Cursor.HasSelection() {
891 start := v.Cursor.CurSelection[0]
892 end := v.Cursor.CurSelection[1]
894 start, end = end, start
895 v.Cursor.SetSelectionStart(start)
896 v.Cursor.SetSelectionEnd(end)
900 endY := end.Move(-1, v.Buf).Y
901 for y := startY; y <= endY; y++ {
902 for x := 0; x < len(v.Buf.IndentString()); x++ {
903 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
906 v.Buf.Remove(Loc{0, y}, Loc{1, y})
912 return PostActionCall("OutdentSelection", v)
919 // InsertTab inserts a tab or spaces
920 func (v *View) InsertTab(usePlugin bool) bool {
921 if usePlugin && !PreActionCall("InsertTab", v) {
925 if v.Cursor.HasSelection() {
929 tabBytes := len(v.Buf.IndentString())
930 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
931 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
932 // for i := 0; i < bytesUntilIndent; i++ {
937 return PostActionCall("InsertTab", v)
942 // SaveAll saves all open buffers
943 func (v *View) SaveAll(usePlugin bool) bool {
945 if usePlugin && !PreActionCall("SaveAll", v) {
949 for _, t := range tabs {
950 for _, v := range t.views {
956 return PostActionCall("SaveAll", v)
962 // Save the buffer to disk
963 func (v *View) Save(usePlugin bool) bool {
965 if usePlugin && !PreActionCall("Save", v) {
969 if v.Type.Scratch == true {
970 // We can't save any view type with scratch set. eg help and log text
973 // If this is an empty buffer, ask for a filename
974 if v.Buf.Path == "" {
977 v.saveToFile(v.Buf.Path)
981 return PostActionCall("Save", v)
987 // This function saves the buffer to `filename` and changes the buffer's path and name
988 // to `filename` if the save is successful
989 func (v *View) saveToFile(filename string) {
990 err := v.Buf.SaveAs(filename)
992 if strings.HasSuffix(err.Error(), "permission denied") {
993 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
995 err = v.Buf.SaveAsWithSudo(filename)
997 messenger.Error(err.Error())
999 v.Buf.Path = filename
1000 v.Buf.name = filename
1001 messenger.Message("Saved " + filename)
1007 messenger.Error(err.Error())
1010 v.Buf.Path = filename
1011 v.Buf.name = filename
1012 messenger.Message("Saved " + filename)
1016 // SaveAs saves the buffer to disk with the given name
1017 func (v *View) SaveAs(usePlugin bool) bool {
1019 if usePlugin && !PreActionCall("SaveAs", v) {
1023 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
1025 // the filename might or might not be quoted, so unquote first then join the strings.
1026 args, err := shellwords.Split(filename)
1027 filename = strings.Join(args, " ")
1029 messenger.Error("Error parsing arguments: ", err)
1032 v.saveToFile(filename)
1036 PostActionCall("SaveAs", v)
1042 // Find opens a prompt and searches forward for the input
1043 func (v *View) Find(usePlugin bool) bool {
1045 if usePlugin && !PreActionCall("Find", v) {
1050 if v.Cursor.HasSelection() {
1051 searchStart = v.Cursor.CurSelection[1]
1052 searchStart = v.Cursor.CurSelection[1]
1053 searchStr = v.Cursor.GetSelection()
1055 searchStart = v.Cursor.Loc
1057 BeginSearch(searchStr)
1060 return PostActionCall("Find", v)
1066 // FindNext searches forwards for the last used search term
1067 func (v *View) FindNext(usePlugin bool) bool {
1068 if usePlugin && !PreActionCall("FindNext", v) {
1072 if v.Cursor.HasSelection() {
1073 searchStart = v.Cursor.CurSelection[1]
1074 // lastSearch = v.Cursor.GetSelection()
1076 searchStart = v.Cursor.Loc
1078 if lastSearch == "" {
1081 messenger.Message("Finding: " + lastSearch)
1082 Search(lastSearch, v, true)
1085 return PostActionCall("FindNext", v)
1090 // FindPrevious searches backwards for the last used search term
1091 func (v *View) FindPrevious(usePlugin bool) bool {
1092 if usePlugin && !PreActionCall("FindPrevious", v) {
1096 if v.Cursor.HasSelection() {
1097 searchStart = v.Cursor.CurSelection[0]
1099 searchStart = v.Cursor.Loc
1101 messenger.Message("Finding: " + lastSearch)
1102 Search(lastSearch, v, false)
1105 return PostActionCall("FindPrevious", v)
1110 // Undo undoes the last action
1111 func (v *View) Undo(usePlugin bool) bool {
1112 if usePlugin && !PreActionCall("Undo", v) {
1116 if v.Buf.curCursor == 0 {
1117 v.Buf.clearCursors()
1121 messenger.Message("Undid action")
1124 return PostActionCall("Undo", v)
1129 // Redo redoes the last action
1130 func (v *View) Redo(usePlugin bool) bool {
1131 if usePlugin && !PreActionCall("Redo", v) {
1135 if v.Buf.curCursor == 0 {
1136 v.Buf.clearCursors()
1140 messenger.Message("Redid action")
1143 return PostActionCall("Redo", v)
1148 // Copy the selection to the system clipboard
1149 func (v *View) Copy(usePlugin bool) bool {
1151 if usePlugin && !PreActionCall("Copy", v) {
1155 if v.Cursor.HasSelection() {
1156 v.Cursor.CopySelection("clipboard")
1158 messenger.Message("Copied selection")
1162 return PostActionCall("Copy", v)
1168 // CutLine cuts the current line to the clipboard
1169 func (v *View) CutLine(usePlugin bool) bool {
1170 if usePlugin && !PreActionCall("CutLine", v) {
1174 v.Cursor.SelectLine()
1175 if !v.Cursor.HasSelection() {
1178 if v.freshClip == true {
1179 if v.Cursor.HasSelection() {
1180 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1181 messenger.Error(err)
1183 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1186 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1190 v.lastCutTime = time.Now()
1191 v.Cursor.DeleteSelection()
1192 v.Cursor.ResetSelection()
1193 messenger.Message("Cut line")
1196 return PostActionCall("CutLine", v)
1201 // Cut the selection to the system clipboard
1202 func (v *View) Cut(usePlugin bool) bool {
1203 if usePlugin && !PreActionCall("Cut", v) {
1207 if v.Cursor.HasSelection() {
1208 v.Cursor.CopySelection("clipboard")
1209 v.Cursor.DeleteSelection()
1210 v.Cursor.ResetSelection()
1212 messenger.Message("Cut selection")
1215 return PostActionCall("Cut", v)
1223 // DuplicateLine duplicates the current line or selection
1224 func (v *View) DuplicateLine(usePlugin bool) bool {
1225 if usePlugin && !PreActionCall("DuplicateLine", v) {
1229 if v.Cursor.HasSelection() {
1230 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1233 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1237 messenger.Message("Duplicated line")
1240 return PostActionCall("DuplicateLine", v)
1245 // DeleteLine deletes the current line
1246 func (v *View) DeleteLine(usePlugin bool) bool {
1247 if usePlugin && !PreActionCall("DeleteLine", v) {
1251 v.Cursor.SelectLine()
1252 if !v.Cursor.HasSelection() {
1255 v.Cursor.DeleteSelection()
1256 v.Cursor.ResetSelection()
1257 messenger.Message("Deleted line")
1260 return PostActionCall("DeleteLine", v)
1265 // MoveLinesUp moves up the current line or selected lines if any
1266 func (v *View) MoveLinesUp(usePlugin bool) bool {
1267 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1271 if v.Cursor.HasSelection() {
1272 if v.Cursor.CurSelection[0].Y == 0 {
1273 messenger.Message("Can not move further up")
1276 start := v.Cursor.CurSelection[0].Y
1277 end := v.Cursor.CurSelection[1].Y
1279 end, start = start, end
1286 v.Cursor.CurSelection[1].Y -= 1
1287 messenger.Message("Moved up selected line(s)")
1289 if v.Cursor.Loc.Y == 0 {
1290 messenger.Message("Can not move further up")
1297 messenger.Message("Moved up current line")
1299 v.Buf.IsModified = true
1302 return PostActionCall("MoveLinesUp", v)
1307 // MoveLinesDown moves down the current line or selected lines if any
1308 func (v *View) MoveLinesDown(usePlugin bool) bool {
1309 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1313 if v.Cursor.HasSelection() {
1314 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1315 messenger.Message("Can not move further down")
1318 start := v.Cursor.CurSelection[0].Y
1319 end := v.Cursor.CurSelection[1].Y
1321 end, start = start, end
1324 v.Buf.MoveLinesDown(
1328 messenger.Message("Moved down selected line(s)")
1330 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1331 messenger.Message("Can not move further down")
1334 v.Buf.MoveLinesDown(
1338 messenger.Message("Moved down current line")
1340 v.Buf.IsModified = true
1343 return PostActionCall("MoveLinesDown", v)
1348 // Paste whatever is in the system clipboard into the buffer
1349 // Delete and paste if the user has a selection
1350 func (v *View) Paste(usePlugin bool) bool {
1351 if usePlugin && !PreActionCall("Paste", v) {
1355 clip, _ := clipboard.ReadAll("clipboard")
1359 return PostActionCall("Paste", v)
1364 // PastePrimary pastes from the primary clipboard (only use on linux)
1365 func (v *View) PastePrimary(usePlugin bool) bool {
1366 if usePlugin && !PreActionCall("Paste", v) {
1370 clip, _ := clipboard.ReadAll("primary")
1374 return PostActionCall("Paste", v)
1379 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1380 // currently on a brace
1381 func (v *View) JumpToMatchingBrace(usePlugin bool) bool {
1382 if usePlugin && !PreActionCall("JumpToMatchingBrace", v) {
1386 for _, bp := range bracePairs {
1387 r := v.Cursor.RuneUnder(v.Cursor.X)
1388 if r == bp[0] || r == bp[1] {
1389 matchingBrace := v.Buf.FindMatchingBrace(bp, v.Cursor.Loc)
1390 v.Cursor.GotoLoc(matchingBrace)
1395 return PostActionCall("JumpToMatchingBrace", v)
1400 // SelectAll selects the entire buffer
1401 func (v *View) SelectAll(usePlugin bool) bool {
1402 if usePlugin && !PreActionCall("SelectAll", v) {
1406 v.Cursor.SetSelectionStart(v.Buf.Start())
1407 v.Cursor.SetSelectionEnd(v.Buf.End())
1408 // Put the cursor at the beginning
1413 return PostActionCall("SelectAll", v)
1418 // OpenFile opens a new file in the buffer
1419 func (v *View) OpenFile(usePlugin bool) bool {
1421 if usePlugin && !PreActionCall("OpenFile", v) {
1426 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1428 HandleCommand(input)
1430 return PostActionCall("OpenFile", v)
1438 // Start moves the viewport to the start of the buffer
1439 func (v *View) Start(usePlugin bool) bool {
1441 if usePlugin && !PreActionCall("Start", v) {
1448 return PostActionCall("Start", v)
1454 // End moves the viewport to the end of the buffer
1455 func (v *View) End(usePlugin bool) bool {
1457 if usePlugin && !PreActionCall("End", v) {
1461 if v.Height > v.Buf.NumLines {
1464 v.Topline = v.Buf.NumLines - v.Height
1468 return PostActionCall("End", v)
1474 // PageUp scrolls the view up a page
1475 func (v *View) PageUp(usePlugin bool) bool {
1477 if usePlugin && !PreActionCall("PageUp", v) {
1481 if v.Topline > v.Height {
1482 v.ScrollUp(v.Height)
1488 return PostActionCall("PageUp", v)
1494 // PageDown scrolls the view down a page
1495 func (v *View) PageDown(usePlugin bool) bool {
1497 if usePlugin && !PreActionCall("PageDown", v) {
1501 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1502 v.ScrollDown(v.Height)
1503 } else if v.Buf.NumLines >= v.Height {
1504 v.Topline = v.Buf.NumLines - v.Height
1508 return PostActionCall("PageDown", v)
1514 // CursorPageUp places the cursor a page up
1515 func (v *View) CursorPageUp(usePlugin bool) bool {
1516 if usePlugin && !PreActionCall("CursorPageUp", v) {
1522 if v.Cursor.HasSelection() {
1523 v.Cursor.Loc = v.Cursor.CurSelection[0]
1524 v.Cursor.ResetSelection()
1525 v.Cursor.StoreVisualX()
1527 v.Cursor.UpN(v.Height)
1530 return PostActionCall("CursorPageUp", v)
1535 // CursorPageDown places the cursor a page up
1536 func (v *View) CursorPageDown(usePlugin bool) bool {
1537 if usePlugin && !PreActionCall("CursorPageDown", v) {
1543 if v.Cursor.HasSelection() {
1544 v.Cursor.Loc = v.Cursor.CurSelection[1]
1545 v.Cursor.ResetSelection()
1546 v.Cursor.StoreVisualX()
1548 v.Cursor.DownN(v.Height)
1551 return PostActionCall("CursorPageDown", v)
1556 // HalfPageUp scrolls the view up half a page
1557 func (v *View) HalfPageUp(usePlugin bool) bool {
1559 if usePlugin && !PreActionCall("HalfPageUp", v) {
1563 if v.Topline > v.Height/2 {
1564 v.ScrollUp(v.Height / 2)
1570 return PostActionCall("HalfPageUp", v)
1576 // HalfPageDown scrolls the view down half a page
1577 func (v *View) HalfPageDown(usePlugin bool) bool {
1579 if usePlugin && !PreActionCall("HalfPageDown", v) {
1583 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1584 v.ScrollDown(v.Height / 2)
1586 if v.Buf.NumLines >= v.Height {
1587 v.Topline = v.Buf.NumLines - v.Height
1592 return PostActionCall("HalfPageDown", v)
1598 // ToggleRuler turns line numbers off and on
1599 func (v *View) ToggleRuler(usePlugin bool) bool {
1601 if usePlugin && !PreActionCall("ToggleRuler", v) {
1605 if v.Buf.Settings["ruler"] == false {
1606 v.Buf.Settings["ruler"] = true
1607 messenger.Message("Enabled ruler")
1609 v.Buf.Settings["ruler"] = false
1610 messenger.Message("Disabled ruler")
1614 return PostActionCall("ToggleRuler", v)
1620 // JumpLine jumps to a line and moves the view accordingly.
1621 func (v *View) JumpLine(usePlugin bool) bool {
1622 if usePlugin && !PreActionCall("JumpLine", v) {
1626 // Prompt for line number
1627 message := fmt.Sprintf("Jump to line:col (1 - %v) # ", v.Buf.NumLines)
1628 input, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
1635 if strings.Contains(input, ":") {
1636 split := strings.Split(input, ":")
1637 lineInt, err = strconv.Atoi(split[0])
1639 messenger.Message("Invalid line number")
1642 colInt, err = strconv.Atoi(split[1])
1644 messenger.Message("Invalid column number")
1648 lineInt, err = strconv.Atoi(input)
1650 messenger.Message("Invalid line number")
1655 // Move cursor and view if possible.
1656 if lineInt < v.Buf.NumLines && lineInt >= 0 {
1658 v.Cursor.Y = lineInt
1661 return PostActionCall("JumpLine", v)
1665 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1669 // ClearStatus clears the messenger bar
1670 func (v *View) ClearStatus(usePlugin bool) bool {
1672 if usePlugin && !PreActionCall("ClearStatus", v) {
1676 messenger.Message("")
1679 return PostActionCall("ClearStatus", v)
1685 // ToggleHelp toggles the help screen
1686 func (v *View) ToggleHelp(usePlugin bool) bool {
1688 if usePlugin && !PreActionCall("ToggleHelp", v) {
1692 if v.Type != vtHelp {
1693 // Open the default help
1700 return PostActionCall("ToggleHelp", v)
1706 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1707 func (v *View) ToggleKeyMenu(usePlugin bool) bool {
1709 if usePlugin && !PreActionCall("ToggleBindings", v) {
1713 globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
1714 for _, tab := range tabs {
1719 return PostActionCall("ToggleBindings", v)
1725 // ShellMode opens a terminal to run a shell command
1726 func (v *View) ShellMode(usePlugin bool) bool {
1728 if usePlugin && !PreActionCall("ShellMode", v) {
1732 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1734 // The true here is for openTerm to make the command interactive
1735 HandleShellCommand(input, true, true)
1737 return PostActionCall("ShellMode", v)
1744 // CommandMode lets the user enter a command
1745 func (v *View) CommandMode(usePlugin bool) bool {
1747 if usePlugin && !PreActionCall("CommandMode", v) {
1751 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1753 HandleCommand(input)
1755 return PostActionCall("CommandMode", v)
1763 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1764 func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
1766 if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
1770 v.isOverwriteMode = !v.isOverwriteMode
1773 return PostActionCall("ToggleOverwriteMode", v)
1779 // Escape leaves current mode
1780 func (v *View) Escape(usePlugin bool) bool {
1782 // check if user is searching, or the last search is still active
1783 if searching || lastSearch != "" {
1787 // check if a prompt is shown, hide it and don't quit
1788 if messenger.hasPrompt {
1789 messenger.Reset() // FIXME
1797 // Quit this will close the current tab or view that is open
1798 func (v *View) Quit(usePlugin bool) bool {
1800 if usePlugin && !PreActionCall("Quit", v) {
1804 // Make sure not to quit if there are unsaved changes
1807 if len(tabs[curTab].views) > 1 {
1808 v.splitNode.Delete()
1809 tabs[v.TabNum].Cleanup()
1810 tabs[v.TabNum].Resize()
1811 } else if len(tabs) > 1 {
1812 if len(tabs[v.TabNum].views) == 1 {
1813 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1814 for i, t := range tabs {
1817 if curTab >= len(tabs) {
1821 CurView().ToggleTabbar()
1826 PostActionCall("Quit", v)
1830 messenger.SaveHistory()
1836 return PostActionCall("Quit", v)
1842 // QuitAll quits the whole editor; all splits and tabs
1843 func (v *View) QuitAll(usePlugin bool) bool {
1845 if usePlugin && !PreActionCall("QuitAll", v) {
1850 for _, tab := range tabs {
1851 for _, v := range tab.views {
1859 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1860 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1863 for _, tab := range tabs {
1864 for _, v := range tab.views {
1870 PostActionCall("QuitAll", v)
1874 messenger.SaveHistory()
1883 // AddTab adds a new tab with an empty buffer
1884 func (v *View) AddTab(usePlugin bool) bool {
1886 if usePlugin && !PreActionCall("AddTab", v) {
1890 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1891 tab.SetNum(len(tabs))
1892 tabs = append(tabs, tab)
1893 curTab = len(tabs) - 1
1895 for _, t := range tabs {
1896 for _, v := range t.views {
1903 return PostActionCall("AddTab", v)
1909 // PreviousTab switches to the previous tab in the tab list
1910 func (v *View) PreviousTab(usePlugin bool) bool {
1912 if usePlugin && !PreActionCall("PreviousTab", v) {
1918 } else if curTab == 0 {
1919 curTab = len(tabs) - 1
1923 return PostActionCall("PreviousTab", v)
1929 // NextTab switches to the next tab in the tab list
1930 func (v *View) NextTab(usePlugin bool) bool {
1932 if usePlugin && !PreActionCall("NextTab", v) {
1936 if curTab < len(tabs)-1 {
1938 } else if curTab == len(tabs)-1 {
1943 return PostActionCall("NextTab", v)
1949 // VSplitBinding opens an empty vertical split
1950 func (v *View) VSplitBinding(usePlugin bool) bool {
1952 if usePlugin && !PreActionCall("VSplit", v) {
1956 v.VSplit(NewBufferFromString("", ""))
1959 return PostActionCall("VSplit", v)
1965 // HSplitBinding opens an empty horizontal split
1966 func (v *View) HSplitBinding(usePlugin bool) bool {
1968 if usePlugin && !PreActionCall("HSplit", v) {
1972 v.HSplit(NewBufferFromString("", ""))
1975 return PostActionCall("HSplit", v)
1981 // Unsplit closes all splits in the current tab except the active one
1982 func (v *View) Unsplit(usePlugin bool) bool {
1984 if usePlugin && !PreActionCall("Unsplit", v) {
1988 curView := tabs[curTab].CurView
1989 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1990 view := tabs[curTab].views[i]
1991 if view != nil && view.Num != curView {
1993 // messenger.Message("Quit ", view.Buf.Path)
1998 return PostActionCall("Unsplit", v)
2004 // NextSplit changes the view to the next split
2005 func (v *View) NextSplit(usePlugin bool) bool {
2007 if usePlugin && !PreActionCall("NextSplit", v) {
2012 if tab.CurView < len(tab.views)-1 {
2019 return PostActionCall("NextSplit", v)
2025 // PreviousSplit changes the view to the previous split
2026 func (v *View) PreviousSplit(usePlugin bool) bool {
2028 if usePlugin && !PreActionCall("PreviousSplit", v) {
2033 if tab.CurView > 0 {
2036 tab.CurView = len(tab.views) - 1
2040 return PostActionCall("PreviousSplit", v)
2046 var curMacro []interface{}
2047 var recordingMacro bool
2049 // ToggleMacro toggles recording of a macro
2050 func (v *View) ToggleMacro(usePlugin bool) bool {
2052 if usePlugin && !PreActionCall("ToggleMacro", v) {
2056 recordingMacro = !recordingMacro
2059 curMacro = []interface{}{}
2060 messenger.Message("Recording")
2062 messenger.Message("Stopped recording")
2066 return PostActionCall("ToggleMacro", v)
2072 // PlayMacro plays back the most recently recorded macro
2073 func (v *View) PlayMacro(usePlugin bool) bool {
2074 if usePlugin && !PreActionCall("PlayMacro", v) {
2078 for _, action := range curMacro {
2079 switch t := action.(type) {
2081 // Insert a character
2082 if v.Cursor.HasSelection() {
2083 v.Cursor.DeleteSelection()
2084 v.Cursor.ResetSelection()
2086 v.Buf.Insert(v.Cursor.Loc, string(t))
2089 for pl := range loadedPlugins {
2090 _, err := Call(pl+".onRune", string(t), v)
2091 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
2095 case func(*View, bool) bool:
2101 return PostActionCall("PlayMacro", v)
2106 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
2107 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
2108 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
2109 // You can only spawn a cursor from the main cursor
2110 if v.Cursor == spawner {
2111 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
2115 if !spawner.HasSelection() {
2116 spawner.SelectWord()
2122 sel := spawner.GetSelection()
2124 searchStart = spawner.CurSelection[1]
2126 Search(regexp.QuoteMeta(sel), v, true)
2128 for _, cur := range v.Buf.cursors {
2129 if c.Loc == cur.Loc {
2133 v.Buf.cursors = append(v.Buf.cursors, c)
2134 v.Buf.UpdateCursors()
2140 PostActionCall("SpawnMultiCursor", v)
2147 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
2148 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
2149 if v.Cursor == &v.Buf.Cursor {
2150 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
2153 x, y := e.Position()
2154 x -= v.lineNumOffset - v.leftCol + v.x
2155 y += v.Topline - v.y
2161 v.MoveToMouseClick(x, y)
2163 v.Cursor = &v.Buf.Cursor
2165 v.Buf.cursors = append(v.Buf.cursors, c)
2166 v.Buf.MergeCursors()
2167 v.Buf.UpdateCursors()
2170 PostActionCall("SpawnMultiCursorAtMouse", v)
2176 // SkipMultiCursor moves the current multiple cursor to the next available position
2177 func (v *View) SkipMultiCursor(usePlugin bool) bool {
2178 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
2181 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
2184 sel := cursor.GetSelection()
2186 searchStart = cursor.CurSelection[1]
2188 Search(regexp.QuoteMeta(sel), v, true)
2193 PostActionCall("SkipMultiCursor", v)
2199 // RemoveMultiCursor removes the latest multiple cursor
2200 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
2201 end := len(v.Buf.cursors)
2204 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
2208 v.Buf.cursors[end-1] = nil
2209 v.Buf.cursors = v.Buf.cursors[:end-1]
2210 v.Buf.UpdateCursors()
2214 return PostActionCall("RemoveMultiCursor", v)
2219 v.RemoveAllMultiCursors(usePlugin)
2224 // RemoveAllMultiCursors removes all cursors except the base cursor
2225 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
2227 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
2231 v.Buf.clearCursors()
2235 return PostActionCall("RemoveAllMultiCursors", v)