11 "github.com/yuin/gopher-lua"
12 "github.com/zyedidia/clipboard"
13 "github.com/zyedidia/micro/cmd/micro/shellwords"
14 "github.com/zyedidia/tcell"
17 // PreActionCall executes the lua pre callback if possible
18 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
20 for pl := range loadedPlugins {
21 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
22 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
26 if ret == lua.LFalse {
33 // PostActionCall executes the lua plugin callback if possible
34 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
36 for pl := range loadedPlugins {
37 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
38 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
42 if ret == lua.LFalse {
49 func (v *View) deselect(index int) bool {
50 if v.Cursor.HasSelection() {
51 v.Cursor.Loc = v.Cursor.CurSelection[index]
52 v.Cursor.ResetSelection()
53 v.Cursor.StoreVisualX()
59 // MousePress is the event that should happen when a normal click happens
60 // This is almost always bound to left click
61 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
62 if usePlugin && !PreActionCall("MousePress", v, e) {
67 x -= v.lineNumOffset - v.leftCol + v.x
70 // This is usually bound to left click
71 v.MoveToMouseClick(x, y)
73 if len(v.Buf.cursors) > 1 {
74 for i := 1; i < len(v.Buf.cursors); i++ {
75 v.Buf.cursors[i] = nil
77 v.Buf.cursors = v.Buf.cursors[:1]
79 v.Cursor.ResetSelection()
82 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
85 v.lastClickTime = time.Now()
91 v.Cursor.CopySelection("primary")
94 v.lastClickTime = time.Now()
100 v.Cursor.CopySelection("primary")
103 v.doubleClick = false
104 v.tripleClick = false
105 v.lastClickTime = time.Now()
107 v.Cursor.OrigSelection[0] = v.Cursor.Loc
108 v.Cursor.CurSelection[0] = v.Cursor.Loc
109 v.Cursor.CurSelection[1] = v.Cursor.Loc
111 v.mouseReleased = false
112 } else if !v.mouseReleased {
114 v.Cursor.AddLineToSelection()
115 } else if v.doubleClick {
116 v.Cursor.AddWordToSelection()
118 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
119 v.Cursor.CopySelection("primary")
123 v.lastLoc = Loc{x, y}
126 PostActionCall("MousePress", v, e)
131 // ScrollUpAction scrolls the view up
132 func (v *View) ScrollUpAction(usePlugin bool) bool {
134 if usePlugin && !PreActionCall("ScrollUp", v) {
138 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
139 v.ScrollUp(scrollspeed)
142 PostActionCall("ScrollUp", v)
148 // ScrollDownAction scrolls the view up
149 func (v *View) ScrollDownAction(usePlugin bool) bool {
151 if usePlugin && !PreActionCall("ScrollDown", v) {
155 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
156 v.ScrollDown(scrollspeed)
159 PostActionCall("ScrollDown", v)
165 // Center centers the view on the cursor
166 func (v *View) Center(usePlugin bool) bool {
167 if usePlugin && !PreActionCall("Center", v) {
171 v.Topline = v.Cursor.Y - v.Height/2
172 if v.Topline+v.Height > v.Buf.NumLines {
173 v.Topline = v.Buf.NumLines - v.Height
180 return PostActionCall("Center", v)
185 // CursorUp moves the cursor up
186 func (v *View) CursorUp(usePlugin bool) bool {
187 if usePlugin && !PreActionCall("CursorUp", v) {
195 return PostActionCall("CursorUp", v)
200 // CursorDown moves the cursor down
201 func (v *View) CursorDown(usePlugin bool) bool {
202 if usePlugin && !PreActionCall("CursorDown", v) {
210 return PostActionCall("CursorDown", v)
215 // CursorLeft moves the cursor left
216 func (v *View) CursorLeft(usePlugin bool) bool {
217 if usePlugin && !PreActionCall("CursorLeft", v) {
221 if v.Cursor.HasSelection() {
222 v.Cursor.Loc = v.Cursor.CurSelection[0]
223 v.Cursor.ResetSelection()
224 v.Cursor.StoreVisualX()
226 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
227 tabmovement := v.Buf.Settings["tabmovement"].(bool)
228 if tabstospaces && tabmovement {
229 tabsize := int(v.Buf.Settings["tabsize"].(float64))
230 line := v.Buf.Line(v.Cursor.Y)
231 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
232 for i := 0; i < tabsize; i++ {
244 return PostActionCall("CursorLeft", v)
249 // CursorRight moves the cursor right
250 func (v *View) CursorRight(usePlugin bool) bool {
251 if usePlugin && !PreActionCall("CursorRight", v) {
255 if v.Cursor.HasSelection() {
256 v.Cursor.Loc = v.Cursor.CurSelection[1]
257 v.Cursor.ResetSelection()
258 v.Cursor.StoreVisualX()
260 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
261 tabmovement := v.Buf.Settings["tabmovement"].(bool)
262 if tabstospaces && tabmovement {
263 tabsize := int(v.Buf.Settings["tabsize"].(float64))
264 line := v.Buf.Line(v.Cursor.Y)
265 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
266 for i := 0; i < tabsize; i++ {
278 return PostActionCall("CursorRight", v)
283 // WordRight moves the cursor one word to the right
284 func (v *View) WordRight(usePlugin bool) bool {
285 if usePlugin && !PreActionCall("WordRight", v) {
292 return PostActionCall("WordRight", v)
297 // WordLeft moves the cursor one word to the left
298 func (v *View) WordLeft(usePlugin bool) bool {
299 if usePlugin && !PreActionCall("WordLeft", v) {
306 return PostActionCall("WordLeft", v)
311 // SelectUp selects up one line
312 func (v *View) SelectUp(usePlugin bool) bool {
313 if usePlugin && !PreActionCall("SelectUp", v) {
317 if !v.Cursor.HasSelection() {
318 v.Cursor.OrigSelection[0] = v.Cursor.Loc
321 v.Cursor.SelectTo(v.Cursor.Loc)
324 return PostActionCall("SelectUp", v)
329 // SelectDown selects down one line
330 func (v *View) SelectDown(usePlugin bool) bool {
331 if usePlugin && !PreActionCall("SelectDown", v) {
335 if !v.Cursor.HasSelection() {
336 v.Cursor.OrigSelection[0] = v.Cursor.Loc
339 v.Cursor.SelectTo(v.Cursor.Loc)
342 return PostActionCall("SelectDown", v)
347 // SelectLeft selects the character to the left of the cursor
348 func (v *View) SelectLeft(usePlugin bool) bool {
349 if usePlugin && !PreActionCall("SelectLeft", v) {
355 if loc.GreaterThan(count) {
358 if !v.Cursor.HasSelection() {
359 v.Cursor.OrigSelection[0] = loc
362 v.Cursor.SelectTo(v.Cursor.Loc)
365 return PostActionCall("SelectLeft", v)
370 // SelectRight selects the character to the right of the cursor
371 func (v *View) SelectRight(usePlugin bool) bool {
372 if usePlugin && !PreActionCall("SelectRight", v) {
378 if loc.GreaterThan(count) {
381 if !v.Cursor.HasSelection() {
382 v.Cursor.OrigSelection[0] = loc
385 v.Cursor.SelectTo(v.Cursor.Loc)
388 return PostActionCall("SelectRight", v)
393 // SelectWordRight selects the word to the right of the cursor
394 func (v *View) SelectWordRight(usePlugin bool) bool {
395 if usePlugin && !PreActionCall("SelectWordRight", v) {
399 if !v.Cursor.HasSelection() {
400 v.Cursor.OrigSelection[0] = v.Cursor.Loc
403 v.Cursor.SelectTo(v.Cursor.Loc)
406 return PostActionCall("SelectWordRight", v)
411 // SelectWordLeft selects the word to the left of the cursor
412 func (v *View) SelectWordLeft(usePlugin bool) bool {
413 if usePlugin && !PreActionCall("SelectWordLeft", v) {
417 if !v.Cursor.HasSelection() {
418 v.Cursor.OrigSelection[0] = v.Cursor.Loc
421 v.Cursor.SelectTo(v.Cursor.Loc)
424 return PostActionCall("SelectWordLeft", v)
429 // StartOfLine moves the cursor to the start of the line
430 func (v *View) StartOfLine(usePlugin bool) bool {
431 if usePlugin && !PreActionCall("StartOfLine", v) {
440 return PostActionCall("StartOfLine", v)
445 // EndOfLine moves the cursor to the end of the line
446 func (v *View) EndOfLine(usePlugin bool) bool {
447 if usePlugin && !PreActionCall("EndOfLine", v) {
456 return PostActionCall("EndOfLine", v)
461 // SelectToStartOfLine selects to the start of the current line
462 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
463 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
467 if !v.Cursor.HasSelection() {
468 v.Cursor.OrigSelection[0] = v.Cursor.Loc
471 v.Cursor.SelectTo(v.Cursor.Loc)
474 return PostActionCall("SelectToStartOfLine", v)
479 // SelectToEndOfLine selects to the end of the current line
480 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
481 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
485 if !v.Cursor.HasSelection() {
486 v.Cursor.OrigSelection[0] = v.Cursor.Loc
489 v.Cursor.SelectTo(v.Cursor.Loc)
492 return PostActionCall("SelectToEndOfLine", v)
497 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
498 func (v *View) ParagraphPrevious(usePlugin bool) bool {
499 if usePlugin && !PreActionCall("ParagraphPrevious", v) {
503 for line = v.Cursor.Y; line > 0; line-- {
504 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
510 // If no empty line found. move cursor to end of buffer
512 v.Cursor.Loc = v.Buf.Start()
516 return PostActionCall("ParagraphPrevious", v)
521 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
522 func (v *View) ParagraphNext(usePlugin bool) bool {
523 if usePlugin && !PreActionCall("ParagraphNext", v) {
528 for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
529 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
535 // If no empty line found. move cursor to end of buffer
536 if line == len(v.Buf.lines) {
537 v.Cursor.Loc = v.Buf.End()
541 return PostActionCall("ParagraphNext", v)
546 func (v *View) Retab(usePlugin bool) bool {
547 if usePlugin && !PreActionCall("Retab", v) {
551 toSpaces := v.Buf.Settings["tabstospaces"].(bool)
552 tabsize := int(v.Buf.Settings["tabsize"].(float64))
555 for i := 0; i < v.Buf.NumLines; i++ {
558 ws := GetLeadingWhitespace(l)
561 ws = strings.Replace(ws, "\t", Spaces(tabsize), -1)
563 ws = strings.Replace(ws, Spaces(tabsize), "\t", -1)
567 l = strings.TrimLeft(l, " \t")
568 v.Buf.lines[i].data = []byte(ws + l)
572 v.Buf.IsModified = dirty
575 return PostActionCall("Retab", v)
580 // CursorStart moves the cursor to the start of the buffer
581 func (v *View) CursorStart(usePlugin bool) bool {
582 if usePlugin && !PreActionCall("CursorStart", v) {
592 return PostActionCall("CursorStart", v)
597 // CursorEnd moves the cursor to the end of the buffer
598 func (v *View) CursorEnd(usePlugin bool) bool {
599 if usePlugin && !PreActionCall("CursorEnd", v) {
605 v.Cursor.Loc = v.Buf.End()
606 v.Cursor.StoreVisualX()
609 return PostActionCall("CursorEnd", v)
614 // SelectToStart selects the text from the cursor to the start of the buffer
615 func (v *View) SelectToStart(usePlugin bool) bool {
616 if usePlugin && !PreActionCall("SelectToStart", v) {
620 if !v.Cursor.HasSelection() {
621 v.Cursor.OrigSelection[0] = v.Cursor.Loc
624 v.Cursor.SelectTo(v.Buf.Start())
627 return PostActionCall("SelectToStart", v)
632 // SelectToEnd selects the text from the cursor to the end of the buffer
633 func (v *View) SelectToEnd(usePlugin bool) bool {
634 if usePlugin && !PreActionCall("SelectToEnd", v) {
638 if !v.Cursor.HasSelection() {
639 v.Cursor.OrigSelection[0] = v.Cursor.Loc
642 v.Cursor.SelectTo(v.Buf.End())
645 return PostActionCall("SelectToEnd", v)
650 // InsertSpace inserts a space
651 func (v *View) InsertSpace(usePlugin bool) bool {
652 if usePlugin && !PreActionCall("InsertSpace", v) {
656 if v.Cursor.HasSelection() {
657 v.Cursor.DeleteSelection()
658 v.Cursor.ResetSelection()
660 v.Buf.Insert(v.Cursor.Loc, " ")
664 return PostActionCall("InsertSpace", v)
669 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
670 func (v *View) InsertNewline(usePlugin bool) bool {
671 if usePlugin && !PreActionCall("InsertNewline", v) {
676 if v.Cursor.HasSelection() {
677 v.Cursor.DeleteSelection()
678 v.Cursor.ResetSelection()
681 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
682 v.Buf.Insert(v.Cursor.Loc, "\n")
685 if v.Buf.Settings["autoindent"].(bool) {
686 v.Buf.Insert(v.Cursor.Loc, ws)
687 // for i := 0; i < len(ws); i++ {
691 // Remove the whitespaces if keepautoindent setting is off
692 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
693 line := v.Buf.Line(v.Cursor.Y - 1)
694 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
697 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
700 return PostActionCall("InsertNewline", v)
705 // Backspace deletes the previous character
706 func (v *View) Backspace(usePlugin bool) bool {
707 if usePlugin && !PreActionCall("Backspace", v) {
711 // Delete a character
712 if v.Cursor.HasSelection() {
713 v.Cursor.DeleteSelection()
714 v.Cursor.ResetSelection()
715 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
716 // We have to do something a bit hacky here because we want to
717 // delete the line by first moving left and then deleting backwards
718 // but the undo redo would place the cursor in the wrong place
719 // So instead we move left, save the position, move back, delete
720 // and restore the position
722 // If the user is using spaces instead of tabs and they are deleting
723 // whitespace at the start of the line, we should delete as if it's a
724 // tab (tabSize number of spaces)
725 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
726 tabSize := int(v.Buf.Settings["tabsize"].(float64))
727 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
729 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
732 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
735 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
738 return PostActionCall("Backspace", v)
743 // DeleteWordRight deletes the word to the right of the cursor
744 func (v *View) DeleteWordRight(usePlugin bool) bool {
745 if usePlugin && !PreActionCall("DeleteWordRight", v) {
749 v.SelectWordRight(false)
750 if v.Cursor.HasSelection() {
751 v.Cursor.DeleteSelection()
752 v.Cursor.ResetSelection()
756 return PostActionCall("DeleteWordRight", v)
761 // DeleteWordLeft deletes the word to the left of the cursor
762 func (v *View) DeleteWordLeft(usePlugin bool) bool {
763 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
767 v.SelectWordLeft(false)
768 if v.Cursor.HasSelection() {
769 v.Cursor.DeleteSelection()
770 v.Cursor.ResetSelection()
774 return PostActionCall("DeleteWordLeft", v)
779 // Delete deletes the next character
780 func (v *View) Delete(usePlugin bool) bool {
781 if usePlugin && !PreActionCall("Delete", v) {
785 if v.Cursor.HasSelection() {
786 v.Cursor.DeleteSelection()
787 v.Cursor.ResetSelection()
790 if loc.LessThan(v.Buf.End()) {
791 v.Buf.Remove(loc, loc.Move(1, v.Buf))
796 return PostActionCall("Delete", v)
801 // IndentSelection indents the current selection
802 func (v *View) IndentSelection(usePlugin bool) bool {
803 if usePlugin && !PreActionCall("IndentSelection", v) {
807 if v.Cursor.HasSelection() {
808 start := v.Cursor.CurSelection[0]
809 end := v.Cursor.CurSelection[1]
811 start, end = end, start
815 endY := end.Move(-1, v.Buf).Y
816 endX := end.Move(-1, v.Buf).X
817 for y := startY; y <= endY; y++ {
818 tabsize := len(v.Buf.IndentString())
819 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
820 if y == startY && start.X > 0 {
821 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
824 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
830 return PostActionCall("IndentSelection", v)
837 // OutdentLine moves the current line back one indentation
838 func (v *View) OutdentLine(usePlugin bool) bool {
839 if usePlugin && !PreActionCall("OutdentLine", v) {
843 if v.Cursor.HasSelection() {
847 for x := 0; x < len(v.Buf.IndentString()); x++ {
848 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
851 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
856 return PostActionCall("OutdentLine", v)
861 // OutdentSelection takes the current selection and moves it back one indent level
862 func (v *View) OutdentSelection(usePlugin bool) bool {
863 if usePlugin && !PreActionCall("OutdentSelection", v) {
867 if v.Cursor.HasSelection() {
868 start := v.Cursor.CurSelection[0]
869 end := v.Cursor.CurSelection[1]
871 start, end = end, start
875 endY := end.Move(-1, v.Buf).Y
876 for y := startY; y <= endY; y++ {
877 for x := 0; x < len(v.Buf.IndentString()); x++ {
878 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
881 v.Buf.Remove(Loc{0, y}, Loc{1, y})
887 return PostActionCall("OutdentSelection", v)
894 // InsertTab inserts a tab or spaces
895 func (v *View) InsertTab(usePlugin bool) bool {
896 if usePlugin && !PreActionCall("InsertTab", v) {
900 if v.Cursor.HasSelection() {
904 tabBytes := len(v.Buf.IndentString())
905 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
906 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
907 // for i := 0; i < bytesUntilIndent; i++ {
912 return PostActionCall("InsertTab", v)
917 // SaveAll saves all open buffers
918 func (v *View) SaveAll(usePlugin bool) bool {
920 if usePlugin && !PreActionCall("SaveAll", v) {
924 for _, t := range tabs {
925 for _, v := range t.views {
931 return PostActionCall("SaveAll", v)
937 // Save the buffer to disk
938 func (v *View) Save(usePlugin bool) bool {
940 if usePlugin && !PreActionCall("Save", v) {
944 if v.Type.Scratch == true {
945 // We can't save any view type with scratch set. eg help and log text
948 // If this is an empty buffer, ask for a filename
949 if v.Buf.Path == "" {
952 v.saveToFile(v.Buf.Path)
956 return PostActionCall("Save", v)
962 // This function saves the buffer to `filename` and changes the buffer's path and name
963 // to `filename` if the save is successful
964 func (v *View) saveToFile(filename string) {
965 err := v.Buf.SaveAs(filename)
967 if strings.HasSuffix(err.Error(), "permission denied") {
968 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
970 err = v.Buf.SaveAsWithSudo(filename)
972 messenger.Error(err.Error())
974 v.Buf.Path = filename
975 v.Buf.name = filename
976 messenger.Message("Saved " + filename)
982 messenger.Error(err.Error())
985 v.Buf.Path = filename
986 v.Buf.name = filename
987 messenger.Message("Saved " + filename)
991 // SaveAs saves the buffer to disk with the given name
992 func (v *View) SaveAs(usePlugin bool) bool {
994 if usePlugin && !PreActionCall("Find", v) {
998 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
1000 // the filename might or might not be quoted, so unquote first then join the strings.
1001 args, err := shellwords.Split(filename)
1002 filename = strings.Join(args, " ")
1004 messenger.Error("Error parsing arguments: ", err)
1007 v.saveToFile(filename)
1011 PostActionCall("Find", v)
1017 // Find opens a prompt and searches forward for the input
1018 func (v *View) Find(usePlugin bool) bool {
1020 if usePlugin && !PreActionCall("Find", v) {
1025 if v.Cursor.HasSelection() {
1026 searchStart = v.Cursor.CurSelection[1]
1027 searchStart = v.Cursor.CurSelection[1]
1028 searchStr = v.Cursor.GetSelection()
1030 searchStart = v.Cursor.Loc
1032 BeginSearch(searchStr)
1035 return PostActionCall("Find", v)
1041 // FindNext searches forwards for the last used search term
1042 func (v *View) FindNext(usePlugin bool) bool {
1043 if usePlugin && !PreActionCall("FindNext", v) {
1047 if v.Cursor.HasSelection() {
1048 searchStart = v.Cursor.CurSelection[1]
1049 // lastSearch = v.Cursor.GetSelection()
1051 searchStart = v.Cursor.Loc
1053 if lastSearch == "" {
1056 messenger.Message("Finding: " + lastSearch)
1057 Search(lastSearch, v, true)
1060 return PostActionCall("FindNext", v)
1065 // FindPrevious searches backwards for the last used search term
1066 func (v *View) FindPrevious(usePlugin bool) bool {
1067 if usePlugin && !PreActionCall("FindPrevious", v) {
1071 if v.Cursor.HasSelection() {
1072 searchStart = v.Cursor.CurSelection[0]
1074 searchStart = v.Cursor.Loc
1076 messenger.Message("Finding: " + lastSearch)
1077 Search(lastSearch, v, false)
1080 return PostActionCall("FindPrevious", v)
1085 // Undo undoes the last action
1086 func (v *View) Undo(usePlugin bool) bool {
1087 if usePlugin && !PreActionCall("Undo", v) {
1091 if v.Buf.curCursor == 0 {
1092 v.Buf.clearCursors()
1096 messenger.Message("Undid action")
1099 return PostActionCall("Undo", v)
1104 // Redo redoes the last action
1105 func (v *View) Redo(usePlugin bool) bool {
1106 if usePlugin && !PreActionCall("Redo", v) {
1110 if v.Buf.curCursor == 0 {
1111 v.Buf.clearCursors()
1115 messenger.Message("Redid action")
1118 return PostActionCall("Redo", v)
1123 // Copy the selection to the system clipboard
1124 func (v *View) Copy(usePlugin bool) bool {
1126 if usePlugin && !PreActionCall("Copy", v) {
1130 if v.Cursor.HasSelection() {
1131 v.Cursor.CopySelection("clipboard")
1133 messenger.Message("Copied selection")
1137 return PostActionCall("Copy", v)
1143 // CutLine cuts the current line to the clipboard
1144 func (v *View) CutLine(usePlugin bool) bool {
1145 if usePlugin && !PreActionCall("CutLine", v) {
1149 v.Cursor.SelectLine()
1150 if !v.Cursor.HasSelection() {
1153 if v.freshClip == true {
1154 if v.Cursor.HasSelection() {
1155 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1156 messenger.Error(err)
1158 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1161 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1165 v.lastCutTime = time.Now()
1166 v.Cursor.DeleteSelection()
1167 v.Cursor.ResetSelection()
1168 messenger.Message("Cut line")
1171 return PostActionCall("CutLine", v)
1176 // Cut the selection to the system clipboard
1177 func (v *View) Cut(usePlugin bool) bool {
1178 if usePlugin && !PreActionCall("Cut", v) {
1182 if v.Cursor.HasSelection() {
1183 v.Cursor.CopySelection("clipboard")
1184 v.Cursor.DeleteSelection()
1185 v.Cursor.ResetSelection()
1187 messenger.Message("Cut selection")
1190 return PostActionCall("Cut", v)
1198 // DuplicateLine duplicates the current line or selection
1199 func (v *View) DuplicateLine(usePlugin bool) bool {
1200 if usePlugin && !PreActionCall("DuplicateLine", v) {
1204 if v.Cursor.HasSelection() {
1205 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1208 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1212 messenger.Message("Duplicated line")
1215 return PostActionCall("DuplicateLine", v)
1220 // DeleteLine deletes the current line
1221 func (v *View) DeleteLine(usePlugin bool) bool {
1222 if usePlugin && !PreActionCall("DeleteLine", v) {
1226 v.Cursor.SelectLine()
1227 if !v.Cursor.HasSelection() {
1230 v.Cursor.DeleteSelection()
1231 v.Cursor.ResetSelection()
1232 messenger.Message("Deleted line")
1235 return PostActionCall("DeleteLine", v)
1240 // MoveLinesUp moves up the current line or selected lines if any
1241 func (v *View) MoveLinesUp(usePlugin bool) bool {
1242 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1246 if v.Cursor.HasSelection() {
1247 if v.Cursor.CurSelection[0].Y == 0 {
1248 messenger.Message("Can not move further up")
1251 start := v.Cursor.CurSelection[0].Y
1252 end := v.Cursor.CurSelection[1].Y
1254 end, start = start, end
1261 v.Cursor.CurSelection[1].Y -= 1
1262 messenger.Message("Moved up selected line(s)")
1264 if v.Cursor.Loc.Y == 0 {
1265 messenger.Message("Can not move further up")
1272 messenger.Message("Moved up current line")
1274 v.Buf.IsModified = true
1277 return PostActionCall("MoveLinesUp", v)
1282 // MoveLinesDown moves down the current line or selected lines if any
1283 func (v *View) MoveLinesDown(usePlugin bool) bool {
1284 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1288 if v.Cursor.HasSelection() {
1289 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1290 messenger.Message("Can not move further down")
1293 start := v.Cursor.CurSelection[0].Y
1294 end := v.Cursor.CurSelection[1].Y
1296 end, start = start, end
1299 v.Buf.MoveLinesDown(
1303 messenger.Message("Moved down selected line(s)")
1305 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1306 messenger.Message("Can not move further down")
1309 v.Buf.MoveLinesDown(
1313 messenger.Message("Moved down current line")
1315 v.Buf.IsModified = true
1318 return PostActionCall("MoveLinesDown", v)
1323 // Paste whatever is in the system clipboard into the buffer
1324 // Delete and paste if the user has a selection
1325 func (v *View) Paste(usePlugin bool) bool {
1326 if usePlugin && !PreActionCall("Paste", v) {
1330 clip, _ := clipboard.ReadAll("clipboard")
1334 return PostActionCall("Paste", v)
1339 // PastePrimary pastes from the primary clipboard (only use on linux)
1340 func (v *View) PastePrimary(usePlugin bool) bool {
1341 if usePlugin && !PreActionCall("Paste", v) {
1345 clip, _ := clipboard.ReadAll("primary")
1349 return PostActionCall("Paste", v)
1354 // SelectAll selects the entire buffer
1355 func (v *View) SelectAll(usePlugin bool) bool {
1356 if usePlugin && !PreActionCall("SelectAll", v) {
1360 v.Cursor.SetSelectionStart(v.Buf.Start())
1361 v.Cursor.SetSelectionEnd(v.Buf.End())
1362 // Put the cursor at the beginning
1367 return PostActionCall("SelectAll", v)
1372 // OpenFile opens a new file in the buffer
1373 func (v *View) OpenFile(usePlugin bool) bool {
1375 if usePlugin && !PreActionCall("OpenFile", v) {
1380 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1382 HandleCommand(input)
1384 return PostActionCall("OpenFile", v)
1392 // Start moves the viewport to the start of the buffer
1393 func (v *View) Start(usePlugin bool) bool {
1395 if usePlugin && !PreActionCall("Start", v) {
1402 return PostActionCall("Start", v)
1408 // End moves the viewport to the end of the buffer
1409 func (v *View) End(usePlugin bool) bool {
1411 if usePlugin && !PreActionCall("End", v) {
1415 if v.Height > v.Buf.NumLines {
1418 v.Topline = v.Buf.NumLines - v.Height
1422 return PostActionCall("End", v)
1428 // PageUp scrolls the view up a page
1429 func (v *View) PageUp(usePlugin bool) bool {
1431 if usePlugin && !PreActionCall("PageUp", v) {
1435 if v.Topline > v.Height {
1436 v.ScrollUp(v.Height)
1442 return PostActionCall("PageUp", v)
1448 // PageDown scrolls the view down a page
1449 func (v *View) PageDown(usePlugin bool) bool {
1451 if usePlugin && !PreActionCall("PageDown", v) {
1455 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1456 v.ScrollDown(v.Height)
1457 } else if v.Buf.NumLines >= v.Height {
1458 v.Topline = v.Buf.NumLines - v.Height
1462 return PostActionCall("PageDown", v)
1468 // CursorPageUp places the cursor a page up
1469 func (v *View) CursorPageUp(usePlugin bool) bool {
1470 if usePlugin && !PreActionCall("CursorPageUp", v) {
1476 if v.Cursor.HasSelection() {
1477 v.Cursor.Loc = v.Cursor.CurSelection[0]
1478 v.Cursor.ResetSelection()
1479 v.Cursor.StoreVisualX()
1481 v.Cursor.UpN(v.Height)
1484 return PostActionCall("CursorPageUp", v)
1489 // CursorPageDown places the cursor a page up
1490 func (v *View) CursorPageDown(usePlugin bool) bool {
1491 if usePlugin && !PreActionCall("CursorPageDown", v) {
1497 if v.Cursor.HasSelection() {
1498 v.Cursor.Loc = v.Cursor.CurSelection[1]
1499 v.Cursor.ResetSelection()
1500 v.Cursor.StoreVisualX()
1502 v.Cursor.DownN(v.Height)
1505 return PostActionCall("CursorPageDown", v)
1510 // HalfPageUp scrolls the view up half a page
1511 func (v *View) HalfPageUp(usePlugin bool) bool {
1513 if usePlugin && !PreActionCall("HalfPageUp", v) {
1517 if v.Topline > v.Height/2 {
1518 v.ScrollUp(v.Height / 2)
1524 return PostActionCall("HalfPageUp", v)
1530 // HalfPageDown scrolls the view down half a page
1531 func (v *View) HalfPageDown(usePlugin bool) bool {
1533 if usePlugin && !PreActionCall("HalfPageDown", v) {
1537 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1538 v.ScrollDown(v.Height / 2)
1540 if v.Buf.NumLines >= v.Height {
1541 v.Topline = v.Buf.NumLines - v.Height
1546 return PostActionCall("HalfPageDown", v)
1552 // ToggleRuler turns line numbers off and on
1553 func (v *View) ToggleRuler(usePlugin bool) bool {
1555 if usePlugin && !PreActionCall("ToggleRuler", v) {
1559 if v.Buf.Settings["ruler"] == false {
1560 v.Buf.Settings["ruler"] = true
1561 messenger.Message("Enabled ruler")
1563 v.Buf.Settings["ruler"] = false
1564 messenger.Message("Disabled ruler")
1568 return PostActionCall("ToggleRuler", v)
1574 // JumpLine jumps to a line and moves the view accordingly.
1575 func (v *View) JumpLine(usePlugin bool) bool {
1576 if usePlugin && !PreActionCall("JumpLine", v) {
1580 // Prompt for line number
1581 message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
1582 linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
1586 lineint, err := strconv.Atoi(linestring)
1587 lineint = lineint - 1 // fix offset
1589 messenger.Error(err) // return errors
1592 // Move cursor and view if possible.
1593 if lineint < v.Buf.NumLines && lineint >= 0 {
1595 v.Cursor.Y = lineint
1598 return PostActionCall("JumpLine", v)
1602 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1606 // ClearStatus clears the messenger bar
1607 func (v *View) ClearStatus(usePlugin bool) bool {
1609 if usePlugin && !PreActionCall("ClearStatus", v) {
1613 messenger.Message("")
1616 return PostActionCall("ClearStatus", v)
1622 // ToggleHelp toggles the help screen
1623 func (v *View) ToggleHelp(usePlugin bool) bool {
1625 if usePlugin && !PreActionCall("ToggleHelp", v) {
1629 if v.Type != vtHelp {
1630 // Open the default help
1637 return PostActionCall("ToggleHelp", v)
1643 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1644 func (v *View) ToggleKeyMenu(usePlugin bool) bool {
1646 if usePlugin && !PreActionCall("ToggleBindings", v) {
1650 globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
1651 for _, tab := range tabs {
1656 return PostActionCall("ToggleBindings", v)
1662 // ShellMode opens a terminal to run a shell command
1663 func (v *View) ShellMode(usePlugin bool) bool {
1665 if usePlugin && !PreActionCall("ShellMode", v) {
1669 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1671 // The true here is for openTerm to make the command interactive
1672 HandleShellCommand(input, true, true)
1674 return PostActionCall("ShellMode", v)
1681 // CommandMode lets the user enter a command
1682 func (v *View) CommandMode(usePlugin bool) bool {
1684 if usePlugin && !PreActionCall("CommandMode", v) {
1688 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1690 HandleCommand(input)
1692 return PostActionCall("CommandMode", v)
1700 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1701 func (v *View) ToggleOverwriteMode(usePlugin bool) bool {
1703 if usePlugin && !PreActionCall("ToggleOverwriteMode", v) {
1707 v.isOverwriteMode = !v.isOverwriteMode
1710 return PostActionCall("ToggleOverwriteMode", v)
1716 // Escape leaves current mode
1717 func (v *View) Escape(usePlugin bool) bool {
1719 // check if user is searching, or the last search is still active
1720 if searching || lastSearch != "" {
1724 // check if a prompt is shown, hide it and don't quit
1725 if messenger.hasPrompt {
1726 messenger.Reset() // FIXME
1734 // Quit this will close the current tab or view that is open
1735 func (v *View) Quit(usePlugin bool) bool {
1737 if usePlugin && !PreActionCall("Quit", v) {
1741 // Make sure not to quit if there are unsaved changes
1744 if len(tabs[curTab].views) > 1 {
1745 v.splitNode.Delete()
1746 tabs[v.TabNum].Cleanup()
1747 tabs[v.TabNum].Resize()
1748 } else if len(tabs) > 1 {
1749 if len(tabs[v.TabNum].views) == 1 {
1750 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1751 for i, t := range tabs {
1754 if curTab >= len(tabs) {
1758 CurView().ToggleTabbar()
1763 PostActionCall("Quit", v)
1767 messenger.SaveHistory()
1773 return PostActionCall("Quit", v)
1779 // QuitAll quits the whole editor; all splits and tabs
1780 func (v *View) QuitAll(usePlugin bool) bool {
1782 if usePlugin && !PreActionCall("QuitAll", v) {
1787 for _, tab := range tabs {
1788 for _, v := range tab.views {
1796 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1797 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1800 for _, tab := range tabs {
1801 for _, v := range tab.views {
1807 PostActionCall("QuitAll", v)
1811 messenger.SaveHistory()
1820 // AddTab adds a new tab with an empty buffer
1821 func (v *View) AddTab(usePlugin bool) bool {
1823 if usePlugin && !PreActionCall("AddTab", v) {
1827 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1828 tab.SetNum(len(tabs))
1829 tabs = append(tabs, tab)
1830 curTab = len(tabs) - 1
1832 for _, t := range tabs {
1833 for _, v := range t.views {
1840 return PostActionCall("AddTab", v)
1846 // PreviousTab switches to the previous tab in the tab list
1847 func (v *View) PreviousTab(usePlugin bool) bool {
1849 if usePlugin && !PreActionCall("PreviousTab", v) {
1855 } else if curTab == 0 {
1856 curTab = len(tabs) - 1
1860 return PostActionCall("PreviousTab", v)
1866 // NextTab switches to the next tab in the tab list
1867 func (v *View) NextTab(usePlugin bool) bool {
1869 if usePlugin && !PreActionCall("NextTab", v) {
1873 if curTab < len(tabs)-1 {
1875 } else if curTab == len(tabs)-1 {
1880 return PostActionCall("NextTab", v)
1886 // VSplitBinding opens an empty vertical split
1887 func (v *View) VSplitBinding(usePlugin bool) bool {
1889 if usePlugin && !PreActionCall("VSplit", v) {
1893 v.VSplit(NewBufferFromString("", ""))
1896 return PostActionCall("VSplit", v)
1902 // HSplitBinding opens an empty horizontal split
1903 func (v *View) HSplitBinding(usePlugin bool) bool {
1905 if usePlugin && !PreActionCall("HSplit", v) {
1909 v.HSplit(NewBufferFromString("", ""))
1912 return PostActionCall("HSplit", v)
1918 // Unsplit closes all splits in the current tab except the active one
1919 func (v *View) Unsplit(usePlugin bool) bool {
1921 if usePlugin && !PreActionCall("Unsplit", v) {
1925 curView := tabs[curTab].CurView
1926 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1927 view := tabs[curTab].views[i]
1928 if view != nil && view.Num != curView {
1930 // messenger.Message("Quit ", view.Buf.Path)
1935 return PostActionCall("Unsplit", v)
1941 // NextSplit changes the view to the next split
1942 func (v *View) NextSplit(usePlugin bool) bool {
1944 if usePlugin && !PreActionCall("NextSplit", v) {
1949 if tab.CurView < len(tab.views)-1 {
1956 return PostActionCall("NextSplit", v)
1962 // PreviousSplit changes the view to the previous split
1963 func (v *View) PreviousSplit(usePlugin bool) bool {
1965 if usePlugin && !PreActionCall("PreviousSplit", v) {
1970 if tab.CurView > 0 {
1973 tab.CurView = len(tab.views) - 1
1977 return PostActionCall("PreviousSplit", v)
1983 var curMacro []interface{}
1984 var recordingMacro bool
1986 // ToggleMacro toggles recording of a macro
1987 func (v *View) ToggleMacro(usePlugin bool) bool {
1989 if usePlugin && !PreActionCall("ToggleMacro", v) {
1993 recordingMacro = !recordingMacro
1996 curMacro = []interface{}{}
1997 messenger.Message("Recording")
1999 messenger.Message("Stopped recording")
2003 return PostActionCall("ToggleMacro", v)
2009 // PlayMacro plays back the most recently recorded macro
2010 func (v *View) PlayMacro(usePlugin bool) bool {
2011 if usePlugin && !PreActionCall("PlayMacro", v) {
2015 for _, action := range curMacro {
2016 switch t := action.(type) {
2018 // Insert a character
2019 if v.Cursor.HasSelection() {
2020 v.Cursor.DeleteSelection()
2021 v.Cursor.ResetSelection()
2023 v.Buf.Insert(v.Cursor.Loc, string(t))
2026 for pl := range loadedPlugins {
2027 _, err := Call(pl+".onRune", string(t), v)
2028 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
2032 case func(*View, bool) bool:
2038 return PostActionCall("PlayMacro", v)
2043 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
2044 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
2045 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
2046 // You can only spawn a cursor from the main cursor
2047 if v.Cursor == spawner {
2048 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
2052 if !spawner.HasSelection() {
2053 spawner.SelectWord()
2059 sel := spawner.GetSelection()
2061 searchStart = spawner.CurSelection[1]
2063 Search(regexp.QuoteMeta(sel), v, true)
2065 for _, cur := range v.Buf.cursors {
2066 if c.Loc == cur.Loc {
2070 v.Buf.cursors = append(v.Buf.cursors, c)
2071 v.Buf.UpdateCursors()
2077 PostActionCall("SpawnMultiCursor", v)
2084 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
2085 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
2086 if v.Cursor == &v.Buf.Cursor {
2087 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
2090 x, y := e.Position()
2091 x -= v.lineNumOffset - v.leftCol + v.x
2092 y += v.Topline - v.y
2098 v.MoveToMouseClick(x, y)
2100 v.Cursor = &v.Buf.Cursor
2102 v.Buf.cursors = append(v.Buf.cursors, c)
2103 v.Buf.MergeCursors()
2104 v.Buf.UpdateCursors()
2107 PostActionCall("SpawnMultiCursorAtMouse", v)
2113 // SkipMultiCursor moves the current multiple cursor to the next available position
2114 func (v *View) SkipMultiCursor(usePlugin bool) bool {
2115 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
2118 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
2121 sel := cursor.GetSelection()
2123 searchStart = cursor.CurSelection[1]
2125 Search(regexp.QuoteMeta(sel), v, true)
2130 PostActionCall("SkipMultiCursor", v)
2136 // RemoveMultiCursor removes the latest multiple cursor
2137 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
2138 end := len(v.Buf.cursors)
2141 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
2145 v.Buf.cursors[end-1] = nil
2146 v.Buf.cursors = v.Buf.cursors[:end-1]
2147 v.Buf.UpdateCursors()
2151 return PostActionCall("RemoveMultiCursor", v)
2156 v.RemoveAllMultiCursors(usePlugin)
2161 // RemoveAllMultiCursors removes all cursors except the base cursor
2162 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
2164 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
2168 v.Buf.clearCursors()
2172 return PostActionCall("RemoveAllMultiCursors", v)