9 "github.com/yuin/gopher-lua"
10 "github.com/zyedidia/clipboard"
11 "github.com/zyedidia/tcell"
14 // PreActionCall executes the lua pre callback if possible
15 func PreActionCall(funcName string, view *View, args ...interface{}) bool {
17 for pl := range loadedPlugins {
18 ret, err := Call(pl+".pre"+funcName, append([]interface{}{view}, args...)...)
19 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
23 if ret == lua.LFalse {
30 // PostActionCall executes the lua plugin callback if possible
31 func PostActionCall(funcName string, view *View, args ...interface{}) bool {
33 for pl := range loadedPlugins {
34 ret, err := Call(pl+".on"+funcName, append([]interface{}{view}, args...)...)
35 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
39 if ret == lua.LFalse {
46 func (v *View) deselect(index int) bool {
47 if v.Cursor.HasSelection() {
48 v.Cursor.Loc = v.Cursor.CurSelection[index]
49 v.Cursor.ResetSelection()
55 // MousePress is the event that should happen when a normal click happens
56 // This is almost always bound to left click
57 func (v *View) MousePress(usePlugin bool, e *tcell.EventMouse) bool {
58 if usePlugin && !PreActionCall("MousePress", v, e) {
63 x -= v.lineNumOffset - v.leftCol + v.x
66 // This is usually bound to left click
68 v.MoveToMouseClick(x, y)
69 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
72 v.lastClickTime = time.Now()
78 v.Cursor.CopySelection("primary")
81 v.lastClickTime = time.Now()
87 v.Cursor.CopySelection("primary")
92 v.lastClickTime = time.Now()
94 v.Cursor.OrigSelection[0] = v.Cursor.Loc
95 v.Cursor.CurSelection[0] = v.Cursor.Loc
96 v.Cursor.CurSelection[1] = v.Cursor.Loc
98 v.mouseReleased = false
99 } else if !v.mouseReleased {
100 v.MoveToMouseClick(x, y)
102 v.Cursor.AddLineToSelection()
103 } else if v.doubleClick {
104 v.Cursor.AddWordToSelection()
106 v.Cursor.SetSelectionEnd(v.Cursor.Loc)
107 v.Cursor.CopySelection("primary")
112 PostActionCall("MousePress", v, e)
117 // ScrollUpAction scrolls the view up
118 func (v *View) ScrollUpAction(usePlugin bool) bool {
119 if usePlugin && !PreActionCall("ScrollUp", v) {
123 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
124 v.ScrollUp(scrollspeed)
127 PostActionCall("ScrollUp", v)
132 // ScrollDownAction scrolls the view up
133 func (v *View) ScrollDownAction(usePlugin bool) bool {
134 if usePlugin && !PreActionCall("ScrollDown", v) {
138 scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
139 v.ScrollDown(scrollspeed)
142 PostActionCall("ScrollDown", v)
147 // Center centers the view on the cursor
148 func (v *View) Center(usePlugin bool) bool {
149 if usePlugin && !PreActionCall("Center", v) {
153 v.Topline = v.Cursor.Y - v.Height/2
154 if v.Topline+v.Height > v.Buf.NumLines {
155 v.Topline = v.Buf.NumLines - v.Height
162 return PostActionCall("Center", v)
167 // CursorUp moves the cursor up
168 func (v *View) CursorUp(usePlugin bool) bool {
169 if usePlugin && !PreActionCall("CursorUp", v) {
177 return PostActionCall("CursorUp", v)
182 // CursorDown moves the cursor down
183 func (v *View) CursorDown(usePlugin bool) bool {
184 if usePlugin && !PreActionCall("CursorDown", v) {
192 return PostActionCall("CursorDown", v)
197 // CursorLeft moves the cursor left
198 func (v *View) CursorLeft(usePlugin bool) bool {
199 if usePlugin && !PreActionCall("CursorLeft", v) {
203 if v.Cursor.HasSelection() {
204 v.Cursor.Loc = v.Cursor.CurSelection[0]
205 v.Cursor.ResetSelection()
207 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
208 tabmovement := v.Buf.Settings["tabmovement"].(bool)
209 if tabstospaces && tabmovement {
210 tabsize := int(v.Buf.Settings["tabsize"].(float64))
211 line := v.Buf.Line(v.Cursor.Y)
212 if v.Cursor.X-tabsize >= 0 && line[v.Cursor.X-tabsize:v.Cursor.X] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X-tabsize]) {
213 for i := 0; i < tabsize; i++ {
225 return PostActionCall("CursorLeft", v)
230 // CursorRight moves the cursor right
231 func (v *View) CursorRight(usePlugin bool) bool {
232 if usePlugin && !PreActionCall("CursorRight", v) {
236 if v.Cursor.HasSelection() {
237 v.Cursor.Loc = v.Cursor.CurSelection[1].Move(-1, v.Buf)
238 v.Cursor.ResetSelection()
240 tabstospaces := v.Buf.Settings["tabstospaces"].(bool)
241 tabmovement := v.Buf.Settings["tabmovement"].(bool)
242 if tabstospaces && tabmovement {
243 tabsize := int(v.Buf.Settings["tabsize"].(float64))
244 line := v.Buf.Line(v.Cursor.Y)
245 if v.Cursor.X+tabsize < Count(line) && line[v.Cursor.X:v.Cursor.X+tabsize] == Spaces(tabsize) && IsStrWhitespace(line[0:v.Cursor.X]) {
246 for i := 0; i < tabsize; i++ {
258 return PostActionCall("CursorRight", v)
263 // WordRight moves the cursor one word to the right
264 func (v *View) WordRight(usePlugin bool) bool {
265 if usePlugin && !PreActionCall("WordRight", v) {
272 return PostActionCall("WordRight", v)
277 // WordLeft moves the cursor one word to the left
278 func (v *View) WordLeft(usePlugin bool) bool {
279 if usePlugin && !PreActionCall("WordLeft", v) {
286 return PostActionCall("WordLeft", v)
291 // SelectUp selects up one line
292 func (v *View) SelectUp(usePlugin bool) bool {
293 if usePlugin && !PreActionCall("SelectUp", v) {
297 if !v.Cursor.HasSelection() {
298 v.Cursor.OrigSelection[0] = v.Cursor.Loc
301 v.Cursor.SelectTo(v.Cursor.Loc)
304 return PostActionCall("SelectUp", v)
309 // SelectDown selects down one line
310 func (v *View) SelectDown(usePlugin bool) bool {
311 if usePlugin && !PreActionCall("SelectDown", v) {
315 if !v.Cursor.HasSelection() {
316 v.Cursor.OrigSelection[0] = v.Cursor.Loc
319 v.Cursor.SelectTo(v.Cursor.Loc)
322 return PostActionCall("SelectDown", v)
327 // SelectLeft selects the character to the left of the cursor
328 func (v *View) SelectLeft(usePlugin bool) bool {
329 if usePlugin && !PreActionCall("SelectLeft", v) {
334 count := v.Buf.End().Move(-1, v.Buf)
335 if loc.GreaterThan(count) {
338 if !v.Cursor.HasSelection() {
339 v.Cursor.OrigSelection[0] = loc
342 v.Cursor.SelectTo(v.Cursor.Loc)
345 return PostActionCall("SelectLeft", v)
350 // SelectRight selects the character to the right of the cursor
351 func (v *View) SelectRight(usePlugin bool) bool {
352 if usePlugin && !PreActionCall("SelectRight", v) {
357 count := v.Buf.End().Move(-1, v.Buf)
358 if loc.GreaterThan(count) {
361 if !v.Cursor.HasSelection() {
362 v.Cursor.OrigSelection[0] = loc
365 v.Cursor.SelectTo(v.Cursor.Loc)
368 return PostActionCall("SelectRight", v)
373 // SelectWordRight selects the word to the right of the cursor
374 func (v *View) SelectWordRight(usePlugin bool) bool {
375 if usePlugin && !PreActionCall("SelectWordRight", v) {
379 if !v.Cursor.HasSelection() {
380 v.Cursor.OrigSelection[0] = v.Cursor.Loc
383 v.Cursor.SelectTo(v.Cursor.Loc)
386 return PostActionCall("SelectWordRight", v)
391 // SelectWordLeft selects the word to the left of the cursor
392 func (v *View) SelectWordLeft(usePlugin bool) bool {
393 if usePlugin && !PreActionCall("SelectWordLeft", v) {
397 if !v.Cursor.HasSelection() {
398 v.Cursor.OrigSelection[0] = v.Cursor.Loc
401 v.Cursor.SelectTo(v.Cursor.Loc)
404 return PostActionCall("SelectWordLeft", v)
409 // StartOfLine moves the cursor to the start of the line
410 func (v *View) StartOfLine(usePlugin bool) bool {
411 if usePlugin && !PreActionCall("StartOfLine", v) {
420 return PostActionCall("StartOfLine", v)
425 // EndOfLine moves the cursor to the end of the line
426 func (v *View) EndOfLine(usePlugin bool) bool {
427 if usePlugin && !PreActionCall("EndOfLine", v) {
436 return PostActionCall("EndOfLine", v)
441 // SelectToStartOfLine selects to the start of the current line
442 func (v *View) SelectToStartOfLine(usePlugin bool) bool {
443 if usePlugin && !PreActionCall("SelectToStartOfLine", v) {
447 if !v.Cursor.HasSelection() {
448 v.Cursor.OrigSelection[0] = v.Cursor.Loc
451 v.Cursor.SelectTo(v.Cursor.Loc)
454 return PostActionCall("SelectToStartOfLine", v)
459 // SelectToEndOfLine selects to the end of the current line
460 func (v *View) SelectToEndOfLine(usePlugin bool) bool {
461 if usePlugin && !PreActionCall("SelectToEndOfLine", v) {
465 if !v.Cursor.HasSelection() {
466 v.Cursor.OrigSelection[0] = v.Cursor.Loc
469 v.Cursor.SelectTo(v.Cursor.Loc)
472 return PostActionCall("SelectToEndOfLine", v)
477 // CursorStart moves the cursor to the start of the buffer
478 func (v *View) CursorStart(usePlugin bool) bool {
479 if usePlugin && !PreActionCall("CursorStart", v) {
489 return PostActionCall("CursorStart", v)
494 // CursorEnd moves the cursor to the end of the buffer
495 func (v *View) CursorEnd(usePlugin bool) bool {
496 if usePlugin && !PreActionCall("CursorEnd", v) {
502 v.Cursor.Loc = v.Buf.End()
505 return PostActionCall("CursorEnd", v)
510 // SelectToStart selects the text from the cursor to the start of the buffer
511 func (v *View) SelectToStart(usePlugin bool) bool {
512 if usePlugin && !PreActionCall("SelectToStart", v) {
516 if !v.Cursor.HasSelection() {
517 v.Cursor.OrigSelection[0] = v.Cursor.Loc
520 v.Cursor.SelectTo(v.Buf.Start())
523 return PostActionCall("SelectToStart", v)
528 // SelectToEnd selects the text from the cursor to the end of the buffer
529 func (v *View) SelectToEnd(usePlugin bool) bool {
530 if usePlugin && !PreActionCall("SelectToEnd", v) {
534 if !v.Cursor.HasSelection() {
535 v.Cursor.OrigSelection[0] = v.Cursor.Loc
538 v.Cursor.SelectTo(v.Buf.End())
541 return PostActionCall("SelectToEnd", v)
546 // InsertSpace inserts a space
547 func (v *View) InsertSpace(usePlugin bool) bool {
548 if usePlugin && !PreActionCall("InsertSpace", v) {
552 if v.Cursor.HasSelection() {
553 v.Cursor.DeleteSelection()
554 v.Cursor.ResetSelection()
556 v.Buf.Insert(v.Cursor.Loc, " ")
560 return PostActionCall("InsertSpace", v)
565 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
566 func (v *View) InsertNewline(usePlugin bool) bool {
567 if usePlugin && !PreActionCall("InsertNewline", v) {
572 if v.Cursor.HasSelection() {
573 v.Cursor.DeleteSelection()
574 v.Cursor.ResetSelection()
577 v.Buf.Insert(v.Cursor.Loc, "\n")
578 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
581 if v.Buf.Settings["autoindent"].(bool) {
582 v.Buf.Insert(v.Cursor.Loc, ws)
583 for i := 0; i < len(ws); i++ {
587 // Remove the whitespaces if keepautoindent setting is off
588 if IsSpacesOrTabs(v.Buf.Line(v.Cursor.Y-1)) && !v.Buf.Settings["keepautoindent"].(bool) {
589 line := v.Buf.Line(v.Cursor.Y - 1)
590 v.Buf.Remove(Loc{0, v.Cursor.Y - 1}, Loc{Count(line), v.Cursor.Y - 1})
593 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
596 return PostActionCall("InsertNewline", v)
601 // Backspace deletes the previous character
602 func (v *View) Backspace(usePlugin bool) bool {
603 if usePlugin && !PreActionCall("Backspace", v) {
607 // Delete a character
608 if v.Cursor.HasSelection() {
609 v.Cursor.DeleteSelection()
610 v.Cursor.ResetSelection()
611 } else if v.Cursor.Loc.GreaterThan(v.Buf.Start()) {
612 // We have to do something a bit hacky here because we want to
613 // delete the line by first moving left and then deleting backwards
614 // but the undo redo would place the cursor in the wrong place
615 // So instead we move left, save the position, move back, delete
616 // and restore the position
618 // If the user is using spaces instead of tabs and they are deleting
619 // whitespace at the start of the line, we should delete as if it's a
620 // tab (tabSize number of spaces)
621 lineStart := v.Buf.Line(v.Cursor.Y)[:v.Cursor.X]
622 tabSize := int(v.Buf.Settings["tabsize"].(float64))
623 if v.Buf.Settings["tabstospaces"].(bool) && IsSpaces(lineStart) && len(lineStart) != 0 && len(lineStart)%tabSize == 0 {
625 v.Cursor.Loc = loc.Move(-tabSize, v.Buf)
626 cx, cy := v.Cursor.X, v.Cursor.Y
628 v.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
629 v.Cursor.X, v.Cursor.Y = cx, cy
632 cx, cy := v.Cursor.X, v.Cursor.Y
635 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
636 v.Cursor.X, v.Cursor.Y = cx, cy
639 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
642 return PostActionCall("Backspace", v)
647 // DeleteWordRight deletes the word to the right of the cursor
648 func (v *View) DeleteWordRight(usePlugin bool) bool {
649 if usePlugin && !PreActionCall("DeleteWordRight", v) {
653 v.SelectWordRight(false)
654 if v.Cursor.HasSelection() {
655 v.Cursor.DeleteSelection()
656 v.Cursor.ResetSelection()
660 return PostActionCall("DeleteWordRight", v)
665 // DeleteWordLeft deletes the word to the left of the cursor
666 func (v *View) DeleteWordLeft(usePlugin bool) bool {
667 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
671 v.SelectWordLeft(false)
672 if v.Cursor.HasSelection() {
673 v.Cursor.DeleteSelection()
674 v.Cursor.ResetSelection()
678 return PostActionCall("DeleteWordLeft", v)
683 // Delete deletes the next character
684 func (v *View) Delete(usePlugin bool) bool {
685 if usePlugin && !PreActionCall("Delete", v) {
689 if v.Cursor.HasSelection() {
690 v.Cursor.DeleteSelection()
691 v.Cursor.ResetSelection()
694 if loc.LessThan(v.Buf.End()) {
695 v.Buf.Remove(loc, loc.Move(1, v.Buf))
700 return PostActionCall("Delete", v)
705 // IndentSelection indents the current selection
706 func (v *View) IndentSelection(usePlugin bool) bool {
707 if usePlugin && !PreActionCall("IndentSelection", v) {
711 if v.Cursor.HasSelection() {
712 start := v.Cursor.CurSelection[0]
713 end := v.Cursor.CurSelection[1]
715 start, end = end, start
719 endY := end.Move(-1, v.Buf).Y
720 endX := end.Move(-1, v.Buf).X
721 for y := startY; y <= endY; y++ {
722 tabsize := len(v.Buf.IndentString())
723 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
724 if y == startY && start.X > 0 {
725 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
728 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
734 return PostActionCall("IndentSelection", v)
741 // OutdentLine moves the current line back one indentation
742 func (v *View) OutdentLine(usePlugin bool) bool {
743 if usePlugin && !PreActionCall("OutdentLine", v) {
747 if v.Cursor.HasSelection() {
751 for x := 0; x < len(v.Buf.IndentString()); x++ {
752 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
755 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
761 return PostActionCall("OutdentLine", v)
766 // OutdentSelection takes the current selection and moves it back one indent level
767 func (v *View) OutdentSelection(usePlugin bool) bool {
768 if usePlugin && !PreActionCall("OutdentSelection", 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 for x := 0; x < len(v.Buf.IndentString()); x++ {
784 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
787 v.Buf.Remove(Loc{0, y}, Loc{1, y})
788 if y == startY && start.X > 0 {
789 v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
792 v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
799 return PostActionCall("OutdentSelection", v)
806 // InsertTab inserts a tab or spaces
807 func (v *View) InsertTab(usePlugin bool) bool {
808 if usePlugin && !PreActionCall("InsertTab", v) {
812 if v.Cursor.HasSelection() {
816 tabBytes := len(v.Buf.IndentString())
817 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
818 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
819 for i := 0; i < bytesUntilIndent; i++ {
824 return PostActionCall("InsertTab", v)
829 // SaveAll saves all open buffers
830 func (v *View) SaveAll(usePlugin bool) bool {
831 if usePlugin && !PreActionCall("SaveAll", v) {
835 for _, t := range tabs {
836 for _, v := range t.views {
842 return PostActionCall("SaveAll", v)
847 // Save the buffer to disk
848 func (v *View) Save(usePlugin bool) bool {
849 if usePlugin && !PreActionCall("Save", v) {
853 if v.Type.scratch == true {
854 // We can't save any view type with scratch set. eg help and log text
857 // If this is an empty buffer, ask for a filename
858 if v.Buf.Path == "" {
861 v.saveToFile(v.Buf.Path)
865 return PostActionCall("Save", v)
870 // This function saves the buffer to `filename` and changes the buffer's path and name
871 // to `filename` if the save is successful
872 func (v *View) saveToFile(filename string) {
873 err := v.Buf.SaveAs(filename)
875 if strings.HasSuffix(err.Error(), "permission denied") {
876 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
878 err = v.Buf.SaveAsWithSudo(filename)
880 messenger.Error(err.Error())
882 v.Buf.Path = filename
883 v.Buf.name = filename
884 messenger.Message("Saved " + filename)
890 messenger.Error(err.Error())
893 v.Buf.Path = filename
894 v.Buf.name = filename
895 messenger.Message("Saved " + filename)
899 // SaveAs saves the buffer to disk with the given name
900 func (v *View) SaveAs(usePlugin bool) bool {
901 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
903 // the filename might or might not be quoted, so unquote first then join the strings.
904 filename = strings.Join(SplitCommandArgs(filename), " ")
905 v.saveToFile(filename)
911 // Find opens a prompt and searches forward for the input
912 func (v *View) Find(usePlugin bool) bool {
913 if usePlugin && !PreActionCall("Find", v) {
918 if v.Cursor.HasSelection() {
919 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
920 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
921 searchStr = v.Cursor.GetSelection()
923 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
925 BeginSearch(searchStr)
928 return PostActionCall("Find", v)
933 // FindNext searches forwards for the last used search term
934 func (v *View) FindNext(usePlugin bool) bool {
935 if usePlugin && !PreActionCall("FindNext", v) {
939 if v.Cursor.HasSelection() {
940 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
941 // lastSearch = v.Cursor.GetSelection()
943 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
945 if lastSearch == "" {
948 messenger.Message("Finding: " + lastSearch)
949 Search(lastSearch, v, true)
952 return PostActionCall("FindNext", v)
957 // FindPrevious searches backwards for the last used search term
958 func (v *View) FindPrevious(usePlugin bool) bool {
959 if usePlugin && !PreActionCall("FindPrevious", v) {
963 if v.Cursor.HasSelection() {
964 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
966 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
968 messenger.Message("Finding: " + lastSearch)
969 Search(lastSearch, v, false)
972 return PostActionCall("FindPrevious", v)
977 // Undo undoes the last action
978 func (v *View) Undo(usePlugin bool) bool {
979 if usePlugin && !PreActionCall("Undo", v) {
984 messenger.Message("Undid action")
987 return PostActionCall("Undo", v)
992 // Redo redoes the last action
993 func (v *View) Redo(usePlugin bool) bool {
994 if usePlugin && !PreActionCall("Redo", v) {
999 messenger.Message("Redid action")
1002 return PostActionCall("Redo", v)
1007 // Copy the selection to the system clipboard
1008 func (v *View) Copy(usePlugin bool) bool {
1009 if usePlugin && !PreActionCall("Copy", v) {
1013 if v.Cursor.HasSelection() {
1014 v.Cursor.CopySelection("clipboard")
1016 messenger.Message("Copied selection")
1020 return PostActionCall("Copy", v)
1025 // CutLine cuts the current line to the clipboard
1026 func (v *View) CutLine(usePlugin bool) bool {
1027 if usePlugin && !PreActionCall("CutLine", v) {
1031 v.Cursor.SelectLine()
1032 if !v.Cursor.HasSelection() {
1035 if v.freshClip == true {
1036 if v.Cursor.HasSelection() {
1037 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1038 messenger.Error(err)
1040 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1043 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1047 v.lastCutTime = time.Now()
1048 v.Cursor.DeleteSelection()
1049 v.Cursor.ResetSelection()
1050 messenger.Message("Cut line")
1053 return PostActionCall("CutLine", v)
1058 // Cut the selection to the system clipboard
1059 func (v *View) Cut(usePlugin bool) bool {
1060 if usePlugin && !PreActionCall("Cut", v) {
1064 if v.Cursor.HasSelection() {
1065 v.Cursor.CopySelection("clipboard")
1066 v.Cursor.DeleteSelection()
1067 v.Cursor.ResetSelection()
1069 messenger.Message("Cut selection")
1072 return PostActionCall("Cut", v)
1080 // DuplicateLine duplicates the current line or selection
1081 func (v *View) DuplicateLine(usePlugin bool) bool {
1082 if usePlugin && !PreActionCall("DuplicateLine", v) {
1086 if v.Cursor.HasSelection() {
1087 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1090 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1094 messenger.Message("Duplicated line")
1097 return PostActionCall("DuplicateLine", v)
1102 // DeleteLine deletes the current line
1103 func (v *View) DeleteLine(usePlugin bool) bool {
1104 if usePlugin && !PreActionCall("DeleteLine", v) {
1108 v.Cursor.SelectLine()
1109 if !v.Cursor.HasSelection() {
1112 v.Cursor.DeleteSelection()
1113 v.Cursor.ResetSelection()
1114 messenger.Message("Deleted line")
1117 return PostActionCall("DeleteLine", v)
1122 // MoveLinesUp moves up the current line or selected lines if any
1123 func (v *View) MoveLinesUp(usePlugin bool) bool {
1124 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1128 if v.Cursor.HasSelection() {
1129 if v.Cursor.CurSelection[0].Y == 0 {
1130 messenger.Message("Can not move further up")
1134 v.Cursor.CurSelection[0].Y,
1135 v.Cursor.CurSelection[1].Y,
1138 v.Cursor.CurSelection[0].Y -= 1
1139 v.Cursor.CurSelection[1].Y -= 1
1140 messenger.Message("Moved up selected line(s)")
1142 if v.Cursor.Loc.Y == 0 {
1143 messenger.Message("Can not move further up")
1151 messenger.Message("Moved up current line")
1153 v.Buf.IsModified = true
1156 return PostActionCall("MoveLinesUp", v)
1161 // MoveLinesDown moves down the current line or selected lines if any
1162 func (v *View) MoveLinesDown(usePlugin bool) bool {
1163 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1167 if v.Cursor.HasSelection() {
1168 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1169 messenger.Message("Can not move further down")
1172 v.Buf.MoveLinesDown(
1173 v.Cursor.CurSelection[0].Y,
1174 v.Cursor.CurSelection[1].Y,
1177 v.Cursor.CurSelection[0].Y += 1
1178 v.Cursor.CurSelection[1].Y += 1
1179 messenger.Message("Moved down selected line(s)")
1181 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1182 messenger.Message("Can not move further down")
1185 v.Buf.MoveLinesDown(
1190 messenger.Message("Moved down current line")
1192 v.Buf.IsModified = true
1195 return PostActionCall("MoveLinesDown", v)
1200 // Paste whatever is in the system clipboard into the buffer
1201 // Delete and paste if the user has a selection
1202 func (v *View) Paste(usePlugin bool) bool {
1203 if usePlugin && !PreActionCall("Paste", v) {
1207 clip, _ := clipboard.ReadAll("clipboard")
1211 return PostActionCall("Paste", v)
1216 // PastePrimary pastes from the primary clipboard (only use on linux)
1217 func (v *View) PastePrimary(usePlugin bool) bool {
1218 if usePlugin && !PreActionCall("Paste", v) {
1222 clip, _ := clipboard.ReadAll("primary")
1226 return PostActionCall("Paste", v)
1231 // SelectAll selects the entire buffer
1232 func (v *View) SelectAll(usePlugin bool) bool {
1233 if usePlugin && !PreActionCall("SelectAll", v) {
1237 v.Cursor.SetSelectionStart(v.Buf.Start())
1238 v.Cursor.SetSelectionEnd(v.Buf.End())
1239 // Put the cursor at the beginning
1244 return PostActionCall("SelectAll", v)
1249 // OpenFile opens a new file in the buffer
1250 func (v *View) OpenFile(usePlugin bool) bool {
1251 if usePlugin && !PreActionCall("OpenFile", v) {
1256 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1258 HandleCommand(input)
1260 return PostActionCall("OpenFile", v)
1267 // Start moves the viewport to the start of the buffer
1268 func (v *View) Start(usePlugin bool) bool {
1269 if usePlugin && !PreActionCall("Start", v) {
1276 return PostActionCall("Start", v)
1281 // End moves the viewport to the end of the buffer
1282 func (v *View) End(usePlugin bool) bool {
1283 if usePlugin && !PreActionCall("End", v) {
1287 if v.Height > v.Buf.NumLines {
1290 v.Topline = v.Buf.NumLines - v.Height
1294 return PostActionCall("End", v)
1299 // PageUp scrolls the view up a page
1300 func (v *View) PageUp(usePlugin bool) bool {
1301 if usePlugin && !PreActionCall("PageUp", v) {
1305 if v.Topline > v.Height {
1306 v.ScrollUp(v.Height)
1312 return PostActionCall("PageUp", v)
1317 // PageDown scrolls the view down a page
1318 func (v *View) PageDown(usePlugin bool) bool {
1319 if usePlugin && !PreActionCall("PageDown", v) {
1323 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1324 v.ScrollDown(v.Height)
1325 } else if v.Buf.NumLines >= v.Height {
1326 v.Topline = v.Buf.NumLines - v.Height
1330 return PostActionCall("PageDown", v)
1335 // CursorPageUp places the cursor a page up
1336 func (v *View) CursorPageUp(usePlugin bool) bool {
1337 if usePlugin && !PreActionCall("CursorPageUp", v) {
1343 if v.Cursor.HasSelection() {
1344 v.Cursor.Loc = v.Cursor.CurSelection[0]
1345 v.Cursor.ResetSelection()
1347 v.Cursor.UpN(v.Height)
1350 return PostActionCall("CursorPageUp", v)
1355 // CursorPageDown places the cursor a page up
1356 func (v *View) CursorPageDown(usePlugin bool) bool {
1357 if usePlugin && !PreActionCall("CursorPageDown", v) {
1363 if v.Cursor.HasSelection() {
1364 v.Cursor.Loc = v.Cursor.CurSelection[1]
1365 v.Cursor.ResetSelection()
1367 v.Cursor.DownN(v.Height)
1370 return PostActionCall("CursorPageDown", v)
1375 // HalfPageUp scrolls the view up half a page
1376 func (v *View) HalfPageUp(usePlugin bool) bool {
1377 if usePlugin && !PreActionCall("HalfPageUp", v) {
1381 if v.Topline > v.Height/2 {
1382 v.ScrollUp(v.Height / 2)
1388 return PostActionCall("HalfPageUp", v)
1393 // HalfPageDown scrolls the view down half a page
1394 func (v *View) HalfPageDown(usePlugin bool) bool {
1395 if usePlugin && !PreActionCall("HalfPageDown", v) {
1399 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1400 v.ScrollDown(v.Height / 2)
1402 if v.Buf.NumLines >= v.Height {
1403 v.Topline = v.Buf.NumLines - v.Height
1408 return PostActionCall("HalfPageDown", v)
1413 // ToggleRuler turns line numbers off and on
1414 func (v *View) ToggleRuler(usePlugin bool) bool {
1415 if usePlugin && !PreActionCall("ToggleRuler", v) {
1419 if v.Buf.Settings["ruler"] == false {
1420 v.Buf.Settings["ruler"] = true
1421 messenger.Message("Enabled ruler")
1423 v.Buf.Settings["ruler"] = false
1424 messenger.Message("Disabled ruler")
1428 return PostActionCall("ToggleRuler", v)
1433 // JumpLine jumps to a line and moves the view accordingly.
1434 func (v *View) JumpLine(usePlugin bool) bool {
1435 if usePlugin && !PreActionCall("JumpLine", v) {
1439 // Prompt for line number
1440 linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1444 lineint, err := strconv.Atoi(linestring)
1445 lineint = lineint - 1 // fix offset
1447 messenger.Error(err) // return errors
1450 // Move cursor and view if possible.
1451 if lineint < v.Buf.NumLines && lineint >= 0 {
1453 v.Cursor.Y = lineint
1456 return PostActionCall("JumpLine", v)
1460 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1464 // ClearStatus clears the messenger bar
1465 func (v *View) ClearStatus(usePlugin bool) bool {
1466 if usePlugin && !PreActionCall("ClearStatus", v) {
1470 messenger.Message("")
1473 return PostActionCall("ClearStatus", v)
1478 // ToggleHelp toggles the help screen
1479 func (v *View) ToggleHelp(usePlugin bool) bool {
1480 if usePlugin && !PreActionCall("ToggleHelp", v) {
1484 if v.Type != vtHelp {
1485 // Open the default help
1492 return PostActionCall("ToggleHelp", v)
1497 // ShellMode opens a terminal to run a shell command
1498 func (v *View) ShellMode(usePlugin bool) bool {
1499 if usePlugin && !PreActionCall("ShellMode", v) {
1503 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1505 // The true here is for openTerm to make the command interactive
1506 HandleShellCommand(input, true, true)
1508 return PostActionCall("ShellMode", v)
1514 // CommandMode lets the user enter a command
1515 func (v *View) CommandMode(usePlugin bool) bool {
1516 if usePlugin && !PreActionCall("CommandMode", v) {
1520 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1522 HandleCommand(input)
1524 return PostActionCall("CommandMode", v)
1531 // Escape leaves current mode
1532 func (v *View) Escape(usePlugin bool) bool {
1533 // check if user is searching, or the last search is still active
1534 if searching || lastSearch != "" {
1538 // check if a prompt is shown, hide it and don't quit
1539 if messenger.hasPrompt {
1540 messenger.Reset() // FIXME
1547 // Quit this will close the current tab or view that is open
1548 func (v *View) Quit(usePlugin bool) bool {
1549 if usePlugin && !PreActionCall("Quit", v) {
1553 // Make sure not to quit if there are unsaved changes
1556 if len(tabs[curTab].views) > 1 {
1557 v.splitNode.Delete()
1558 tabs[v.TabNum].Cleanup()
1559 tabs[v.TabNum].Resize()
1560 } else if len(tabs) > 1 {
1561 if len(tabs[v.TabNum].views) == 1 {
1562 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1563 for i, t := range tabs {
1566 if curTab >= len(tabs) {
1570 CurView().ToggleTabbar()
1575 PostActionCall("Quit", v)
1584 return PostActionCall("Quit", v)
1589 // QuitAll quits the whole editor; all splits and tabs
1590 func (v *View) QuitAll(usePlugin bool) bool {
1591 if usePlugin && !PreActionCall("QuitAll", v) {
1596 for _, tab := range tabs {
1597 for _, v := range tab.views {
1605 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1606 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1609 for _, tab := range tabs {
1610 for _, v := range tab.views {
1616 PostActionCall("QuitAll", v)
1627 // AddTab adds a new tab with an empty buffer
1628 func (v *View) AddTab(usePlugin bool) bool {
1629 if usePlugin && !PreActionCall("AddTab", v) {
1633 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1634 tab.SetNum(len(tabs))
1635 tabs = append(tabs, tab)
1636 curTab = len(tabs) - 1
1638 for _, t := range tabs {
1639 for _, v := range t.views {
1646 return PostActionCall("AddTab", v)
1651 // PreviousTab switches to the previous tab in the tab list
1652 func (v *View) PreviousTab(usePlugin bool) bool {
1653 if usePlugin && !PreActionCall("PreviousTab", v) {
1659 } else if curTab == 0 {
1660 curTab = len(tabs) - 1
1664 return PostActionCall("PreviousTab", v)
1669 // NextTab switches to the next tab in the tab list
1670 func (v *View) NextTab(usePlugin bool) bool {
1671 if usePlugin && !PreActionCall("NextTab", v) {
1675 if curTab < len(tabs)-1 {
1677 } else if curTab == len(tabs)-1 {
1682 return PostActionCall("NextTab", v)
1687 // VSplitBinding opens an empty vertical split
1688 func (v *View) VSplitBinding(usePlugin bool) bool {
1689 if usePlugin && !PreActionCall("VSplit", v) {
1693 v.VSplit(NewBufferFromString("", ""))
1696 return PostActionCall("VSplit", v)
1701 // HSplitBinding opens an empty horizontal split
1702 func (v *View) HSplitBinding(usePlugin bool) bool {
1703 if usePlugin && !PreActionCall("HSplit", v) {
1707 v.HSplit(NewBufferFromString("", ""))
1710 return PostActionCall("HSplit", v)
1715 // Unsplit closes all splits in the current tab except the active one
1716 func (v *View) Unsplit(usePlugin bool) bool {
1717 if usePlugin && !PreActionCall("Unsplit", v) {
1721 curView := tabs[curTab].CurView
1722 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1723 view := tabs[curTab].views[i]
1724 if view != nil && view.Num != curView {
1726 // messenger.Message("Quit ", view.Buf.Path)
1731 return PostActionCall("Unsplit", v)
1736 // NextSplit changes the view to the next split
1737 func (v *View) NextSplit(usePlugin bool) bool {
1738 if usePlugin && !PreActionCall("NextSplit", v) {
1743 if tab.CurView < len(tab.views)-1 {
1750 return PostActionCall("NextSplit", v)
1755 // PreviousSplit changes the view to the previous split
1756 func (v *View) PreviousSplit(usePlugin bool) bool {
1757 if usePlugin && !PreActionCall("PreviousSplit", v) {
1762 if tab.CurView > 0 {
1765 tab.CurView = len(tab.views) - 1
1769 return PostActionCall("PreviousSplit", v)
1774 var curMacro []interface{}
1775 var recordingMacro bool
1777 // ToggleMacro toggles recording of a macro
1778 func (v *View) ToggleMacro(usePlugin bool) bool {
1779 if usePlugin && !PreActionCall("ToggleMacro", v) {
1783 recordingMacro = !recordingMacro
1786 curMacro = []interface{}{}
1787 messenger.Message("Recording")
1789 messenger.Message("Stopped recording")
1793 return PostActionCall("ToggleMacro", v)
1798 // PlayMacro plays back the most recently recorded macro
1799 func (v *View) PlayMacro(usePlugin bool) bool {
1800 if usePlugin && !PreActionCall("PlayMacro", v) {
1804 for _, action := range curMacro {
1805 switch t := action.(type) {
1807 // Insert a character
1808 if v.Cursor.HasSelection() {
1809 v.Cursor.DeleteSelection()
1810 v.Cursor.ResetSelection()
1812 v.Buf.Insert(v.Cursor.Loc, string(t))
1815 for pl := range loadedPlugins {
1816 _, err := Call(pl+".onRune", string(t), v)
1817 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1821 case func(*View, bool) bool:
1827 return PostActionCall("PlayMacro", v)
1832 // SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
1833 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
1834 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
1835 // You can only spawn a cursor from the main cursor
1836 if v.Cursor == spawner {
1837 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
1841 if !spawner.HasSelection() {
1842 spawner.SelectWord()
1848 sel := spawner.GetSelection()
1850 searchStart = ToCharPos(spawner.CurSelection[1], v.Buf)
1852 Search(sel, v, true)
1854 for _, cur := range v.Buf.cursors {
1855 if c.Loc == cur.Loc {
1859 v.Buf.cursors = append(v.Buf.cursors, c)
1865 PostActionCall("SpawnMultiCursor", v)
1872 // SkipMultiCursor moves the current multiple cursor to the next available position
1873 func (v *View) SkipMultiCursor(usePlugin bool) bool {
1874 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
1876 if v.Cursor == cursor {
1877 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
1880 sel := cursor.GetSelection()
1882 searchStart = ToCharPos(cursor.CurSelection[1], v.Buf)
1884 Search(sel, v, true)
1889 PostActionCall("SkipMultiCursor", v)
1895 // RemoveMultiCursor removes the latest multiple cursor
1896 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
1897 end := len(v.Buf.cursors)
1899 nextOne := v.Buf.cursors[len(v.Buf.cursors)-2]
1900 if v.Cursor == nextOne {
1901 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
1906 v.Buf.cursors[end-1] = nil
1907 v.Buf.cursors = v.Buf.cursors[:end-1]
1912 return PostActionCall("RemoveMultiCursor", v)
1917 v.RemoveAllMultiCursors(usePlugin)
1922 // RemoveAllMultiCursors removes all cursors except the base cursor
1923 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
1924 if v.Cursor == &v.Buf.Cursor {
1925 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
1929 for i := 1; i < len(v.Buf.cursors); i++ {
1930 v.Buf.cursors[i] = nil
1932 v.Buf.cursors = v.Buf.cursors[:1]
1933 v.Cursor.ResetSelection()
1937 return PostActionCall("RemoveAllMultiCursors", v)