11 "github.com/yuin/gopher-lua"
12 "github.com/zyedidia/clipboard"
13 "github.com/zyedidia/tcell"
16 // PreActionCall executes the lua pre callback if possible
17 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
19 for pl := range loadedPlugins {
20 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
21 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
25 if ret == lua.LFalse {
32 // PostActionCall executes the lua plugin callback if possible
33 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
35 for pl := range loadedPlugins {
36 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
37 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
41 if ret == lua.LFalse {
48 func (v *View) deselect(index int) bool {
49 if v.Cursor.HasSelection() {
50 v.Cursor.Loc = v.Cursor.CurSelection[index]
51 v.Cursor.ResetSelection()
52 v.Cursor.StoreVisualX()
58 // MousePress is the event that should happen when a normal click happens
59 // This is almost always bound to left click
60 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
61 if usePlugin && !PreActionCall("MousePress", v, e) {
66 x -= v.lineNumOffset - v.leftCol + v.x
69 // This is usually bound to left click
70 v.MoveToMouseClick(x, y)
72 if len(v.Buf.cursors) > 1 {
73 for i := 1; i < len(v.Buf.cursors); i++ {
74 v.Buf.cursors[i] = nil
76 v.Buf.cursors = v.Buf.cursors[:1]
78 v.Cursor.ResetSelection()
81 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold && (x == v.lastLoc.X && y == v.lastLoc.Y) {
84 v.lastClickTime = time.Now()
90 v.Cursor.CopySelection("primary")
93 v.lastClickTime = time.Now()
99 v.Cursor.CopySelection("primary")
102 v.doubleClick = false
103 v.tripleClick = false
104 v.lastClickTime = time.Now()
106 v.Cursor.OrigSelection[0] = v.Cursor.Loc
107 v.Cursor.CurSelection[0] = v.Cursor.Loc
108 v.Cursor.CurSelection[1] = v.Cursor.Loc
110 v.mouseReleased = false
111 } else if !v.mouseReleased {
113 v.Cursor.AddLineToSelection()
114 } else if v.doubleClick {
115 v.Cursor.AddWordToSelection()
117 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
118 v.Cursor.CopySelection("primary")
122 v.lastLoc = Loc{x, y}
125 PostActionCall("MousePress", v, e)
130 // ScrollUpAction scrolls the view up
131 func (v *View) ScrollUpAction(usePlugin bool) bool {
133 if usePlugin && !PreActionCall("ScrollUp", v) {
137 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
138 v.ScrollUp(scrollspeed)
141 PostActionCall("ScrollUp", v)
147 // ScrollDownAction scrolls the view up
148 func (v *View) ScrollDownAction(usePlugin bool) bool {
150 if usePlugin && !PreActionCall("ScrollDown", v) {
154 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
155 v.ScrollDown(scrollspeed)
158 PostActionCall("ScrollDown", v)
164 // Center centers the view on the cursor
165 func (v *View) Center(usePlugin bool) bool {
166 if usePlugin && !PreActionCall("Center", v) {
170 v.Topline = v.Cursor.Y - v.Height/2
171 if v.Topline+v.Height > v.Buf.NumLines {
172 v.Topline = v.Buf.NumLines - v.Height
179 return PostActionCall("Center", v)
184 // CursorUp moves the cursor up
185 func (v *View) CursorUp(usePlugin bool) bool {
186 if usePlugin && !PreActionCall("CursorUp", v) {
194 return PostActionCall("CursorUp", v)
199 // CursorDown moves the cursor down
200 func (v *View) CursorDown(usePlugin bool) bool {
201 if usePlugin && !PreActionCall("CursorDown", v) {
209 return PostActionCall("CursorDown", v)
214 // CursorLeft moves the cursor left
215 func (v *View) CursorLeft(usePlugin bool) bool {
216 if usePlugin && !PreActionCall("CursorLeft", v) {
220 if v.Cursor.HasSelection() {
221 v.Cursor.Loc = v.Cursor.CurSelection[0]
222 v.Cursor.ResetSelection()
223 v.Cursor.StoreVisualX()
225 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
226 tabmovement := v.Buf.Settings["tabmovement"].(bool)
227 if tabstospaces && tabmovement {
228 tabsize := int(v.Buf.Settings["tabsize"].(float64))
229 line := v.Buf.Line(v.Cursor.Y)
230 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
231 for i := 0; i < tabsize; i++ {
243 return PostActionCall("CursorLeft", v)
248 // CursorRight moves the cursor right
249 func (v *View) CursorRight(usePlugin bool) bool {
250 if usePlugin && !PreActionCall("CursorRight", v) {
254 if v.Cursor.HasSelection() {
255 v.Cursor.Loc = v.Cursor.CurSelection[1]
256 v.Cursor.ResetSelection()
257 v.Cursor.StoreVisualX()
259 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
260 tabmovement := v.Buf.Settings["tabmovement"].(bool)
261 if tabstospaces && tabmovement {
262 tabsize := int(v.Buf.Settings["tabsize"].(float64))
263 line := v.Buf.Line(v.Cursor.Y)
264 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
265 for i := 0; i < tabsize; i++ {
277 return PostActionCall("CursorRight", v)
282 // WordRight moves the cursor one word to the right
283 func (v *View) WordRight(usePlugin bool) bool {
284 if usePlugin && !PreActionCall("WordRight", v) {
291 return PostActionCall("WordRight", v)
296 // WordLeft moves the cursor one word to the left
297 func (v *View) WordLeft(usePlugin bool) bool {
298 if usePlugin && !PreActionCall("WordLeft", v) {
305 return PostActionCall("WordLeft", v)
310 // SelectUp selects up one line
311 func (v *View) SelectUp(usePlugin bool) bool {
312 if usePlugin && !PreActionCall("SelectUp", v) {
316 if !v.Cursor.HasSelection() {
317 v.Cursor.OrigSelection[0] = v.Cursor.Loc
320 v.Cursor.SelectTo(v.Cursor.Loc)
323 return PostActionCall("SelectUp", v)
328 // SelectDown selects down one line
329 func (v *View) SelectDown(usePlugin bool) bool {
330 if usePlugin && !PreActionCall("SelectDown", v) {
334 if !v.Cursor.HasSelection() {
335 v.Cursor.OrigSelection[0] = v.Cursor.Loc
338 v.Cursor.SelectTo(v.Cursor.Loc)
341 return PostActionCall("SelectDown", v)
346 // SelectLeft selects the character to the left of the cursor
347 func (v *View) SelectLeft(usePlugin bool) bool {
348 if usePlugin && !PreActionCall("SelectLeft", v) {
354 if loc.GreaterThan(count) {
357 if !v.Cursor.HasSelection() {
358 v.Cursor.OrigSelection[0] = loc
361 v.Cursor.SelectTo(v.Cursor.Loc)
364 return PostActionCall("SelectLeft", v)
369 // SelectRight selects the character to the right of the cursor
370 func (v *View) SelectRight(usePlugin bool) bool {
371 if usePlugin && !PreActionCall("SelectRight", v) {
377 if loc.GreaterThan(count) {
380 if !v.Cursor.HasSelection() {
381 v.Cursor.OrigSelection[0] = loc
384 v.Cursor.SelectTo(v.Cursor.Loc)
387 return PostActionCall("SelectRight", v)
392 // SelectWordRight selects the word to the right of the cursor
393 func (v *View) SelectWordRight(usePlugin bool) bool {
394 if usePlugin && !PreActionCall("SelectWordRight", v) {
398 if !v.Cursor.HasSelection() {
399 v.Cursor.OrigSelection[0] = v.Cursor.Loc
402 v.Cursor.SelectTo(v.Cursor.Loc)
405 return PostActionCall("SelectWordRight", v)
410 // SelectWordLeft selects the word to the left of the cursor
411 func (v *View) SelectWordLeft(usePlugin bool) bool {
412 if usePlugin && !PreActionCall("SelectWordLeft", v) {
416 if !v.Cursor.HasSelection() {
417 v.Cursor.OrigSelection[0] = v.Cursor.Loc
420 v.Cursor.SelectTo(v.Cursor.Loc)
423 return PostActionCall("SelectWordLeft", v)
428 // StartOfLine moves the cursor to the start of the line
429 func (v *View) StartOfLine(usePlugin bool) bool {
430 if usePlugin && !PreActionCall("StartOfLine", v) {
439 return PostActionCall("StartOfLine", v)
444 // EndOfLine moves the cursor to the end of the line
445 func (v *View) EndOfLine(usePlugin bool) bool {
446 if usePlugin && !PreActionCall("EndOfLine", v) {
455 return PostActionCall("EndOfLine", v)
460 // SelectToStartOfLine selects to the start of the current line
461 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
462 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
466 if !v.Cursor.HasSelection() {
467 v.Cursor.OrigSelection[0] = v.Cursor.Loc
470 v.Cursor.SelectTo(v.Cursor.Loc)
473 return PostActionCall("SelectToStartOfLine", v)
478 // SelectToEndOfLine selects to the end of the current line
479 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
480 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
484 if !v.Cursor.HasSelection() {
485 v.Cursor.OrigSelection[0] = v.Cursor.Loc
488 v.Cursor.SelectTo(v.Cursor.Loc)
491 return PostActionCall("SelectToEndOfLine", v)
496 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
497 func (v *View) ParagraphPrevious(usePlugin bool) bool {
498 if usePlugin && !PreActionCall("ParagraphPrevious", v) {
502 for line = v.Cursor.Y; line > 0; line-- {
503 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
509 // If no empty line found. move cursor to end of buffer
511 v.Cursor.Loc = v.Buf.Start()
515 return PostActionCall("ParagraphPrevious", v)
520 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
521 func (v *View) ParagraphNext(usePlugin bool) bool {
522 if usePlugin && !PreActionCall("ParagraphNext", v) {
527 for line = v.Cursor.Y; line < len(v.Buf.lines); line++ {
528 if len(v.Buf.lines[line].data) == 0 && line != v.Cursor.Y {
534 // If no empty line found. move cursor to end of buffer
535 if line == len(v.Buf.lines) {
536 v.Cursor.Loc = v.Buf.End()
540 return PostActionCall("ParagraphNext", v)
545 // CursorStart moves the cursor to the start of the buffer
546 func (v *View) CursorStart(usePlugin bool) bool {
547 if usePlugin && !PreActionCall("CursorStart", v) {
557 return PostActionCall("CursorStart", v)
562 // CursorEnd moves the cursor to the end of the buffer
563 func (v *View) CursorEnd(usePlugin bool) bool {
564 if usePlugin && !PreActionCall("CursorEnd", v) {
570 v.Cursor.Loc = v.Buf.End()
571 v.Cursor.StoreVisualX()
574 return PostActionCall("CursorEnd", v)
579 // SelectToStart selects the text from the cursor to the start of the buffer
580 func (v *View) SelectToStart(usePlugin bool) bool {
581 if usePlugin && !PreActionCall("SelectToStart", v) {
585 if !v.Cursor.HasSelection() {
586 v.Cursor.OrigSelection[0] = v.Cursor.Loc
589 v.Cursor.SelectTo(v.Buf.Start())
592 return PostActionCall("SelectToStart", v)
597 // SelectToEnd selects the text from the cursor to the end of the buffer
598 func (v *View) SelectToEnd(usePlugin bool) bool {
599 if usePlugin && !PreActionCall("SelectToEnd", v) {
603 if !v.Cursor.HasSelection() {
604 v.Cursor.OrigSelection[0] = v.Cursor.Loc
607 v.Cursor.SelectTo(v.Buf.End())
610 return PostActionCall("SelectToEnd", v)
615 // InsertSpace inserts a space
616 func (v *View) InsertSpace(usePlugin bool) bool {
617 if usePlugin && !PreActionCall("InsertSpace", v) {
621 if v.Cursor.HasSelection() {
622 v.Cursor.DeleteSelection()
623 v.Cursor.ResetSelection()
625 v.Buf.Insert(v.Cursor.Loc, " ")
629 return PostActionCall("InsertSpace", v)
634 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
635 func (v *View) InsertNewline(usePlugin bool) bool {
636 if usePlugin && !PreActionCall("InsertNewline", v) {
641 if v.Cursor.HasSelection() {
642 v.Cursor.DeleteSelection()
643 v.Cursor.ResetSelection()
646 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
647 v.Buf.Insert(v.Cursor.Loc, "\n")
650 if v.Buf.Settings["autoindent"].(bool) {
651 v.Buf.Insert(v.Cursor.Loc, ws)
652 // for i := 0; i < len(ws); i++ {
656 // Remove the whitespaces if keepautoindent setting is off
657 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
658 line := v.Buf.Line(v.Cursor.Y - 1)
659 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
662 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
665 return PostActionCall("InsertNewline", v)
670 // Backspace deletes the previous character
671 func (v *View) Backspace(usePlugin bool) bool {
672 if usePlugin && !PreActionCall("Backspace", v) {
676 // Delete a character
677 if v.Cursor.HasSelection() {
678 v.Cursor.DeleteSelection()
679 v.Cursor.ResetSelection()
680 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
681 // We have to do something a bit hacky here because we want to
682 // delete the line by first moving left and then deleting backwards
683 // but the undo redo would place the cursor in the wrong place
684 // So instead we move left, save the position, move back, delete
685 // and restore the position
687 // If the user is using spaces instead of tabs and they are deleting
688 // whitespace at the start of the line, we should delete as if it's a
689 // tab (tabSize number of spaces)
690 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
691 tabSize := int(v.Buf.Settings["tabsize"].(float64))
692 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
694 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
697 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
700 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
703 return PostActionCall("Backspace", v)
708 // DeleteWordRight deletes the word to the right of the cursor
709 func (v *View) DeleteWordRight(usePlugin bool) bool {
710 if usePlugin && !PreActionCall("DeleteWordRight", v) {
714 v.SelectWordRight(false)
715 if v.Cursor.HasSelection() {
716 v.Cursor.DeleteSelection()
717 v.Cursor.ResetSelection()
721 return PostActionCall("DeleteWordRight", v)
726 // DeleteWordLeft deletes the word to the left of the cursor
727 func (v *View) DeleteWordLeft(usePlugin bool) bool {
728 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
732 v.SelectWordLeft(false)
733 if v.Cursor.HasSelection() {
734 v.Cursor.DeleteSelection()
735 v.Cursor.ResetSelection()
739 return PostActionCall("DeleteWordLeft", v)
744 // Delete deletes the next character
745 func (v *View) Delete(usePlugin bool) bool {
746 if usePlugin && !PreActionCall("Delete", v) {
750 if v.Cursor.HasSelection() {
751 v.Cursor.DeleteSelection()
752 v.Cursor.ResetSelection()
755 if loc.LessThan(v.Buf.End()) {
756 v.Buf.Remove(loc, loc.Move(1, v.Buf))
761 return PostActionCall("Delete", v)
766 // IndentSelection indents the current selection
767 func (v *View) IndentSelection(usePlugin bool) bool {
768 if usePlugin && !PreActionCall("IndentSelection", v) {
772 if v.Cursor.HasSelection() {
773 start := v.Cursor.CurSelection[0]
774 end := v.Cursor.CurSelection[1]
776 start, end = end, start
780 endY := end.Move(-1, v.Buf).Y
781 endX := end.Move(-1, v.Buf).X
782 for y := startY; y <= endY; y++ {
783 tabsize := len(v.Buf.IndentString())
784 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
785 if y == startY && start.X > 0 {
786 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
789 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
795 return PostActionCall("IndentSelection", v)
802 // OutdentLine moves the current line back one indentation
803 func (v *View) OutdentLine(usePlugin bool) bool {
804 if usePlugin && !PreActionCall("OutdentLine", v) {
808 if v.Cursor.HasSelection() {
812 for x := 0; x < len(v.Buf.IndentString()); x++ {
813 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
816 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
821 return PostActionCall("OutdentLine", v)
826 // OutdentSelection takes the current selection and moves it back one indent level
827 func (v *View) OutdentSelection(usePlugin bool) bool {
828 if usePlugin && !PreActionCall("OutdentSelection", v) {
832 if v.Cursor.HasSelection() {
833 start := v.Cursor.CurSelection[0]
834 end := v.Cursor.CurSelection[1]
836 start, end = end, start
840 endY := end.Move(-1, v.Buf).Y
841 for y := startY; y <= endY; y++ {
842 for x := 0; x < len(v.Buf.IndentString()); x++ {
843 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
846 v.Buf.Remove(Loc{0, y}, Loc{1, y})
852 return PostActionCall("OutdentSelection", v)
859 // InsertTab inserts a tab or spaces
860 func (v *View) InsertTab(usePlugin bool) bool {
861 if usePlugin && !PreActionCall("InsertTab", v) {
865 if v.Cursor.HasSelection() {
869 tabBytes := len(v.Buf.IndentString())
870 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
871 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
872 // for i := 0; i < bytesUntilIndent; i++ {
877 return PostActionCall("InsertTab", v)
882 // SaveAll saves all open buffers
883 func (v *View) SaveAll(usePlugin bool) bool {
885 if usePlugin && !PreActionCall("SaveAll", v) {
889 for _, t := range tabs {
890 for _, v := range t.views {
896 return PostActionCall("SaveAll", v)
902 // Save the buffer to disk
903 func (v *View) Save(usePlugin bool) bool {
905 if usePlugin && !PreActionCall("Save", v) {
909 if v.Type.scratch == true {
910 // We can't save any view type with scratch set. eg help and log text
913 // If this is an empty buffer, ask for a filename
914 if v.Buf.Path == "" {
917 v.saveToFile(v.Buf.Path)
921 return PostActionCall("Save", v)
927 // This function saves the buffer to `filename` and changes the buffer's path and name
928 // to `filename` if the save is successful
929 func (v *View) saveToFile(filename string) {
930 err := v.Buf.SaveAs(filename)
932 if strings.HasSuffix(err.Error(), "permission denied") {
933 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
935 err = v.Buf.SaveAsWithSudo(filename)
937 messenger.Error(err.Error())
939 v.Buf.Path = filename
940 v.Buf.name = filename
941 messenger.Message("Saved " + filename)
947 messenger.Error(err.Error())
950 v.Buf.Path = filename
951 v.Buf.name = filename
952 messenger.Message("Saved " + filename)
956 // SaveAs saves the buffer to disk with the given name
957 func (v *View) SaveAs(usePlugin bool) bool {
959 if usePlugin && !PreActionCall("Find", v) {
963 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
965 // the filename might or might not be quoted, so unquote first then join the strings.
966 filename = strings.Join(SplitCommandArgs(filename), " ")
967 v.saveToFile(filename)
971 PostActionCall("Find", v)
977 // Find opens a prompt and searches forward for the input
978 func (v *View) Find(usePlugin bool) bool {
980 if usePlugin && !PreActionCall("Find", v) {
985 if v.Cursor.HasSelection() {
986 searchStart = v.Cursor.CurSelection[1]
987 searchStart = v.Cursor.CurSelection[1]
988 searchStr = v.Cursor.GetSelection()
990 searchStart = v.Cursor.Loc
992 BeginSearch(searchStr)
995 return PostActionCall("Find", v)
1001 // FindNext searches forwards for the last used search term
1002 func (v *View) FindNext(usePlugin bool) bool {
1003 if usePlugin && !PreActionCall("FindNext", v) {
1007 if v.Cursor.HasSelection() {
1008 searchStart = v.Cursor.CurSelection[1]
1009 // lastSearch = v.Cursor.GetSelection()
1011 searchStart = v.Cursor.Loc
1013 if lastSearch == "" {
1016 messenger.Message("Finding: " + lastSearch)
1017 Search(lastSearch, v, true)
1020 return PostActionCall("FindNext", v)
1025 // FindPrevious searches backwards for the last used search term
1026 func (v *View) FindPrevious(usePlugin bool) bool {
1027 if usePlugin && !PreActionCall("FindPrevious", v) {
1031 if v.Cursor.HasSelection() {
1032 searchStart = v.Cursor.CurSelection[0]
1034 searchStart = v.Cursor.Loc
1036 messenger.Message("Finding: " + lastSearch)
1037 Search(lastSearch, v, false)
1040 return PostActionCall("FindPrevious", v)
1045 // Undo undoes the last action
1046 func (v *View) Undo(usePlugin bool) bool {
1047 if usePlugin && !PreActionCall("Undo", v) {
1051 if v.Buf.curCursor == 0 {
1052 v.Buf.clearCursors()
1056 messenger.Message("Undid action")
1059 return PostActionCall("Undo", v)
1064 // Redo redoes the last action
1065 func (v *View) Redo(usePlugin bool) bool {
1066 if usePlugin && !PreActionCall("Redo", v) {
1070 if v.Buf.curCursor == 0 {
1071 v.Buf.clearCursors()
1075 messenger.Message("Redid action")
1078 return PostActionCall("Redo", v)
1083 // Copy the selection to the system clipboard
1084 func (v *View) Copy(usePlugin bool) bool {
1086 if usePlugin && !PreActionCall("Copy", v) {
1090 if v.Cursor.HasSelection() {
1091 v.Cursor.CopySelection("clipboard")
1093 messenger.Message("Copied selection")
1097 return PostActionCall("Copy", v)
1103 // CutLine cuts the current line to the clipboard
1104 func (v *View) CutLine(usePlugin bool) bool {
1105 if usePlugin && !PreActionCall("CutLine", v) {
1109 v.Cursor.SelectLine()
1110 if !v.Cursor.HasSelection() {
1113 if v.freshClip == true {
1114 if v.Cursor.HasSelection() {
1115 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1116 messenger.Error(err)
1118 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1121 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1125 v.lastCutTime = time.Now()
1126 v.Cursor.DeleteSelection()
1127 v.Cursor.ResetSelection()
1128 messenger.Message("Cut line")
1131 return PostActionCall("CutLine", v)
1136 // Cut the selection to the system clipboard
1137 func (v *View) Cut(usePlugin bool) bool {
1138 if usePlugin && !PreActionCall("Cut", v) {
1142 if v.Cursor.HasSelection() {
1143 v.Cursor.CopySelection("clipboard")
1144 v.Cursor.DeleteSelection()
1145 v.Cursor.ResetSelection()
1147 messenger.Message("Cut selection")
1150 return PostActionCall("Cut", v)
1158 // DuplicateLine duplicates the current line or selection
1159 func (v *View) DuplicateLine(usePlugin bool) bool {
1160 if usePlugin && !PreActionCall("DuplicateLine", v) {
1164 if v.Cursor.HasSelection() {
1165 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1168 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1172 messenger.Message("Duplicated line")
1175 return PostActionCall("DuplicateLine", v)
1180 // DeleteLine deletes the current line
1181 func (v *View) DeleteLine(usePlugin bool) bool {
1182 if usePlugin && !PreActionCall("DeleteLine", v) {
1186 v.Cursor.SelectLine()
1187 if !v.Cursor.HasSelection() {
1190 v.Cursor.DeleteSelection()
1191 v.Cursor.ResetSelection()
1192 messenger.Message("Deleted line")
1195 return PostActionCall("DeleteLine", v)
1200 // MoveLinesUp moves up the current line or selected lines if any
1201 func (v *View) MoveLinesUp(usePlugin bool) bool {
1202 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1206 if v.Cursor.HasSelection() {
1207 if v.Cursor.CurSelection[0].Y == 0 {
1208 messenger.Message("Can not move further up")
1211 start := v.Cursor.CurSelection[0].Y
1212 end := v.Cursor.CurSelection[1].Y
1214 end, start = start, end
1221 v.Cursor.CurSelection[1].Y -= 1
1222 messenger.Message("Moved up selected line(s)")
1224 if v.Cursor.Loc.Y == 0 {
1225 messenger.Message("Can not move further up")
1232 messenger.Message("Moved up current line")
1234 v.Buf.IsModified = true
1237 return PostActionCall("MoveLinesUp", v)
1242 // MoveLinesDown moves down the current line or selected lines if any
1243 func (v *View) MoveLinesDown(usePlugin bool) bool {
1244 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1248 if v.Cursor.HasSelection() {
1249 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1250 messenger.Message("Can not move further down")
1253 start := v.Cursor.CurSelection[0].Y
1254 end := v.Cursor.CurSelection[1].Y
1256 end, start = start, end
1259 v.Buf.MoveLinesDown(
1263 messenger.Message("Moved down selected line(s)")
1265 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1266 messenger.Message("Can not move further down")
1269 v.Buf.MoveLinesDown(
1273 messenger.Message("Moved down current line")
1275 v.Buf.IsModified = true
1278 return PostActionCall("MoveLinesDown", v)
1283 // Paste whatever is in the system clipboard into the buffer
1284 // Delete and paste if the user has a selection
1285 func (v *View) Paste(usePlugin bool) bool {
1286 if usePlugin && !PreActionCall("Paste", v) {
1290 clip, _ := clipboard.ReadAll("clipboard")
1294 return PostActionCall("Paste", v)
1299 // PastePrimary pastes from the primary clipboard (only use on linux)
1300 func (v *View) PastePrimary(usePlugin bool) bool {
1301 if usePlugin && !PreActionCall("Paste", v) {
1305 clip, _ := clipboard.ReadAll("primary")
1309 return PostActionCall("Paste", v)
1314 // SelectAll selects the entire buffer
1315 func (v *View) SelectAll(usePlugin bool) bool {
1316 if usePlugin && !PreActionCall("SelectAll", v) {
1320 v.Cursor.SetSelectionStart(v.Buf.Start())
1321 v.Cursor.SetSelectionEnd(v.Buf.End())
1322 // Put the cursor at the beginning
1327 return PostActionCall("SelectAll", v)
1332 // OpenFile opens a new file in the buffer
1333 func (v *View) OpenFile(usePlugin bool) bool {
1335 if usePlugin && !PreActionCall("OpenFile", v) {
1340 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1342 HandleCommand(input)
1344 return PostActionCall("OpenFile", v)
1352 // Start moves the viewport to the start of the buffer
1353 func (v *View) Start(usePlugin bool) bool {
1355 if usePlugin && !PreActionCall("Start", v) {
1362 return PostActionCall("Start", v)
1368 // End moves the viewport to the end of the buffer
1369 func (v *View) End(usePlugin bool) bool {
1371 if usePlugin && !PreActionCall("End", v) {
1375 if v.Height > v.Buf.NumLines {
1378 v.Topline = v.Buf.NumLines - v.Height
1382 return PostActionCall("End", v)
1388 // PageUp scrolls the view up a page
1389 func (v *View) PageUp(usePlugin bool) bool {
1391 if usePlugin && !PreActionCall("PageUp", v) {
1395 if v.Topline > v.Height {
1396 v.ScrollUp(v.Height)
1402 return PostActionCall("PageUp", v)
1408 // PageDown scrolls the view down a page
1409 func (v *View) PageDown(usePlugin bool) bool {
1411 if usePlugin && !PreActionCall("PageDown", v) {
1415 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1416 v.ScrollDown(v.Height)
1417 } else if v.Buf.NumLines >= v.Height {
1418 v.Topline = v.Buf.NumLines - v.Height
1422 return PostActionCall("PageDown", v)
1428 // CursorPageUp places the cursor a page up
1429 func (v *View) CursorPageUp(usePlugin bool) bool {
1430 if usePlugin && !PreActionCall("CursorPageUp", v) {
1436 if v.Cursor.HasSelection() {
1437 v.Cursor.Loc = v.Cursor.CurSelection[0]
1438 v.Cursor.ResetSelection()
1439 v.Cursor.StoreVisualX()
1441 v.Cursor.UpN(v.Height)
1444 return PostActionCall("CursorPageUp", v)
1449 // CursorPageDown places the cursor a page up
1450 func (v *View) CursorPageDown(usePlugin bool) bool {
1451 if usePlugin && !PreActionCall("CursorPageDown", v) {
1457 if v.Cursor.HasSelection() {
1458 v.Cursor.Loc = v.Cursor.CurSelection[1]
1459 v.Cursor.ResetSelection()
1460 v.Cursor.StoreVisualX()
1462 v.Cursor.DownN(v.Height)
1465 return PostActionCall("CursorPageDown", v)
1470 // HalfPageUp scrolls the view up half a page
1471 func (v *View) HalfPageUp(usePlugin bool) bool {
1473 if usePlugin && !PreActionCall("HalfPageUp", v) {
1477 if v.Topline > v.Height/2 {
1478 v.ScrollUp(v.Height / 2)
1484 return PostActionCall("HalfPageUp", v)
1490 // HalfPageDown scrolls the view down half a page
1491 func (v *View) HalfPageDown(usePlugin bool) bool {
1493 if usePlugin && !PreActionCall("HalfPageDown", v) {
1497 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1498 v.ScrollDown(v.Height / 2)
1500 if v.Buf.NumLines >= v.Height {
1501 v.Topline = v.Buf.NumLines - v.Height
1506 return PostActionCall("HalfPageDown", v)
1512 // ToggleRuler turns line numbers off and on
1513 func (v *View) ToggleRuler(usePlugin bool) bool {
1515 if usePlugin && !PreActionCall("ToggleRuler", v) {
1519 if v.Buf.Settings["ruler"] == false {
1520 v.Buf.Settings["ruler"] = true
1521 messenger.Message("Enabled ruler")
1523 v.Buf.Settings["ruler"] = false
1524 messenger.Message("Disabled ruler")
1528 return PostActionCall("ToggleRuler", v)
1534 // JumpLine jumps to a line and moves the view accordingly.
1535 func (v *View) JumpLine(usePlugin bool) bool {
1536 if usePlugin && !PreActionCall("JumpLine", v) {
1540 // Prompt for line number
1541 message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
1542 linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
1546 lineint, err := strconv.Atoi(linestring)
1547 lineint = lineint - 1 // fix offset
1549 messenger.Error(err) // return errors
1552 // Move cursor and view if possible.
1553 if lineint < v.Buf.NumLines && lineint >= 0 {
1555 v.Cursor.Y = lineint
1558 return PostActionCall("JumpLine", v)
1562 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1566 // ClearStatus clears the messenger bar
1567 func (v *View) ClearStatus(usePlugin bool) bool {
1569 if usePlugin && !PreActionCall("ClearStatus", v) {
1573 messenger.Message("")
1576 return PostActionCall("ClearStatus", v)
1582 // ToggleHelp toggles the help screen
1583 func (v *View) ToggleHelp(usePlugin bool) bool {
1585 if usePlugin && !PreActionCall("ToggleHelp", v) {
1589 if v.Type != vtHelp {
1590 // Open the default help
1597 return PostActionCall("ToggleHelp", v)
1603 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1604 func (v *View) ToggleKeyMenu(usePlugin bool) bool {
1606 if usePlugin && !PreActionCall("ToggleBindings", v) {
1610 globalSettings["keymenu"] = !globalSettings["keymenu"].(bool)
1611 for _, tab := range tabs {
1616 return PostActionCall("ToggleBindings", v)
1622 // ShellMode opens a terminal to run a shell command
1623 func (v *View) ShellMode(usePlugin bool) bool {
1625 if usePlugin && !PreActionCall("ShellMode", v) {
1629 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1631 // The true here is for openTerm to make the command interactive
1632 HandleShellCommand(input, true, true)
1634 return PostActionCall("ShellMode", v)
1641 // CommandMode lets the user enter a command
1642 func (v *View) CommandMode(usePlugin bool) bool {
1644 if usePlugin && !PreActionCall("CommandMode", v) {
1648 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1650 HandleCommand(input)
1652 return PostActionCall("CommandMode", v)
1660 // Escape leaves current mode
1661 func (v *View) Escape(usePlugin bool) bool {
1663 // check if user is searching, or the last search is still active
1664 if searching || lastSearch != "" {
1668 // check if a prompt is shown, hide it and don't quit
1669 if messenger.hasPrompt {
1670 messenger.Reset() // FIXME
1678 // Quit this will close the current tab or view that is open
1679 func (v *View) Quit(usePlugin bool) bool {
1681 if usePlugin && !PreActionCall("Quit", v) {
1685 // Make sure not to quit if there are unsaved changes
1688 if len(tabs[curTab].views) > 1 {
1689 v.splitNode.Delete()
1690 tabs[v.TabNum].Cleanup()
1691 tabs[v.TabNum].Resize()
1692 } else if len(tabs) > 1 {
1693 if len(tabs[v.TabNum].views) == 1 {
1694 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1695 for i, t := range tabs {
1698 if curTab >= len(tabs) {
1702 CurView().ToggleTabbar()
1707 PostActionCall("Quit", v)
1711 messenger.SaveHistory()
1717 return PostActionCall("Quit", v)
1723 // QuitAll quits the whole editor; all splits and tabs
1724 func (v *View) QuitAll(usePlugin bool) bool {
1726 if usePlugin && !PreActionCall("QuitAll", v) {
1731 for _, tab := range tabs {
1732 for _, v := range tab.views {
1740 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1741 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1744 for _, tab := range tabs {
1745 for _, v := range tab.views {
1751 PostActionCall("QuitAll", v)
1755 messenger.SaveHistory()
1764 // AddTab adds a new tab with an empty buffer
1765 func (v *View) AddTab(usePlugin bool) bool {
1767 if usePlugin && !PreActionCall("AddTab", v) {
1771 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1772 tab.SetNum(len(tabs))
1773 tabs = append(tabs, tab)
1774 curTab = len(tabs) - 1
1776 for _, t := range tabs {
1777 for _, v := range t.views {
1784 return PostActionCall("AddTab", v)
1790 // PreviousTab switches to the previous tab in the tab list
1791 func (v *View) PreviousTab(usePlugin bool) bool {
1793 if usePlugin && !PreActionCall("PreviousTab", v) {
1799 } else if curTab == 0 {
1800 curTab = len(tabs) - 1
1804 return PostActionCall("PreviousTab", v)
1810 // NextTab switches to the next tab in the tab list
1811 func (v *View) NextTab(usePlugin bool) bool {
1813 if usePlugin && !PreActionCall("NextTab", v) {
1817 if curTab < len(tabs)-1 {
1819 } else if curTab == len(tabs)-1 {
1824 return PostActionCall("NextTab", v)
1830 // VSplitBinding opens an empty vertical split
1831 func (v *View) VSplitBinding(usePlugin bool) bool {
1833 if usePlugin && !PreActionCall("VSplit", v) {
1837 v.VSplit(NewBufferFromString("", ""))
1840 return PostActionCall("VSplit", v)
1846 // HSplitBinding opens an empty horizontal split
1847 func (v *View) HSplitBinding(usePlugin bool) bool {
1849 if usePlugin && !PreActionCall("HSplit", v) {
1853 v.HSplit(NewBufferFromString("", ""))
1856 return PostActionCall("HSplit", v)
1862 // Unsplit closes all splits in the current tab except the active one
1863 func (v *View) Unsplit(usePlugin bool) bool {
1865 if usePlugin && !PreActionCall("Unsplit", v) {
1869 curView := tabs[curTab].CurView
1870 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1871 view := tabs[curTab].views[i]
1872 if view != nil && view.Num != curView {
1874 // messenger.Message("Quit ", view.Buf.Path)
1879 return PostActionCall("Unsplit", v)
1885 // NextSplit changes the view to the next split
1886 func (v *View) NextSplit(usePlugin bool) bool {
1888 if usePlugin && !PreActionCall("NextSplit", v) {
1893 if tab.CurView < len(tab.views)-1 {
1900 return PostActionCall("NextSplit", v)
1906 // PreviousSplit changes the view to the previous split
1907 func (v *View) PreviousSplit(usePlugin bool) bool {
1909 if usePlugin && !PreActionCall("PreviousSplit", v) {
1914 if tab.CurView > 0 {
1917 tab.CurView = len(tab.views) - 1
1921 return PostActionCall("PreviousSplit", v)
1927 var curMacro []interface{}
1928 var recordingMacro bool
1930 // ToggleMacro toggles recording of a macro
1931 func (v *View) ToggleMacro(usePlugin bool) bool {
1933 if usePlugin && !PreActionCall("ToggleMacro", v) {
1937 recordingMacro = !recordingMacro
1940 curMacro = []interface{}{}
1941 messenger.Message("Recording")
1943 messenger.Message("Stopped recording")
1947 return PostActionCall("ToggleMacro", v)
1953 // PlayMacro plays back the most recently recorded macro
1954 func (v *View) PlayMacro(usePlugin bool) bool {
1955 if usePlugin && !PreActionCall("PlayMacro", v) {
1959 for _, action := range curMacro {
1960 switch t := action.(type) {
1962 // Insert a character
1963 if v.Cursor.HasSelection() {
1964 v.Cursor.DeleteSelection()
1965 v.Cursor.ResetSelection()
1967 v.Buf.Insert(v.Cursor.Loc, string(t))
1970 for pl := range loadedPlugins {
1971 _, err := Call(pl+".onRune", string(t), v)
1972 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1976 case func(*View, bool) bool:
1982 return PostActionCall("PlayMacro", v)
1987 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1988 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
1989 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
1990 // You can only spawn a cursor from the main cursor
1991 if v.Cursor == spawner {
1992 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
1996 if !spawner.HasSelection() {
1997 spawner.SelectWord()
2003 sel := spawner.GetSelection()
2005 searchStart = spawner.CurSelection[1]
2007 Search(regexp.QuoteMeta(sel), v, true)
2009 for _, cur := range v.Buf.cursors {
2010 if c.Loc == cur.Loc {
2014 v.Buf.cursors = append(v.Buf.cursors, c)
2015 v.Buf.UpdateCursors()
2021 PostActionCall("SpawnMultiCursor", v)
2028 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
2029 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
2030 if v.Cursor == &v.Buf.Cursor {
2031 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
2034 x, y := e.Position()
2035 x -= v.lineNumOffset - v.leftCol + v.x
2036 y += v.Topline - v.y
2042 v.MoveToMouseClick(x, y)
2044 v.Cursor = &v.Buf.Cursor
2046 v.Buf.cursors = append(v.Buf.cursors, c)
2047 v.Buf.MergeCursors()
2048 v.Buf.UpdateCursors()
2051 PostActionCall("SpawnMultiCursorAtMouse", v)
2057 // SkipMultiCursor moves the current multiple cursor to the next available position
2058 func (v *View) SkipMultiCursor(usePlugin bool) bool {
2059 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
2062 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
2065 sel := cursor.GetSelection()
2067 searchStart = cursor.CurSelection[1]
2069 Search(regexp.QuoteMeta(sel), v, true)
2074 PostActionCall("SkipMultiCursor", v)
2080 // RemoveMultiCursor removes the latest multiple cursor
2081 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
2082 end := len(v.Buf.cursors)
2085 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
2089 v.Buf.cursors[end-1] = nil
2090 v.Buf.cursors = v.Buf.cursors[:end-1]
2091 v.Buf.UpdateCursors()
2095 return PostActionCall("RemoveMultiCursor", v)
2100 v.RemoveAllMultiCursors(usePlugin)
2105 // RemoveAllMultiCursors removes all cursors except the base cursor
2106 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
2108 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
2112 v.Buf.clearCursors()
2116 return PostActionCall("RemoveAllMultiCursors", v)