10 "github.com/yuin/gopher-lua"
11 "github.com/zyedidia/clipboard"
12 "github.com/zyedidia/tcell"
15 // PreActionCall executes the lua pre callback if possible
16 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
18 for pl := range loadedPlugins {
19 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
20 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
24 if ret == lua.LFalse {
31 // PostActionCall executes the lua plugin callback if possible
32 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
34 for pl := range loadedPlugins {
35 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
36 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
40 if ret == lua.LFalse {
47 func (v *View) deselect(index int) bool {
48 if v.Cursor.HasSelection() {
49 v.Cursor.Loc = v.Cursor.CurSelection[index]
50 v.Cursor.ResetSelection()
51 v.Cursor.StoreVisualX()
57 // MousePress is the event that should happen when a normal click happens
58 // This is almost always bound to left click
59 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
60 if usePlugin && !PreActionCall("MousePress", v, e) {
65 x -= v.lineNumOffset - v.leftCol + v.x
68 // This is usually bound to left click
69 v.MoveToMouseClick(x, y)
71 if len(v.Buf.cursors) > 1 {
72 for i := 1; i < len(v.Buf.cursors); i++ {
73 v.Buf.cursors[i] = nil
75 v.Buf.cursors = v.Buf.cursors[:1]
77 v.Cursor.ResetSelection()
80 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
83 v.lastClickTime = time.Now()
89 v.Cursor.CopySelection("primary")
92 v.lastClickTime = time.Now()
98 v.Cursor.CopySelection("primary")
101 v.doubleClick = false
102 v.tripleClick = false
103 v.lastClickTime = time.Now()
105 v.Cursor.OrigSelection[0] = v.Cursor.Loc
106 v.Cursor.CurSelection[0] = v.Cursor.Loc
107 v.Cursor.CurSelection[1] = v.Cursor.Loc
109 v.mouseReleased = false
110 } else if !v.mouseReleased {
112 v.Cursor.AddLineToSelection()
113 } else if v.doubleClick {
114 v.Cursor.AddWordToSelection()
116 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
117 v.Cursor.CopySelection("primary")
122 PostActionCall("MousePress", v, e)
127 // ScrollUpAction scrolls the view up
128 func (v *View) ScrollUpAction(usePlugin bool) bool {
130 if usePlugin && !PreActionCall("ScrollUp", v) {
134 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
135 v.ScrollUp(scrollspeed)
138 PostActionCall("ScrollUp", v)
144 // ScrollDownAction scrolls the view up
145 func (v *View) ScrollDownAction(usePlugin bool) bool {
147 if usePlugin && !PreActionCall("ScrollDown", v) {
151 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
152 v.ScrollDown(scrollspeed)
155 PostActionCall("ScrollDown", v)
161 // Center centers the view on the cursor
162 func (v *View) Center(usePlugin bool) bool {
163 if usePlugin && !PreActionCall("Center", v) {
167 v.Topline = v.Cursor.Y - v.Height/2
168 if v.Topline+v.Height > v.Buf.NumLines {
169 v.Topline = v.Buf.NumLines - v.Height
176 return PostActionCall("Center", v)
181 // CursorUp moves the cursor up
182 func (v *View) CursorUp(usePlugin bool) bool {
183 if usePlugin && !PreActionCall("CursorUp", v) {
191 return PostActionCall("CursorUp", v)
196 // CursorDown moves the cursor down
197 func (v *View) CursorDown(usePlugin bool) bool {
198 if usePlugin && !PreActionCall("CursorDown", v) {
206 return PostActionCall("CursorDown", v)
211 // CursorLeft moves the cursor left
212 func (v *View) CursorLeft(usePlugin bool) bool {
213 if usePlugin && !PreActionCall("CursorLeft", v) {
217 if v.Cursor.HasSelection() {
218 v.Cursor.Loc = v.Cursor.CurSelection[0]
219 v.Cursor.ResetSelection()
220 v.Cursor.StoreVisualX()
222 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
223 tabmovement := v.Buf.Settings["tabmovement"].(bool)
224 if tabstospaces && tabmovement {
225 tabsize := int(v.Buf.Settings["tabsize"].(float64))
226 line := v.Buf.Line(v.Cursor.Y)
227 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
228 for i := 0; i < tabsize; i++ {
240 return PostActionCall("CursorLeft", v)
245 // CursorRight moves the cursor right
246 func (v *View) CursorRight(usePlugin bool) bool {
247 if usePlugin && !PreActionCall("CursorRight", v) {
251 if v.Cursor.HasSelection() {
252 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
253 v.Cursor.ResetSelection()
254 v.Cursor.StoreVisualX()
256 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
257 tabmovement := v.Buf.Settings["tabmovement"].(bool)
258 if tabstospaces && tabmovement {
259 tabsize := int(v.Buf.Settings["tabsize"].(float64))
260 line := v.Buf.Line(v.Cursor.Y)
261 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
262 for i := 0; i < tabsize; i++ {
274 return PostActionCall("CursorRight", v)
279 // WordRight moves the cursor one word to the right
280 func (v *View) WordRight(usePlugin bool) bool {
281 if usePlugin && !PreActionCall("WordRight", v) {
288 return PostActionCall("WordRight", v)
293 // WordLeft moves the cursor one word to the left
294 func (v *View) WordLeft(usePlugin bool) bool {
295 if usePlugin && !PreActionCall("WordLeft", v) {
302 return PostActionCall("WordLeft", v)
307 // SelectUp selects up one line
308 func (v *View) SelectUp(usePlugin bool) bool {
309 if usePlugin && !PreActionCall("SelectUp", v) {
313 if !v.Cursor.HasSelection() {
314 v.Cursor.OrigSelection[0] = v.Cursor.Loc
317 v.Cursor.SelectTo(v.Cursor.Loc)
320 return PostActionCall("SelectUp", v)
325 // SelectDown selects down one line
326 func (v *View) SelectDown(usePlugin bool) bool {
327 if usePlugin && !PreActionCall("SelectDown", v) {
331 if !v.Cursor.HasSelection() {
332 v.Cursor.OrigSelection[0] = v.Cursor.Loc
335 v.Cursor.SelectTo(v.Cursor.Loc)
338 return PostActionCall("SelectDown", v)
343 // SelectLeft selects the character to the left of the cursor
344 func (v *View) SelectLeft(usePlugin bool) bool {
345 if usePlugin && !PreActionCall("SelectLeft", v) {
350 count := v.Buf.End().Move(-1, v.Buf)
351 if loc.GreaterThan(count) {
354 if !v.Cursor.HasSelection() {
355 v.Cursor.OrigSelection[0] = loc
358 v.Cursor.SelectTo(v.Cursor.Loc)
361 return PostActionCall("SelectLeft", v)
366 // SelectRight selects the character to the right of the cursor
367 func (v *View) SelectRight(usePlugin bool) bool {
368 if usePlugin && !PreActionCall("SelectRight", v) {
373 count := v.Buf.End().Move(-1, v.Buf)
374 if loc.GreaterThan(count) {
377 if !v.Cursor.HasSelection() {
378 v.Cursor.OrigSelection[0] = loc
381 v.Cursor.SelectTo(v.Cursor.Loc)
384 return PostActionCall("SelectRight", v)
389 // SelectWordRight selects the word to the right of the cursor
390 func (v *View) SelectWordRight(usePlugin bool) bool {
391 if usePlugin && !PreActionCall("SelectWordRight", v) {
395 if !v.Cursor.HasSelection() {
396 v.Cursor.OrigSelection[0] = v.Cursor.Loc
399 v.Cursor.SelectTo(v.Cursor.Loc)
402 return PostActionCall("SelectWordRight", v)
407 // SelectWordLeft selects the word to the left of the cursor
408 func (v *View) SelectWordLeft(usePlugin bool) bool {
409 if usePlugin && !PreActionCall("SelectWordLeft", v) {
413 if !v.Cursor.HasSelection() {
414 v.Cursor.OrigSelection[0] = v.Cursor.Loc
417 v.Cursor.SelectTo(v.Cursor.Loc)
420 return PostActionCall("SelectWordLeft", v)
425 // StartOfLine moves the cursor to the start of the line
426 func (v *View) StartOfLine(usePlugin bool) bool {
427 if usePlugin && !PreActionCall("StartOfLine", v) {
436 return PostActionCall("StartOfLine", v)
441 // EndOfLine moves the cursor to the end of the line
442 func (v *View) EndOfLine(usePlugin bool) bool {
443 if usePlugin && !PreActionCall("EndOfLine", v) {
452 return PostActionCall("EndOfLine", v)
457 // SelectToStartOfLine selects to the start of the current line
458 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
459 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
463 if !v.Cursor.HasSelection() {
464 v.Cursor.OrigSelection[0] = v.Cursor.Loc
467 v.Cursor.SelectTo(v.Cursor.Loc)
470 return PostActionCall("SelectToStartOfLine", v)
475 // SelectToEndOfLine selects to the end of the current line
476 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
477 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
481 if !v.Cursor.HasSelection() {
482 v.Cursor.OrigSelection[0] = v.Cursor.Loc
485 v.Cursor.SelectTo(v.Cursor.Loc)
488 return PostActionCall("SelectToEndOfLine", v)
493 // CursorStart moves the cursor to the start of the buffer
494 func (v *View) CursorStart(usePlugin bool) bool {
495 if usePlugin && !PreActionCall("CursorStart", v) {
505 return PostActionCall("CursorStart", v)
510 // CursorEnd moves the cursor to the end of the buffer
511 func (v *View) CursorEnd(usePlugin bool) bool {
512 if usePlugin && !PreActionCall("CursorEnd", v) {
518 v.Cursor.Loc = v.Buf.End()
519 v.Cursor.StoreVisualX()
522 return PostActionCall("CursorEnd", v)
527 // SelectToStart selects the text from the cursor to the start of the buffer
528 func (v *View) SelectToStart(usePlugin bool) bool {
529 if usePlugin && !PreActionCall("SelectToStart", v) {
533 if !v.Cursor.HasSelection() {
534 v.Cursor.OrigSelection[0] = v.Cursor.Loc
537 v.Cursor.SelectTo(v.Buf.Start())
540 return PostActionCall("SelectToStart", v)
545 // SelectToEnd selects the text from the cursor to the end of the buffer
546 func (v *View) SelectToEnd(usePlugin bool) bool {
547 if usePlugin && !PreActionCall("SelectToEnd", v) {
551 if !v.Cursor.HasSelection() {
552 v.Cursor.OrigSelection[0] = v.Cursor.Loc
555 v.Cursor.SelectTo(v.Buf.End())
558 return PostActionCall("SelectToEnd", v)
563 // InsertSpace inserts a space
564 func (v *View) InsertSpace(usePlugin bool) bool {
565 if usePlugin && !PreActionCall("InsertSpace", v) {
569 if v.Cursor.HasSelection() {
570 v.Cursor.DeleteSelection()
571 v.Cursor.ResetSelection()
573 v.Buf.Insert(v.Cursor.Loc, " ")
577 return PostActionCall("InsertSpace", v)
582 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
583 func (v *View) InsertNewline(usePlugin bool) bool {
584 if usePlugin && !PreActionCall("InsertNewline", v) {
589 if v.Cursor.HasSelection() {
590 v.Cursor.DeleteSelection()
591 v.Cursor.ResetSelection()
594 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
595 v.Buf.Insert(v.Cursor.Loc, "\n")
598 if v.Buf.Settings["autoindent"].(bool) {
599 v.Buf.Insert(v.Cursor.Loc, ws)
600 // for i := 0; i < len(ws); i++ {
604 // Remove the whitespaces if keepautoindent setting is off
605 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
606 line := v.Buf.Line(v.Cursor.Y - 1)
607 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
610 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
613 return PostActionCall("InsertNewline", v)
618 // Backspace deletes the previous character
619 func (v *View) Backspace(usePlugin bool) bool {
620 if usePlugin && !PreActionCall("Backspace", v) {
624 // Delete a character
625 if v.Cursor.HasSelection() {
626 v.Cursor.DeleteSelection()
627 v.Cursor.ResetSelection()
628 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
629 // We have to do something a bit hacky here because we want to
630 // delete the line by first moving left and then deleting backwards
631 // but the undo redo would place the cursor in the wrong place
632 // So instead we move left, save the position, move back, delete
633 // and restore the position
635 // If the user is using spaces instead of tabs and they are deleting
636 // whitespace at the start of the line, we should delete as if it's a
637 // tab (tabSize number of spaces)
638 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
639 tabSize := int(v.Buf.Settings["tabsize"].(float64))
640 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
642 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
645 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
648 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
651 return PostActionCall("Backspace", v)
656 // DeleteWordRight deletes the word to the right of the cursor
657 func (v *View) DeleteWordRight(usePlugin bool) bool {
658 if usePlugin && !PreActionCall("DeleteWordRight", v) {
662 v.SelectWordRight(false)
663 if v.Cursor.HasSelection() {
664 v.Cursor.DeleteSelection()
665 v.Cursor.ResetSelection()
669 return PostActionCall("DeleteWordRight", v)
674 // DeleteWordLeft deletes the word to the left of the cursor
675 func (v *View) DeleteWordLeft(usePlugin bool) bool {
676 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
680 v.SelectWordLeft(false)
681 if v.Cursor.HasSelection() {
682 v.Cursor.DeleteSelection()
683 v.Cursor.ResetSelection()
687 return PostActionCall("DeleteWordLeft", v)
692 // Delete deletes the next character
693 func (v *View) Delete(usePlugin bool) bool {
694 if usePlugin && !PreActionCall("Delete", v) {
698 if v.Cursor.HasSelection() {
699 v.Cursor.DeleteSelection()
700 v.Cursor.ResetSelection()
703 if loc.LessThan(v.Buf.End()) {
704 v.Buf.Remove(loc, loc.Move(1, v.Buf))
709 return PostActionCall("Delete", v)
714 // IndentSelection indents the current selection
715 func (v *View) IndentSelection(usePlugin bool) bool {
716 if usePlugin && !PreActionCall("IndentSelection", v) {
720 if v.Cursor.HasSelection() {
721 start := v.Cursor.CurSelection[0]
722 end := v.Cursor.CurSelection[1]
724 start, end = end, start
728 endY := end.Move(-1, v.Buf).Y
729 endX := end.Move(-1, v.Buf).X
730 for y := startY; y <= endY; y++ {
731 tabsize := len(v.Buf.IndentString())
732 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
733 if y == startY && start.X > 0 {
734 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
737 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
743 return PostActionCall("IndentSelection", v)
750 // OutdentLine moves the current line back one indentation
751 func (v *View) OutdentLine(usePlugin bool) bool {
752 if usePlugin && !PreActionCall("OutdentLine", v) {
756 if v.Cursor.HasSelection() {
760 for x := 0; x < len(v.Buf.IndentString()); x++ {
761 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
764 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
769 return PostActionCall("OutdentLine", v)
774 // OutdentSelection takes the current selection and moves it back one indent level
775 func (v *View) OutdentSelection(usePlugin bool) bool {
776 if usePlugin && !PreActionCall("OutdentSelection", v) {
780 if v.Cursor.HasSelection() {
781 start := v.Cursor.CurSelection[0]
782 end := v.Cursor.CurSelection[1]
784 start, end = end, start
788 endY := end.Move(-1, v.Buf).Y
789 for y := startY; y <= endY; y++ {
790 for x := 0; x < len(v.Buf.IndentString()); x++ {
791 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
794 v.Buf.Remove(Loc{0, y}, Loc{1, y})
800 return PostActionCall("OutdentSelection", v)
807 // InsertTab inserts a tab or spaces
808 func (v *View) InsertTab(usePlugin bool) bool {
809 if usePlugin && !PreActionCall("InsertTab", v) {
813 if v.Cursor.HasSelection() {
817 tabBytes := len(v.Buf.IndentString())
818 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
819 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
820 // for i := 0; i < bytesUntilIndent; i++ {
825 return PostActionCall("InsertTab", v)
830 // SaveAll saves all open buffers
831 func (v *View) SaveAll(usePlugin bool) bool {
833 if usePlugin && !PreActionCall("SaveAll", v) {
837 for _, t := range tabs {
838 for _, v := range t.views {
844 return PostActionCall("SaveAll", v)
850 // Save the buffer to disk
851 func (v *View) Save(usePlugin bool) bool {
853 if usePlugin && !PreActionCall("Save", v) {
857 if v.Type.scratch == true {
858 // We can't save any view type with scratch set. eg help and log text
861 // If this is an empty buffer, ask for a filename
862 if v.Buf.Path == "" {
865 v.saveToFile(v.Buf.Path)
869 return PostActionCall("Save", v)
875 // This function saves the buffer to `filename` and changes the buffer's path and name
876 // to `filename` if the save is successful
877 func (v *View) saveToFile(filename string) {
878 err := v.Buf.SaveAs(filename)
880 if strings.HasSuffix(err.Error(), "permission denied") {
881 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
883 err = v.Buf.SaveAsWithSudo(filename)
885 messenger.Error(err.Error())
887 v.Buf.Path = filename
888 v.Buf.name = filename
889 messenger.Message("Saved " + filename)
895 messenger.Error(err.Error())
898 v.Buf.Path = filename
899 v.Buf.name = filename
900 messenger.Message("Saved " + filename)
904 // SaveAs saves the buffer to disk with the given name
905 func (v *View) SaveAs(usePlugin bool) bool {
907 if usePlugin && !PreActionCall("Find", v) {
911 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
913 // the filename might or might not be quoted, so unquote first then join the strings.
914 filename = strings.Join(SplitCommandArgs(filename), " ")
915 v.saveToFile(filename)
919 PostActionCall("Find", v)
925 // Find opens a prompt and searches forward for the input
926 func (v *View) Find(usePlugin bool) bool {
928 if usePlugin && !PreActionCall("Find", v) {
933 if v.Cursor.HasSelection() {
934 searchStart = v.Cursor.CurSelection[1]
935 searchStart = v.Cursor.CurSelection[1]
936 searchStr = v.Cursor.GetSelection()
938 searchStart = v.Cursor.Loc
940 BeginSearch(searchStr)
943 return PostActionCall("Find", v)
949 // FindNext searches forwards for the last used search term
950 func (v *View) FindNext(usePlugin bool) bool {
951 if usePlugin && !PreActionCall("FindNext", v) {
955 if v.Cursor.HasSelection() {
956 searchStart = v.Cursor.CurSelection[1]
957 // lastSearch = v.Cursor.GetSelection()
959 searchStart = v.Cursor.Loc
961 if lastSearch == "" {
964 messenger.Message("Finding: " + lastSearch)
965 Search(lastSearch, v, true)
968 return PostActionCall("FindNext", v)
973 // FindPrevious searches backwards for the last used search term
974 func (v *View) FindPrevious(usePlugin bool) bool {
975 if usePlugin && !PreActionCall("FindPrevious", v) {
979 if v.Cursor.HasSelection() {
980 searchStart = v.Cursor.CurSelection[0]
982 searchStart = v.Cursor.Loc
984 messenger.Message("Finding: " + lastSearch)
985 Search(lastSearch, v, false)
988 return PostActionCall("FindPrevious", v)
993 // Undo undoes the last action
994 func (v *View) Undo(usePlugin bool) bool {
995 if usePlugin && !PreActionCall("Undo", v) {
1000 messenger.Message("Undid action")
1003 return PostActionCall("Undo", v)
1008 // Redo redoes the last action
1009 func (v *View) Redo(usePlugin bool) bool {
1010 if usePlugin && !PreActionCall("Redo", v) {
1015 messenger.Message("Redid action")
1018 return PostActionCall("Redo", v)
1023 // Copy the selection to the system clipboard
1024 func (v *View) Copy(usePlugin bool) bool {
1026 if usePlugin && !PreActionCall("Copy", v) {
1030 if v.Cursor.HasSelection() {
1031 v.Cursor.CopySelection("clipboard")
1033 messenger.Message("Copied selection")
1037 return PostActionCall("Copy", v)
1043 // CutLine cuts the current line to the clipboard
1044 func (v *View) CutLine(usePlugin bool) bool {
1045 if usePlugin && !PreActionCall("CutLine", v) {
1049 v.Cursor.SelectLine()
1050 if !v.Cursor.HasSelection() {
1053 if v.freshClip == true {
1054 if v.Cursor.HasSelection() {
1055 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1056 messenger.Error(err)
1058 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1061 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1065 v.lastCutTime = time.Now()
1066 v.Cursor.DeleteSelection()
1067 v.Cursor.ResetSelection()
1068 messenger.Message("Cut line")
1071 return PostActionCall("CutLine", v)
1076 // Cut the selection to the system clipboard
1077 func (v *View) Cut(usePlugin bool) bool {
1078 if usePlugin && !PreActionCall("Cut", v) {
1082 if v.Cursor.HasSelection() {
1083 v.Cursor.CopySelection("clipboard")
1084 v.Cursor.DeleteSelection()
1085 v.Cursor.ResetSelection()
1087 messenger.Message("Cut selection")
1090 return PostActionCall("Cut", v)
1098 // DuplicateLine duplicates the current line or selection
1099 func (v *View) DuplicateLine(usePlugin bool) bool {
1100 if usePlugin && !PreActionCall("DuplicateLine", v) {
1104 if v.Cursor.HasSelection() {
1105 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1108 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1112 messenger.Message("Duplicated line")
1115 return PostActionCall("DuplicateLine", v)
1120 // DeleteLine deletes the current line
1121 func (v *View) DeleteLine(usePlugin bool) bool {
1122 if usePlugin && !PreActionCall("DeleteLine", v) {
1126 v.Cursor.SelectLine()
1127 if !v.Cursor.HasSelection() {
1130 v.Cursor.DeleteSelection()
1131 v.Cursor.ResetSelection()
1132 messenger.Message("Deleted line")
1135 return PostActionCall("DeleteLine", v)
1140 // MoveLinesUp moves up the current line or selected lines if any
1141 func (v *View) MoveLinesUp(usePlugin bool) bool {
1142 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1146 if v.Cursor.HasSelection() {
1147 if v.Cursor.CurSelection[0].Y == 0 {
1148 messenger.Message("Can not move further up")
1152 v.Cursor.CurSelection[0].Y,
1153 v.Cursor.CurSelection[1].Y,
1156 v.Cursor.CurSelection[0].Y -= 1
1157 v.Cursor.CurSelection[1].Y -= 1
1158 messenger.Message("Moved up selected line(s)")
1160 if v.Cursor.Loc.Y == 0 {
1161 messenger.Message("Can not move further up")
1168 messenger.Message("Moved up current line")
1170 v.Buf.IsModified = true
1173 return PostActionCall("MoveLinesUp", v)
1178 // MoveLinesDown moves down the current line or selected lines if any
1179 func (v *View) MoveLinesDown(usePlugin bool) bool {
1180 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1184 if v.Cursor.HasSelection() {
1185 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1186 messenger.Message("Can not move further down")
1189 v.Buf.MoveLinesDown(
1190 v.Cursor.CurSelection[0].Y,
1191 v.Cursor.CurSelection[1].Y,
1194 v.Cursor.CurSelection[0].Y += 1
1195 v.Cursor.CurSelection[1].Y += 1
1196 messenger.Message("Moved down selected line(s)")
1198 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1199 messenger.Message("Can not move further down")
1202 v.Buf.MoveLinesDown(
1206 messenger.Message("Moved down current line")
1208 v.Buf.IsModified = true
1211 return PostActionCall("MoveLinesDown", v)
1216 // Paste whatever is in the system clipboard into the buffer
1217 // Delete and paste if the user has a selection
1218 func (v *View) Paste(usePlugin bool) bool {
1219 if usePlugin && !PreActionCall("Paste", v) {
1223 clip, _ := clipboard.ReadAll("clipboard")
1227 return PostActionCall("Paste", v)
1232 // PastePrimary pastes from the primary clipboard (only use on linux)
1233 func (v *View) PastePrimary(usePlugin bool) bool {
1234 if usePlugin && !PreActionCall("Paste", v) {
1238 clip, _ := clipboard.ReadAll("primary")
1242 return PostActionCall("Paste", v)
1247 // SelectAll selects the entire buffer
1248 func (v *View) SelectAll(usePlugin bool) bool {
1249 if usePlugin && !PreActionCall("SelectAll", v) {
1253 v.Cursor.SetSelectionStart(v.Buf.Start())
1254 v.Cursor.SetSelectionEnd(v.Buf.End())
1255 // Put the cursor at the beginning
1260 return PostActionCall("SelectAll", v)
1265 // OpenFile opens a new file in the buffer
1266 func (v *View) OpenFile(usePlugin bool) bool {
1268 if usePlugin && !PreActionCall("OpenFile", v) {
1273 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1275 HandleCommand(input)
1277 return PostActionCall("OpenFile", v)
1285 // Start moves the viewport to the start of the buffer
1286 func (v *View) Start(usePlugin bool) bool {
1288 if usePlugin && !PreActionCall("Start", v) {
1295 return PostActionCall("Start", v)
1301 // End moves the viewport to the end of the buffer
1302 func (v *View) End(usePlugin bool) bool {
1304 if usePlugin && !PreActionCall("End", v) {
1308 if v.Height > v.Buf.NumLines {
1311 v.Topline = v.Buf.NumLines - v.Height
1315 return PostActionCall("End", v)
1321 // PageUp scrolls the view up a page
1322 func (v *View) PageUp(usePlugin bool) bool {
1324 if usePlugin && !PreActionCall("PageUp", v) {
1328 if v.Topline > v.Height {
1329 v.ScrollUp(v.Height)
1335 return PostActionCall("PageUp", v)
1341 // PageDown scrolls the view down a page
1342 func (v *View) PageDown(usePlugin bool) bool {
1344 if usePlugin && !PreActionCall("PageDown", v) {
1348 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1349 v.ScrollDown(v.Height)
1350 } else if v.Buf.NumLines >= v.Height {
1351 v.Topline = v.Buf.NumLines - v.Height
1355 return PostActionCall("PageDown", v)
1361 // CursorPageUp places the cursor a page up
1362 func (v *View) CursorPageUp(usePlugin bool) bool {
1363 if usePlugin && !PreActionCall("CursorPageUp", v) {
1369 if v.Cursor.HasSelection() {
1370 v.Cursor.Loc = v.Cursor.CurSelection[0]
1371 v.Cursor.ResetSelection()
1372 v.Cursor.StoreVisualX()
1374 v.Cursor.UpN(v.Height)
1377 return PostActionCall("CursorPageUp", v)
1382 // CursorPageDown places the cursor a page up
1383 func (v *View) CursorPageDown(usePlugin bool) bool {
1384 if usePlugin && !PreActionCall("CursorPageDown", v) {
1390 if v.Cursor.HasSelection() {
1391 v.Cursor.Loc = v.Cursor.CurSelection[1]
1392 v.Cursor.ResetSelection()
1393 v.Cursor.StoreVisualX()
1395 v.Cursor.DownN(v.Height)
1398 return PostActionCall("CursorPageDown", v)
1403 // HalfPageUp scrolls the view up half a page
1404 func (v *View) HalfPageUp(usePlugin bool) bool {
1406 if usePlugin && !PreActionCall("HalfPageUp", v) {
1410 if v.Topline > v.Height/2 {
1411 v.ScrollUp(v.Height / 2)
1417 return PostActionCall("HalfPageUp", v)
1423 // HalfPageDown scrolls the view down half a page
1424 func (v *View) HalfPageDown(usePlugin bool) bool {
1426 if usePlugin && !PreActionCall("HalfPageDown", v) {
1430 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1431 v.ScrollDown(v.Height / 2)
1433 if v.Buf.NumLines >= v.Height {
1434 v.Topline = v.Buf.NumLines - v.Height
1439 return PostActionCall("HalfPageDown", v)
1445 // ToggleRuler turns line numbers off and on
1446 func (v *View) ToggleRuler(usePlugin bool) bool {
1448 if usePlugin && !PreActionCall("ToggleRuler", v) {
1452 if v.Buf.Settings["ruler"] == false {
1453 v.Buf.Settings["ruler"] = true
1454 messenger.Message("Enabled ruler")
1456 v.Buf.Settings["ruler"] = false
1457 messenger.Message("Disabled ruler")
1461 return PostActionCall("ToggleRuler", v)
1467 // JumpLine jumps to a line and moves the view accordingly.
1468 func (v *View) JumpLine(usePlugin bool) bool {
1469 if usePlugin && !PreActionCall("JumpLine", v) {
1473 // Prompt for line number
1474 message := fmt.Sprintf("Jump to line (1 - %v) # ", v.Buf.NumLines)
1475 linestring, canceled := messenger.Prompt(message, "", "LineNumber", NoCompletion)
1479 lineint, err := strconv.Atoi(linestring)
1480 lineint = lineint - 1 // fix offset
1482 messenger.Error(err) // return errors
1485 // Move cursor and view if possible.
1486 if lineint < v.Buf.NumLines && lineint >= 0 {
1488 v.Cursor.Y = lineint
1491 return PostActionCall("JumpLine", v)
1495 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1499 // ClearStatus clears the messenger bar
1500 func (v *View) ClearStatus(usePlugin bool) bool {
1502 if usePlugin && !PreActionCall("ClearStatus", v) {
1506 messenger.Message("")
1509 return PostActionCall("ClearStatus", v)
1515 // ToggleHelp toggles the help screen
1516 func (v *View) ToggleHelp(usePlugin bool) bool {
1518 if usePlugin && !PreActionCall("ToggleHelp", v) {
1522 if v.Type != vtHelp {
1523 // Open the default help
1530 return PostActionCall("ToggleHelp", v)
1536 // ShellMode opens a terminal to run a shell command
1537 func (v *View) ShellMode(usePlugin bool) bool {
1539 if usePlugin && !PreActionCall("ShellMode", v) {
1543 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1545 // The true here is for openTerm to make the command interactive
1546 HandleShellCommand(input, true, true)
1548 return PostActionCall("ShellMode", v)
1555 // CommandMode lets the user enter a command
1556 func (v *View) CommandMode(usePlugin bool) bool {
1558 if usePlugin && !PreActionCall("CommandMode", v) {
1562 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1564 HandleCommand(input)
1566 return PostActionCall("CommandMode", v)
1574 // Escape leaves current mode
1575 func (v *View) Escape(usePlugin bool) bool {
1577 // check if user is searching, or the last search is still active
1578 if searching || lastSearch != "" {
1582 // check if a prompt is shown, hide it and don't quit
1583 if messenger.hasPrompt {
1584 messenger.Reset() // FIXME
1592 // Quit this will close the current tab or view that is open
1593 func (v *View) Quit(usePlugin bool) bool {
1595 if usePlugin && !PreActionCall("Quit", v) {
1599 // Make sure not to quit if there are unsaved changes
1602 if len(tabs[curTab].views) > 1 {
1603 v.splitNode.Delete()
1604 tabs[v.TabNum].Cleanup()
1605 tabs[v.TabNum].Resize()
1606 } else if len(tabs) > 1 {
1607 if len(tabs[v.TabNum].views) == 1 {
1608 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1609 for i, t := range tabs {
1612 if curTab >= len(tabs) {
1616 CurView().ToggleTabbar()
1621 PostActionCall("Quit", v)
1630 return PostActionCall("Quit", v)
1636 // QuitAll quits the whole editor; all splits and tabs
1637 func (v *View) QuitAll(usePlugin bool) bool {
1639 if usePlugin && !PreActionCall("QuitAll", v) {
1644 for _, tab := range tabs {
1645 for _, v := range tab.views {
1653 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1654 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1657 for _, tab := range tabs {
1658 for _, v := range tab.views {
1664 PostActionCall("QuitAll", v)
1676 // AddTab adds a new tab with an empty buffer
1677 func (v *View) AddTab(usePlugin bool) bool {
1679 if usePlugin && !PreActionCall("AddTab", v) {
1683 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1684 tab.SetNum(len(tabs))
1685 tabs = append(tabs, tab)
1686 curTab = len(tabs) - 1
1688 for _, t := range tabs {
1689 for _, v := range t.views {
1696 return PostActionCall("AddTab", v)
1702 // PreviousTab switches to the previous tab in the tab list
1703 func (v *View) PreviousTab(usePlugin bool) bool {
1705 if usePlugin && !PreActionCall("PreviousTab", v) {
1711 } else if curTab == 0 {
1712 curTab = len(tabs) - 1
1716 return PostActionCall("PreviousTab", v)
1722 // NextTab switches to the next tab in the tab list
1723 func (v *View) NextTab(usePlugin bool) bool {
1725 if usePlugin && !PreActionCall("NextTab", v) {
1729 if curTab < len(tabs)-1 {
1731 } else if curTab == len(tabs)-1 {
1736 return PostActionCall("NextTab", v)
1742 // VSplitBinding opens an empty vertical split
1743 func (v *View) VSplitBinding(usePlugin bool) bool {
1745 if usePlugin && !PreActionCall("VSplit", v) {
1749 v.VSplit(NewBufferFromString("", ""))
1752 return PostActionCall("VSplit", v)
1758 // HSplitBinding opens an empty horizontal split
1759 func (v *View) HSplitBinding(usePlugin bool) bool {
1761 if usePlugin && !PreActionCall("HSplit", v) {
1765 v.HSplit(NewBufferFromString("", ""))
1768 return PostActionCall("HSplit", v)
1774 // Unsplit closes all splits in the current tab except the active one
1775 func (v *View) Unsplit(usePlugin bool) bool {
1777 if usePlugin && !PreActionCall("Unsplit", v) {
1781 curView := tabs[curTab].CurView
1782 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1783 view := tabs[curTab].views[i]
1784 if view != nil && view.Num != curView {
1786 // messenger.Message("Quit ", view.Buf.Path)
1791 return PostActionCall("Unsplit", v)
1797 // NextSplit changes the view to the next split
1798 func (v *View) NextSplit(usePlugin bool) bool {
1800 if usePlugin && !PreActionCall("NextSplit", v) {
1805 if tab.CurView < len(tab.views)-1 {
1812 return PostActionCall("NextSplit", v)
1818 // PreviousSplit changes the view to the previous split
1819 func (v *View) PreviousSplit(usePlugin bool) bool {
1821 if usePlugin && !PreActionCall("PreviousSplit", v) {
1826 if tab.CurView > 0 {
1829 tab.CurView = len(tab.views) - 1
1833 return PostActionCall("PreviousSplit", v)
1839 var curMacro []interface{}
1840 var recordingMacro bool
1842 // ToggleMacro toggles recording of a macro
1843 func (v *View) ToggleMacro(usePlugin bool) bool {
1845 if usePlugin && !PreActionCall("ToggleMacro", v) {
1849 recordingMacro = !recordingMacro
1852 curMacro = []interface{}{}
1853 messenger.Message("Recording")
1855 messenger.Message("Stopped recording")
1859 return PostActionCall("ToggleMacro", v)
1865 // PlayMacro plays back the most recently recorded macro
1866 func (v *View) PlayMacro(usePlugin bool) bool {
1867 if usePlugin && !PreActionCall("PlayMacro", v) {
1871 for _, action := range curMacro {
1872 switch t := action.(type) {
1874 // Insert a character
1875 if v.Cursor.HasSelection() {
1876 v.Cursor.DeleteSelection()
1877 v.Cursor.ResetSelection()
1879 v.Buf.Insert(v.Cursor.Loc, string(t))
1882 for pl := range loadedPlugins {
1883 _, err := Call(pl+".onRune", string(t), v)
1884 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1888 case func(*View, bool) bool:
1894 return PostActionCall("PlayMacro", v)
1899 // SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
1900 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
1901 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
1902 // You can only spawn a cursor from the main cursor
1903 if v.Cursor == spawner {
1904 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
1908 if !spawner.HasSelection() {
1909 spawner.SelectWord()
1915 sel := spawner.GetSelection()
1917 searchStart = spawner.CurSelection[1]
1919 Search(sel, v, true)
1921 for _, cur := range v.Buf.cursors {
1922 if c.Loc == cur.Loc {
1926 v.Buf.cursors = append(v.Buf.cursors, c)
1927 v.Buf.UpdateCursors()
1933 PostActionCall("SpawnMultiCursor", v)
1940 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1941 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
1942 if v.Cursor == &v.Buf.Cursor {
1943 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
1946 x, y := e.Position()
1947 x -= v.lineNumOffset - v.leftCol + v.x
1948 y += v.Topline - v.y
1954 v.MoveToMouseClick(x, y)
1956 v.Cursor = &v.Buf.Cursor
1958 v.Buf.cursors = append(v.Buf.cursors, c)
1959 v.Buf.MergeCursors()
1960 v.Buf.UpdateCursors()
1963 PostActionCall("SpawnMultiCursorAtMouse", v)
1969 // SkipMultiCursor moves the current multiple cursor to the next available position
1970 func (v *View) SkipMultiCursor(usePlugin bool) bool {
1971 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
1974 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
1977 sel := cursor.GetSelection()
1979 searchStart = cursor.CurSelection[1]
1981 Search(sel, v, true)
1986 PostActionCall("SkipMultiCursor", v)
1992 // RemoveMultiCursor removes the latest multiple cursor
1993 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
1994 end := len(v.Buf.cursors)
1997 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
2001 v.Buf.cursors[end-1] = nil
2002 v.Buf.cursors = v.Buf.cursors[:end-1]
2003 v.Buf.UpdateCursors()
2007 return PostActionCall("RemoveMultiCursor", v)
2012 v.RemoveAllMultiCursors(usePlugin)
2017 // RemoveAllMultiCursors removes all cursors except the base cursor
2018 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
2020 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
2024 for i := 1; i < len(v.Buf.cursors); i++ {
2025 v.Buf.cursors[i] = nil
2027 v.Buf.cursors = v.Buf.cursors[:1]
2028 v.Buf.UpdateCursors()
2029 v.Cursor.ResetSelection()
2033 return PostActionCall("RemoveAllMultiCursors", v)