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 ws := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
578 v.Buf.Insert(v.Cursor.Loc, "\n")
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.Buf.Remove(loc.Move(-tabSize, v.Buf), loc)
628 v.Buf.Remove(loc.Move(-1, v.Buf), loc)
631 v.Cursor.LastVisualX = v.Cursor.GetVisualX()
634 return PostActionCall("Backspace", v)
639 // DeleteWordRight deletes the word to the right of the cursor
640 func (v *View) DeleteWordRight(usePlugin bool) bool {
641 if usePlugin && !PreActionCall("DeleteWordRight", v) {
645 v.SelectWordRight(false)
646 if v.Cursor.HasSelection() {
647 v.Cursor.DeleteSelection()
648 v.Cursor.ResetSelection()
652 return PostActionCall("DeleteWordRight", v)
657 // DeleteWordLeft deletes the word to the left of the cursor
658 func (v *View) DeleteWordLeft(usePlugin bool) bool {
659 if usePlugin && !PreActionCall("DeleteWordLeft", v) {
663 v.SelectWordLeft(false)
664 if v.Cursor.HasSelection() {
665 v.Cursor.DeleteSelection()
666 v.Cursor.ResetSelection()
670 return PostActionCall("DeleteWordLeft", v)
675 // Delete deletes the next character
676 func (v *View) Delete(usePlugin bool) bool {
677 if usePlugin && !PreActionCall("Delete", v) {
681 if v.Cursor.HasSelection() {
682 v.Cursor.DeleteSelection()
683 v.Cursor.ResetSelection()
686 if loc.LessThan(v.Buf.End()) {
687 v.Buf.Remove(loc, loc.Move(1, v.Buf))
692 return PostActionCall("Delete", v)
697 // IndentSelection indents the current selection
698 func (v *View) IndentSelection(usePlugin bool) bool {
699 if usePlugin && !PreActionCall("IndentSelection", v) {
703 if v.Cursor.HasSelection() {
704 start := v.Cursor.CurSelection[0]
705 end := v.Cursor.CurSelection[1]
707 start, end = end, start
711 endY := end.Move(-1, v.Buf).Y
712 endX := end.Move(-1, v.Buf).X
713 for y := startY; y <= endY; y++ {
714 tabsize := len(v.Buf.IndentString())
715 v.Buf.Insert(Loc{0, y}, v.Buf.IndentString())
716 if y == startY && start.X > 0 {
717 v.Cursor.SetSelectionStart(start.Move(tabsize, v.Buf))
720 v.Cursor.SetSelectionEnd(Loc{endX + tabsize + 1, endY})
726 return PostActionCall("IndentSelection", v)
733 // OutdentLine moves the current line back one indentation
734 func (v *View) OutdentLine(usePlugin bool) bool {
735 if usePlugin && !PreActionCall("OutdentLine", v) {
739 if v.Cursor.HasSelection() {
743 for x := 0; x < len(v.Buf.IndentString()); x++ {
744 if len(GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))) == 0 {
747 v.Buf.Remove(Loc{0, v.Cursor.Y}, Loc{1, v.Cursor.Y})
752 return PostActionCall("OutdentLine", v)
757 // OutdentSelection takes the current selection and moves it back one indent level
758 func (v *View) OutdentSelection(usePlugin bool) bool {
759 if usePlugin && !PreActionCall("OutdentSelection", v) {
763 if v.Cursor.HasSelection() {
764 start := v.Cursor.CurSelection[0]
765 end := v.Cursor.CurSelection[1]
767 start, end = end, start
771 endY := end.Move(-1, v.Buf).Y
772 endX := end.Move(-1, v.Buf).X
773 for y := startY; y <= endY; y++ {
774 for x := 0; x < len(v.Buf.IndentString()); x++ {
775 if len(GetLeadingWhitespace(v.Buf.Line(y))) == 0 {
778 v.Buf.Remove(Loc{0, y}, Loc{1, y})
779 if y == startY && start.X > 0 {
780 v.Cursor.SetSelectionStart(start.Move(-1, v.Buf))
783 v.Cursor.SetSelectionEnd(Loc{endX - x, endY})
790 return PostActionCall("OutdentSelection", v)
797 // InsertTab inserts a tab or spaces
798 func (v *View) InsertTab(usePlugin bool) bool {
799 if usePlugin && !PreActionCall("InsertTab", v) {
803 if v.Cursor.HasSelection() {
807 tabBytes := len(v.Buf.IndentString())
808 bytesUntilIndent := tabBytes - (v.Cursor.GetVisualX() % tabBytes)
809 v.Buf.Insert(v.Cursor.Loc, v.Buf.IndentString()[:bytesUntilIndent])
810 // for i := 0; i < bytesUntilIndent; i++ {
815 return PostActionCall("InsertTab", v)
820 // SaveAll saves all open buffers
821 func (v *View) SaveAll(usePlugin bool) bool {
822 if usePlugin && !PreActionCall("SaveAll", v) {
826 for _, t := range tabs {
827 for _, v := range t.views {
833 return PostActionCall("SaveAll", v)
838 // Save the buffer to disk
839 func (v *View) Save(usePlugin bool) bool {
840 if usePlugin && !PreActionCall("Save", v) {
844 if v.Type.scratch == true {
845 // We can't save any view type with scratch set. eg help and log text
848 // If this is an empty buffer, ask for a filename
849 if v.Buf.Path == "" {
852 v.saveToFile(v.Buf.Path)
856 return PostActionCall("Save", v)
861 // This function saves the buffer to `filename` and changes the buffer's path and name
862 // to `filename` if the save is successful
863 func (v *View) saveToFile(filename string) {
864 err := v.Buf.SaveAs(filename)
866 if strings.HasSuffix(err.Error(), "permission denied") {
867 choice, _ := messenger.YesNoPrompt("Permission denied. Do you want to save this file using sudo? (y,n)")
869 err = v.Buf.SaveAsWithSudo(filename)
871 messenger.Error(err.Error())
873 v.Buf.Path = filename
874 v.Buf.name = filename
875 messenger.Message("Saved " + filename)
881 messenger.Error(err.Error())
884 v.Buf.Path = filename
885 v.Buf.name = filename
886 messenger.Message("Saved " + filename)
890 // SaveAs saves the buffer to disk with the given name
891 func (v *View) SaveAs(usePlugin bool) bool {
892 filename, canceled := messenger.Prompt("Filename: ", "", "Save", NoCompletion)
894 // the filename might or might not be quoted, so unquote first then join the strings.
895 filename = strings.Join(SplitCommandArgs(filename), " ")
896 v.saveToFile(filename)
902 // Find opens a prompt and searches forward for the input
903 func (v *View) Find(usePlugin bool) bool {
904 if usePlugin && !PreActionCall("Find", v) {
909 if v.Cursor.HasSelection() {
910 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
911 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
912 searchStr = v.Cursor.GetSelection()
914 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
916 BeginSearch(searchStr)
919 return PostActionCall("Find", v)
924 // FindNext searches forwards for the last used search term
925 func (v *View) FindNext(usePlugin bool) bool {
926 if usePlugin && !PreActionCall("FindNext", v) {
930 if v.Cursor.HasSelection() {
931 searchStart = ToCharPos(v.Cursor.CurSelection[1], v.Buf)
932 // lastSearch = v.Cursor.GetSelection()
934 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
936 if lastSearch == "" {
939 messenger.Message("Finding: " + lastSearch)
940 Search(lastSearch, v, true)
943 return PostActionCall("FindNext", v)
948 // FindPrevious searches backwards for the last used search term
949 func (v *View) FindPrevious(usePlugin bool) bool {
950 if usePlugin && !PreActionCall("FindPrevious", v) {
954 if v.Cursor.HasSelection() {
955 searchStart = ToCharPos(v.Cursor.CurSelection[0], v.Buf)
957 searchStart = ToCharPos(v.Cursor.Loc, v.Buf)
959 messenger.Message("Finding: " + lastSearch)
960 Search(lastSearch, v, false)
963 return PostActionCall("FindPrevious", v)
968 // Undo undoes the last action
969 func (v *View) Undo(usePlugin bool) bool {
970 if usePlugin && !PreActionCall("Undo", v) {
975 messenger.Message("Undid action")
978 return PostActionCall("Undo", v)
983 // Redo redoes the last action
984 func (v *View) Redo(usePlugin bool) bool {
985 if usePlugin && !PreActionCall("Redo", v) {
990 messenger.Message("Redid action")
993 return PostActionCall("Redo", v)
998 // Copy the selection to the system clipboard
999 func (v *View) Copy(usePlugin bool) bool {
1000 if usePlugin && !PreActionCall("Copy", v) {
1004 if v.Cursor.HasSelection() {
1005 v.Cursor.CopySelection("clipboard")
1007 messenger.Message("Copied selection")
1011 return PostActionCall("Copy", v)
1016 // CutLine cuts the current line to the clipboard
1017 func (v *View) CutLine(usePlugin bool) bool {
1018 if usePlugin && !PreActionCall("CutLine", v) {
1022 v.Cursor.SelectLine()
1023 if !v.Cursor.HasSelection() {
1026 if v.freshClip == true {
1027 if v.Cursor.HasSelection() {
1028 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
1029 messenger.Error(err)
1031 clipboard.WriteAll(clip+v.Cursor.GetSelection(), "clipboard")
1034 } else if time.Since(v.lastCutTime)/time.Second > 10*time.Second || v.freshClip == false {
1038 v.lastCutTime = time.Now()
1039 v.Cursor.DeleteSelection()
1040 v.Cursor.ResetSelection()
1041 messenger.Message("Cut line")
1044 return PostActionCall("CutLine", v)
1049 // Cut the selection to the system clipboard
1050 func (v *View) Cut(usePlugin bool) bool {
1051 if usePlugin && !PreActionCall("Cut", v) {
1055 if v.Cursor.HasSelection() {
1056 v.Cursor.CopySelection("clipboard")
1057 v.Cursor.DeleteSelection()
1058 v.Cursor.ResetSelection()
1060 messenger.Message("Cut selection")
1063 return PostActionCall("Cut", v)
1071 // DuplicateLine duplicates the current line or selection
1072 func (v *View) DuplicateLine(usePlugin bool) bool {
1073 if usePlugin && !PreActionCall("DuplicateLine", v) {
1077 if v.Cursor.HasSelection() {
1078 v.Buf.Insert(v.Cursor.CurSelection[1], v.Cursor.GetSelection())
1081 v.Buf.Insert(v.Cursor.Loc, "\n"+v.Buf.Line(v.Cursor.Y))
1085 messenger.Message("Duplicated line")
1088 return PostActionCall("DuplicateLine", v)
1093 // DeleteLine deletes the current line
1094 func (v *View) DeleteLine(usePlugin bool) bool {
1095 if usePlugin && !PreActionCall("DeleteLine", v) {
1099 v.Cursor.SelectLine()
1100 if !v.Cursor.HasSelection() {
1103 v.Cursor.DeleteSelection()
1104 v.Cursor.ResetSelection()
1105 messenger.Message("Deleted line")
1108 return PostActionCall("DeleteLine", v)
1113 // MoveLinesUp moves up the current line or selected lines if any
1114 func (v *View) MoveLinesUp(usePlugin bool) bool {
1115 if usePlugin && !PreActionCall("MoveLinesUp", v) {
1119 if v.Cursor.HasSelection() {
1120 if v.Cursor.CurSelection[0].Y == 0 {
1121 messenger.Message("Can not move further up")
1125 v.Cursor.CurSelection[0].Y,
1126 v.Cursor.CurSelection[1].Y,
1129 v.Cursor.CurSelection[0].Y -= 1
1130 v.Cursor.CurSelection[1].Y -= 1
1131 messenger.Message("Moved up selected line(s)")
1133 if v.Cursor.Loc.Y == 0 {
1134 messenger.Message("Can not move further up")
1142 messenger.Message("Moved up current line")
1144 v.Buf.IsModified = true
1147 return PostActionCall("MoveLinesUp", v)
1152 // MoveLinesDown moves down the current line or selected lines if any
1153 func (v *View) MoveLinesDown(usePlugin bool) bool {
1154 if usePlugin && !PreActionCall("MoveLinesDown", v) {
1158 if v.Cursor.HasSelection() {
1159 if v.Cursor.CurSelection[1].Y >= len(v.Buf.lines) {
1160 messenger.Message("Can not move further down")
1163 v.Buf.MoveLinesDown(
1164 v.Cursor.CurSelection[0].Y,
1165 v.Cursor.CurSelection[1].Y,
1168 v.Cursor.CurSelection[0].Y += 1
1169 v.Cursor.CurSelection[1].Y += 1
1170 messenger.Message("Moved down selected line(s)")
1172 if v.Cursor.Loc.Y >= len(v.Buf.lines)-1 {
1173 messenger.Message("Can not move further down")
1176 v.Buf.MoveLinesDown(
1181 messenger.Message("Moved down current line")
1183 v.Buf.IsModified = true
1186 return PostActionCall("MoveLinesDown", v)
1191 // Paste whatever is in the system clipboard into the buffer
1192 // Delete and paste if the user has a selection
1193 func (v *View) Paste(usePlugin bool) bool {
1194 if usePlugin && !PreActionCall("Paste", v) {
1198 clip, _ := clipboard.ReadAll("clipboard")
1202 return PostActionCall("Paste", v)
1207 // PastePrimary pastes from the primary clipboard (only use on linux)
1208 func (v *View) PastePrimary(usePlugin bool) bool {
1209 if usePlugin && !PreActionCall("Paste", v) {
1213 clip, _ := clipboard.ReadAll("primary")
1217 return PostActionCall("Paste", v)
1222 // SelectAll selects the entire buffer
1223 func (v *View) SelectAll(usePlugin bool) bool {
1224 if usePlugin && !PreActionCall("SelectAll", v) {
1228 v.Cursor.SetSelectionStart(v.Buf.Start())
1229 v.Cursor.SetSelectionEnd(v.Buf.End())
1230 // Put the cursor at the beginning
1235 return PostActionCall("SelectAll", v)
1240 // OpenFile opens a new file in the buffer
1241 func (v *View) OpenFile(usePlugin bool) bool {
1242 if usePlugin && !PreActionCall("OpenFile", v) {
1247 input, canceled := messenger.Prompt("> ", "open ", "Open", CommandCompletion)
1249 HandleCommand(input)
1251 return PostActionCall("OpenFile", v)
1258 // Start moves the viewport to the start of the buffer
1259 func (v *View) Start(usePlugin bool) bool {
1260 if usePlugin && !PreActionCall("Start", v) {
1267 return PostActionCall("Start", v)
1272 // End moves the viewport to the end of the buffer
1273 func (v *View) End(usePlugin bool) bool {
1274 if usePlugin && !PreActionCall("End", v) {
1278 if v.Height > v.Buf.NumLines {
1281 v.Topline = v.Buf.NumLines - v.Height
1285 return PostActionCall("End", v)
1290 // PageUp scrolls the view up a page
1291 func (v *View) PageUp(usePlugin bool) bool {
1292 if usePlugin && !PreActionCall("PageUp", v) {
1296 if v.Topline > v.Height {
1297 v.ScrollUp(v.Height)
1303 return PostActionCall("PageUp", v)
1308 // PageDown scrolls the view down a page
1309 func (v *View) PageDown(usePlugin bool) bool {
1310 if usePlugin && !PreActionCall("PageDown", v) {
1314 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height {
1315 v.ScrollDown(v.Height)
1316 } else if v.Buf.NumLines >= v.Height {
1317 v.Topline = v.Buf.NumLines - v.Height
1321 return PostActionCall("PageDown", v)
1326 // CursorPageUp places the cursor a page up
1327 func (v *View) CursorPageUp(usePlugin bool) bool {
1328 if usePlugin && !PreActionCall("CursorPageUp", v) {
1334 if v.Cursor.HasSelection() {
1335 v.Cursor.Loc = v.Cursor.CurSelection[0]
1336 v.Cursor.ResetSelection()
1338 v.Cursor.UpN(v.Height)
1341 return PostActionCall("CursorPageUp", v)
1346 // CursorPageDown places the cursor a page up
1347 func (v *View) CursorPageDown(usePlugin bool) bool {
1348 if usePlugin && !PreActionCall("CursorPageDown", v) {
1354 if v.Cursor.HasSelection() {
1355 v.Cursor.Loc = v.Cursor.CurSelection[1]
1356 v.Cursor.ResetSelection()
1358 v.Cursor.DownN(v.Height)
1361 return PostActionCall("CursorPageDown", v)
1366 // HalfPageUp scrolls the view up half a page
1367 func (v *View) HalfPageUp(usePlugin bool) bool {
1368 if usePlugin && !PreActionCall("HalfPageUp", v) {
1372 if v.Topline > v.Height/2 {
1373 v.ScrollUp(v.Height / 2)
1379 return PostActionCall("HalfPageUp", v)
1384 // HalfPageDown scrolls the view down half a page
1385 func (v *View) HalfPageDown(usePlugin bool) bool {
1386 if usePlugin && !PreActionCall("HalfPageDown", v) {
1390 if v.Buf.NumLines-(v.Topline+v.Height) > v.Height/2 {
1391 v.ScrollDown(v.Height / 2)
1393 if v.Buf.NumLines >= v.Height {
1394 v.Topline = v.Buf.NumLines - v.Height
1399 return PostActionCall("HalfPageDown", v)
1404 // ToggleRuler turns line numbers off and on
1405 func (v *View) ToggleRuler(usePlugin bool) bool {
1406 if usePlugin && !PreActionCall("ToggleRuler", v) {
1410 if v.Buf.Settings["ruler"] == false {
1411 v.Buf.Settings["ruler"] = true
1412 messenger.Message("Enabled ruler")
1414 v.Buf.Settings["ruler"] = false
1415 messenger.Message("Disabled ruler")
1419 return PostActionCall("ToggleRuler", v)
1424 // JumpLine jumps to a line and moves the view accordingly.
1425 func (v *View) JumpLine(usePlugin bool) bool {
1426 if usePlugin && !PreActionCall("JumpLine", v) {
1430 // Prompt for line number
1431 linestring, canceled := messenger.Prompt("Jump to line # ", "", "LineNumber", NoCompletion)
1435 lineint, err := strconv.Atoi(linestring)
1436 lineint = lineint - 1 // fix offset
1438 messenger.Error(err) // return errors
1441 // Move cursor and view if possible.
1442 if lineint < v.Buf.NumLines && lineint >= 0 {
1444 v.Cursor.Y = lineint
1447 return PostActionCall("JumpLine", v)
1451 messenger.Error("Only ", v.Buf.NumLines, " lines to jump")
1455 // ClearStatus clears the messenger bar
1456 func (v *View) ClearStatus(usePlugin bool) bool {
1457 if usePlugin && !PreActionCall("ClearStatus", v) {
1461 messenger.Message("")
1464 return PostActionCall("ClearStatus", v)
1469 // ToggleHelp toggles the help screen
1470 func (v *View) ToggleHelp(usePlugin bool) bool {
1471 if usePlugin && !PreActionCall("ToggleHelp", v) {
1475 if v.Type != vtHelp {
1476 // Open the default help
1483 return PostActionCall("ToggleHelp", v)
1488 // ShellMode opens a terminal to run a shell command
1489 func (v *View) ShellMode(usePlugin bool) bool {
1490 if usePlugin && !PreActionCall("ShellMode", v) {
1494 input, canceled := messenger.Prompt("$ ", "", "Shell", NoCompletion)
1496 // The true here is for openTerm to make the command interactive
1497 HandleShellCommand(input, true, true)
1499 return PostActionCall("ShellMode", v)
1505 // CommandMode lets the user enter a command
1506 func (v *View) CommandMode(usePlugin bool) bool {
1507 if usePlugin && !PreActionCall("CommandMode", v) {
1511 input, canceled := messenger.Prompt("> ", "", "Command", CommandCompletion)
1513 HandleCommand(input)
1515 return PostActionCall("CommandMode", v)
1522 // Escape leaves current mode
1523 func (v *View) Escape(usePlugin bool) bool {
1524 // check if user is searching, or the last search is still active
1525 if searching || lastSearch != "" {
1529 // check if a prompt is shown, hide it and don't quit
1530 if messenger.hasPrompt {
1531 messenger.Reset() // FIXME
1538 // Quit this will close the current tab or view that is open
1539 func (v *View) Quit(usePlugin bool) bool {
1540 if usePlugin && !PreActionCall("Quit", v) {
1544 // Make sure not to quit if there are unsaved changes
1547 if len(tabs[curTab].views) > 1 {
1548 v.splitNode.Delete()
1549 tabs[v.TabNum].Cleanup()
1550 tabs[v.TabNum].Resize()
1551 } else if len(tabs) > 1 {
1552 if len(tabs[v.TabNum].views) == 1 {
1553 tabs = tabs[:v.TabNum+copy(tabs[v.TabNum:], tabs[v.TabNum+1:])]
1554 for i, t := range tabs {
1557 if curTab >= len(tabs) {
1561 CurView().ToggleTabbar()
1566 PostActionCall("Quit", v)
1575 return PostActionCall("Quit", v)
1580 // QuitAll quits the whole editor; all splits and tabs
1581 func (v *View) QuitAll(usePlugin bool) bool {
1582 if usePlugin && !PreActionCall("QuitAll", v) {
1587 for _, tab := range tabs {
1588 for _, v := range tab.views {
1596 // only quit if all of the buffers can be closed and the user confirms that they actually want to quit everything
1597 shouldQuit, _ := messenger.YesNoPrompt("Do you want to quit micro (all open files will be closed)?")
1600 for _, tab := range tabs {
1601 for _, v := range tab.views {
1607 PostActionCall("QuitAll", v)
1618 // AddTab adds a new tab with an empty buffer
1619 func (v *View) AddTab(usePlugin bool) bool {
1620 if usePlugin && !PreActionCall("AddTab", v) {
1624 tab := NewTabFromView(NewView(NewBufferFromString("", "")))
1625 tab.SetNum(len(tabs))
1626 tabs = append(tabs, tab)
1627 curTab = len(tabs) - 1
1629 for _, t := range tabs {
1630 for _, v := range t.views {
1637 return PostActionCall("AddTab", v)
1642 // PreviousTab switches to the previous tab in the tab list
1643 func (v *View) PreviousTab(usePlugin bool) bool {
1644 if usePlugin && !PreActionCall("PreviousTab", v) {
1650 } else if curTab == 0 {
1651 curTab = len(tabs) - 1
1655 return PostActionCall("PreviousTab", v)
1660 // NextTab switches to the next tab in the tab list
1661 func (v *View) NextTab(usePlugin bool) bool {
1662 if usePlugin && !PreActionCall("NextTab", v) {
1666 if curTab < len(tabs)-1 {
1668 } else if curTab == len(tabs)-1 {
1673 return PostActionCall("NextTab", v)
1678 // VSplitBinding opens an empty vertical split
1679 func (v *View) VSplitBinding(usePlugin bool) bool {
1680 if usePlugin && !PreActionCall("VSplit", v) {
1684 v.VSplit(NewBufferFromString("", ""))
1687 return PostActionCall("VSplit", v)
1692 // HSplitBinding opens an empty horizontal split
1693 func (v *View) HSplitBinding(usePlugin bool) bool {
1694 if usePlugin && !PreActionCall("HSplit", v) {
1698 v.HSplit(NewBufferFromString("", ""))
1701 return PostActionCall("HSplit", v)
1706 // Unsplit closes all splits in the current tab except the active one
1707 func (v *View) Unsplit(usePlugin bool) bool {
1708 if usePlugin && !PreActionCall("Unsplit", v) {
1712 curView := tabs[curTab].CurView
1713 for i := len(tabs[curTab].views) - 1; i >= 0; i-- {
1714 view := tabs[curTab].views[i]
1715 if view != nil && view.Num != curView {
1717 // messenger.Message("Quit ", view.Buf.Path)
1722 return PostActionCall("Unsplit", v)
1727 // NextSplit changes the view to the next split
1728 func (v *View) NextSplit(usePlugin bool) bool {
1729 if usePlugin && !PreActionCall("NextSplit", v) {
1734 if tab.CurView < len(tab.views)-1 {
1741 return PostActionCall("NextSplit", v)
1746 // PreviousSplit changes the view to the previous split
1747 func (v *View) PreviousSplit(usePlugin bool) bool {
1748 if usePlugin && !PreActionCall("PreviousSplit", v) {
1753 if tab.CurView > 0 {
1756 tab.CurView = len(tab.views) - 1
1760 return PostActionCall("PreviousSplit", v)
1765 var curMacro []interface{}
1766 var recordingMacro bool
1768 // ToggleMacro toggles recording of a macro
1769 func (v *View) ToggleMacro(usePlugin bool) bool {
1770 if usePlugin && !PreActionCall("ToggleMacro", v) {
1774 recordingMacro = !recordingMacro
1777 curMacro = []interface{}{}
1778 messenger.Message("Recording")
1780 messenger.Message("Stopped recording")
1784 return PostActionCall("ToggleMacro", v)
1789 // PlayMacro plays back the most recently recorded macro
1790 func (v *View) PlayMacro(usePlugin bool) bool {
1791 if usePlugin && !PreActionCall("PlayMacro", v) {
1795 for _, action := range curMacro {
1796 switch t := action.(type) {
1798 // Insert a character
1799 if v.Cursor.HasSelection() {
1800 v.Cursor.DeleteSelection()
1801 v.Cursor.ResetSelection()
1803 v.Buf.Insert(v.Cursor.Loc, string(t))
1806 for pl := range loadedPlugins {
1807 _, err := Call(pl+".onRune", string(t), v)
1808 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
1812 case func(*View, bool) bool:
1818 return PostActionCall("PlayMacro", v)
1823 // SpawnMultiCursor creates a new multiple cursor at the next occurence of the current selection or current word
1824 func (v *View) SpawnMultiCursor(usePlugin bool) bool {
1825 spawner := v.Buf.cursors[len(v.Buf.cursors)-1]
1826 // You can only spawn a cursor from the main cursor
1827 if v.Cursor == spawner {
1828 if usePlugin && !PreActionCall("SpawnMultiCursor", v) {
1832 if !spawner.HasSelection() {
1833 spawner.SelectWord()
1839 sel := spawner.GetSelection()
1841 searchStart = ToCharPos(spawner.CurSelection[1], v.Buf)
1843 Search(sel, v, true)
1845 for _, cur := range v.Buf.cursors {
1846 if c.Loc == cur.Loc {
1850 v.Buf.cursors = append(v.Buf.cursors, c)
1851 v.Buf.UpdateCursors()
1857 PostActionCall("SpawnMultiCursor", v)
1864 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1865 func (v *View) MouseMultiCursor(usePlugin bool, e *tcell.EventMouse) bool {
1866 if v.Cursor == &v.Buf.Cursor {
1867 if usePlugin && !PreActionCall("SpawnMultiCursorAtMouse", v, e) {
1870 x, y := e.Position()
1871 x -= v.lineNumOffset - v.leftCol + v.x
1872 y += v.Topline - v.y
1878 v.MoveToMouseClick(x, y)
1880 v.Cursor = &v.Buf.Cursor
1882 v.Buf.cursors = append(v.Buf.cursors, c)
1883 v.Buf.UpdateCursors()
1886 PostActionCall("SpawnMultiCursorAtMouse", v)
1892 // SkipMultiCursor moves the current multiple cursor to the next available position
1893 func (v *View) SkipMultiCursor(usePlugin bool) bool {
1894 cursor := v.Buf.cursors[len(v.Buf.cursors)-1]
1896 if v.Cursor == cursor {
1897 if usePlugin && !PreActionCall("SkipMultiCursor", v) {
1900 sel := cursor.GetSelection()
1902 searchStart = ToCharPos(cursor.CurSelection[1], v.Buf)
1904 Search(sel, v, true)
1909 PostActionCall("SkipMultiCursor", v)
1915 // RemoveMultiCursor removes the latest multiple cursor
1916 func (v *View) RemoveMultiCursor(usePlugin bool) bool {
1917 end := len(v.Buf.cursors)
1919 nextOne := v.Buf.cursors[len(v.Buf.cursors)-2]
1920 if v.Cursor == nextOne {
1921 if usePlugin && !PreActionCall("RemoveMultiCursor", v) {
1926 v.Buf.cursors[end-1] = nil
1927 v.Buf.cursors = v.Buf.cursors[:end-1]
1928 v.Buf.UpdateCursors()
1933 return PostActionCall("RemoveMultiCursor", v)
1938 v.RemoveAllMultiCursors(usePlugin)
1943 // RemoveAllMultiCursors removes all cursors except the base cursor
1944 func (v *View) RemoveAllMultiCursors(usePlugin bool) bool {
1945 if v.Cursor == &v.Buf.Cursor {
1946 if usePlugin && !PreActionCall("RemoveAllMultiCursors", v) {
1950 for i := 1; i < len(v.Buf.cursors); i++ {
1951 v.Buf.cursors[i] = nil
1953 v.Buf.cursors = v.Buf.cursors[:1]
1954 v.Buf.UpdateCursors()
1955 v.Cursor.ResetSelection()
1959 return PostActionCall("RemoveAllMultiCursors", v)