9 shellquote "github.com/kballard/go-shellquote"
10 "github.com/zyedidia/micro/v2/internal/buffer"
11 "github.com/zyedidia/micro/v2/internal/clipboard"
12 "github.com/zyedidia/micro/v2/internal/config"
13 "github.com/zyedidia/micro/v2/internal/screen"
14 "github.com/zyedidia/micro/v2/internal/shell"
15 "github.com/zyedidia/micro/v2/internal/util"
16 "github.com/zyedidia/tcell"
19 // ScrollUp is not an action
20 func (h *BufPane) ScrollUp(n int) {
30 // ScrollDown is not an action
31 func (h *BufPane) ScrollDown(n int) {
33 if v.StartLine <= h.Buf.LinesNum()-1-n {
39 // MousePress is the event that should happen when a normal click happens
40 // This is almost always bound to left click
41 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
43 mx, my := e.Position()
44 mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
45 h.Cursor.Loc = mouseLoc
47 if b.NumCursors() > 1 {
50 h.Cursor = h.Buf.GetActiveCursor()
51 h.Cursor.Loc = mouseLoc
53 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
56 h.lastClickTime = time.Now()
62 h.Cursor.CopySelection(clipboard.PrimaryReg)
65 h.lastClickTime = time.Now()
71 h.Cursor.CopySelection(clipboard.PrimaryReg)
76 h.lastClickTime = time.Now()
78 h.Cursor.OrigSelection[0] = h.Cursor.Loc
79 h.Cursor.CurSelection[0] = h.Cursor.Loc
80 h.Cursor.CurSelection[1] = h.Cursor.Loc
82 h.mouseReleased = false
83 } else if !h.mouseReleased {
85 h.Cursor.AddLineToSelection()
86 } else if h.doubleClick {
87 h.Cursor.AddWordToSelection()
89 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
93 h.Cursor.StoreVisualX()
98 // ScrollUpAction scrolls the view up
99 func (h *BufPane) ScrollUpAction() bool {
100 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
104 // ScrollDownAction scrolls the view up
105 func (h *BufPane) ScrollDownAction() bool {
106 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
110 // Center centers the view on the cursor
111 func (h *BufPane) Center() bool {
113 v.StartLine = h.Cursor.Y - v.Height/2
114 if v.StartLine+v.Height > h.Buf.LinesNum() {
115 v.StartLine = h.Buf.LinesNum() - v.Height
125 // CursorUp moves the cursor up
126 func (h *BufPane) CursorUp() bool {
127 h.Cursor.Deselect(true)
133 // CursorDown moves the cursor down
134 func (h *BufPane) CursorDown() bool {
135 h.Cursor.Deselect(true)
141 // CursorLeft moves the cursor left
142 func (h *BufPane) CursorLeft() bool {
143 if h.Cursor.HasSelection() {
144 h.Cursor.Deselect(true)
146 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
147 tabmovement := h.Buf.Settings["tabmovement"].(bool)
148 if tabstospaces && tabmovement {
149 tabsize := int(h.Buf.Settings["tabsize"].(float64))
150 line := h.Buf.LineBytes(h.Cursor.Y)
151 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
152 for i := 0; i < tabsize; i++ {
166 // CursorRight moves the cursor right
167 func (h *BufPane) CursorRight() bool {
168 if h.Cursor.HasSelection() {
169 h.Cursor.Deselect(false)
170 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
172 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
173 tabmovement := h.Buf.Settings["tabmovement"].(bool)
174 if tabstospaces && tabmovement {
175 tabsize := int(h.Buf.Settings["tabsize"].(float64))
176 line := h.Buf.LineBytes(h.Cursor.Y)
177 if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) {
178 for i := 0; i < tabsize; i++ {
193 // WordRight moves the cursor one word to the right
194 func (h *BufPane) WordRight() bool {
195 h.Cursor.Deselect(false)
201 // WordLeft moves the cursor one word to the left
202 func (h *BufPane) WordLeft() bool {
203 h.Cursor.Deselect(true)
209 // SelectUp selects up one line
210 func (h *BufPane) SelectUp() bool {
211 if !h.Cursor.HasSelection() {
212 h.Cursor.OrigSelection[0] = h.Cursor.Loc
215 h.Cursor.SelectTo(h.Cursor.Loc)
220 // SelectDown selects down one line
221 func (h *BufPane) SelectDown() bool {
222 if !h.Cursor.HasSelection() {
223 h.Cursor.OrigSelection[0] = h.Cursor.Loc
226 h.Cursor.SelectTo(h.Cursor.Loc)
231 // SelectLeft selects the character to the left of the cursor
232 func (h *BufPane) SelectLeft() bool {
235 if loc.GreaterThan(count) {
238 if !h.Cursor.HasSelection() {
239 h.Cursor.OrigSelection[0] = loc
242 h.Cursor.SelectTo(h.Cursor.Loc)
247 // SelectRight selects the character to the right of the cursor
248 func (h *BufPane) SelectRight() bool {
251 if loc.GreaterThan(count) {
254 if !h.Cursor.HasSelection() {
255 h.Cursor.OrigSelection[0] = loc
258 h.Cursor.SelectTo(h.Cursor.Loc)
263 // SelectWordRight selects the word to the right of the cursor
264 func (h *BufPane) SelectWordRight() bool {
265 if !h.Cursor.HasSelection() {
266 h.Cursor.OrigSelection[0] = h.Cursor.Loc
269 h.Cursor.SelectTo(h.Cursor.Loc)
274 // SelectWordLeft selects the word to the left of the cursor
275 func (h *BufPane) SelectWordLeft() bool {
276 if !h.Cursor.HasSelection() {
277 h.Cursor.OrigSelection[0] = h.Cursor.Loc
280 h.Cursor.SelectTo(h.Cursor.Loc)
285 // StartOfText moves the cursor to the start of the text of the line
286 func (h *BufPane) StartOfText() bool {
287 h.Cursor.Deselect(true)
288 h.Cursor.StartOfText()
293 // StartOfTextToggle toggles the cursor between the start of the text of the line
294 // and the start of the line
295 func (h *BufPane) StartOfTextToggle() bool {
296 h.Cursor.Deselect(true)
297 if h.Cursor.IsStartOfText() {
300 h.Cursor.StartOfText()
306 // StartOfLine moves the cursor to the start of the line
307 func (h *BufPane) StartOfLine() bool {
308 h.Cursor.Deselect(true)
314 // EndOfLine moves the cursor to the end of the line
315 func (h *BufPane) EndOfLine() bool {
316 h.Cursor.Deselect(true)
322 // SelectLine selects the entire current line
323 func (h *BufPane) SelectLine() bool {
324 h.Cursor.SelectLine()
329 // SelectToStartOfText selects to the start of the text on the current line
330 func (h *BufPane) SelectToStartOfText() bool {
331 if !h.Cursor.HasSelection() {
332 h.Cursor.OrigSelection[0] = h.Cursor.Loc
334 h.Cursor.StartOfText()
335 h.Cursor.SelectTo(h.Cursor.Loc)
340 // SelectToStartOfTextToggle toggles the selection between the start of the text
341 // on the current line and the start of the line
342 func (h *BufPane) SelectToStartOfTextToggle() bool {
343 if !h.Cursor.HasSelection() {
344 h.Cursor.OrigSelection[0] = h.Cursor.Loc
346 if h.Cursor.IsStartOfText() {
349 h.Cursor.StartOfText()
351 h.Cursor.SelectTo(h.Cursor.Loc)
356 // SelectToStartOfLine selects to the start of the current line
357 func (h *BufPane) SelectToStartOfLine() bool {
358 if !h.Cursor.HasSelection() {
359 h.Cursor.OrigSelection[0] = h.Cursor.Loc
362 h.Cursor.SelectTo(h.Cursor.Loc)
367 // SelectToEndOfLine selects to the end of the current line
368 func (h *BufPane) SelectToEndOfLine() bool {
369 if !h.Cursor.HasSelection() {
370 h.Cursor.OrigSelection[0] = h.Cursor.Loc
373 h.Cursor.SelectTo(h.Cursor.Loc)
378 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
379 func (h *BufPane) ParagraphPrevious() bool {
381 for line = h.Cursor.Y; line > 0; line-- {
382 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
388 // If no empty line found. move cursor to end of buffer
390 h.Cursor.Loc = h.Buf.Start()
396 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
397 func (h *BufPane) ParagraphNext() bool {
399 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
400 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
406 // If no empty line found. move cursor to end of buffer
407 if line == h.Buf.LinesNum() {
408 h.Cursor.Loc = h.Buf.End()
414 // Retab changes all tabs to spaces or all spaces to tabs depending
415 // on the user's settings
416 func (h *BufPane) Retab() bool {
422 // CursorStart moves the cursor to the start of the buffer
423 func (h *BufPane) CursorStart() bool {
424 h.Cursor.Deselect(true)
427 h.Cursor.StoreVisualX()
432 // CursorEnd moves the cursor to the end of the buffer
433 func (h *BufPane) CursorEnd() bool {
434 h.Cursor.Deselect(true)
435 h.Cursor.Loc = h.Buf.End()
436 h.Cursor.StoreVisualX()
441 // SelectToStart selects the text from the cursor to the start of the buffer
442 func (h *BufPane) SelectToStart() bool {
443 if !h.Cursor.HasSelection() {
444 h.Cursor.OrigSelection[0] = h.Cursor.Loc
447 h.Cursor.SelectTo(h.Buf.Start())
452 // SelectToEnd selects the text from the cursor to the end of the buffer
453 func (h *BufPane) SelectToEnd() bool {
454 if !h.Cursor.HasSelection() {
455 h.Cursor.OrigSelection[0] = h.Cursor.Loc
458 h.Cursor.SelectTo(h.Buf.End())
463 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
464 func (h *BufPane) InsertNewline() bool {
466 if h.Cursor.HasSelection() {
467 h.Cursor.DeleteSelection()
468 h.Cursor.ResetSelection()
471 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
473 h.Buf.Insert(h.Cursor.Loc, "\n")
476 if h.Buf.Settings["autoindent"].(bool) {
480 h.Buf.Insert(h.Cursor.Loc, string(ws))
481 // for i := 0; i < len(ws); i++ {
485 // Remove the whitespaces if keepautoindent setting is off
486 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
487 line := h.Buf.LineBytes(h.Cursor.Y - 1)
488 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
491 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
496 // Backspace deletes the previous character
497 func (h *BufPane) Backspace() bool {
498 if h.Cursor.HasSelection() {
499 h.Cursor.DeleteSelection()
500 h.Cursor.ResetSelection()
501 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
502 // We have to do something a bit hacky here because we want to
503 // delete the line by first moving left and then deleting backwards
504 // but the undo redo would place the cursor in the wrong place
505 // So instead we move left, save the position, move back, delete
506 // and restore the position
508 // If the user is using spaces instead of tabs and they are deleting
509 // whitespace at the start of the line, we should delete as if it's a
510 // tab (tabSize number of spaces)
511 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
512 tabSize := int(h.Buf.Settings["tabsize"].(float64))
513 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
515 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
518 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
521 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
526 // DeleteWordRight deletes the word to the right of the cursor
527 func (h *BufPane) DeleteWordRight() bool {
529 if h.Cursor.HasSelection() {
530 h.Cursor.DeleteSelection()
531 h.Cursor.ResetSelection()
537 // DeleteWordLeft deletes the word to the left of the cursor
538 func (h *BufPane) DeleteWordLeft() bool {
540 if h.Cursor.HasSelection() {
541 h.Cursor.DeleteSelection()
542 h.Cursor.ResetSelection()
548 // Delete deletes the next character
549 func (h *BufPane) Delete() bool {
550 if h.Cursor.HasSelection() {
551 h.Cursor.DeleteSelection()
552 h.Cursor.ResetSelection()
555 if loc.LessThan(h.Buf.End()) {
556 h.Buf.Remove(loc, loc.Move(1, h.Buf))
563 // IndentSelection indents the current selection
564 func (h *BufPane) IndentSelection() bool {
565 if h.Cursor.HasSelection() {
566 start := h.Cursor.CurSelection[0]
567 end := h.Cursor.CurSelection[1]
569 start, end = end, start
570 h.Cursor.SetSelectionStart(start)
571 h.Cursor.SetSelectionEnd(end)
575 endY := end.Move(-1, h.Buf).Y
576 endX := end.Move(-1, h.Buf).X
577 tabsize := int(h.Buf.Settings["tabsize"].(float64))
578 indentsize := len(h.Buf.IndentString(tabsize))
579 for y := startY; y <= endY; y++ {
580 if len(h.Buf.LineBytes(y)) > 0 {
581 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
582 if y == startY && start.X > 0 {
583 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
586 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
590 h.Buf.RelocateCursors()
598 // IndentLine moves the current line forward one indentation
599 func (h *BufPane) IndentLine() bool {
600 if h.Cursor.HasSelection() {
604 tabsize := int(h.Buf.Settings["tabsize"].(float64))
605 indentstr := h.Buf.IndentString(tabsize)
606 h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
607 h.Buf.RelocateCursors()
612 // OutdentLine moves the current line back one indentation
613 func (h *BufPane) OutdentLine() bool {
614 if h.Cursor.HasSelection() {
618 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
619 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
622 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
624 h.Buf.RelocateCursors()
629 // OutdentSelection takes the current selection and moves it back one indent level
630 func (h *BufPane) OutdentSelection() bool {
631 if h.Cursor.HasSelection() {
632 start := h.Cursor.CurSelection[0]
633 end := h.Cursor.CurSelection[1]
635 start, end = end, start
636 h.Cursor.SetSelectionStart(start)
637 h.Cursor.SetSelectionEnd(end)
641 endY := end.Move(-1, h.Buf).Y
642 for y := startY; y <= endY; y++ {
643 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
644 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
647 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
650 h.Buf.RelocateCursors()
658 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
659 func (h *BufPane) Autocomplete() bool {
662 if h.Cursor.HasSelection() {
669 r := h.Cursor.RuneUnder(h.Cursor.X)
670 prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
671 if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
672 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
676 if b.HasSuggestions {
677 b.CycleAutocomplete(true)
680 return b.Autocomplete(buffer.BufferComplete)
683 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
684 func (h *BufPane) CycleAutocompleteBack() bool {
685 if h.Cursor.HasSelection() {
689 if h.Buf.HasSuggestions {
690 h.Buf.CycleAutocomplete(false)
696 // InsertTab inserts a tab or spaces
697 func (h *BufPane) InsertTab() bool {
699 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
700 tabBytes := len(indent)
701 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
702 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
707 // SaveAll saves all open buffers
708 func (h *BufPane) SaveAll() bool {
709 for _, b := range buffer.OpenBuffers {
715 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
716 func (h *BufPane) SaveCB(action string, callback func()) bool {
717 // If this is an empty buffer, ask for a filename
718 if h.Buf.Path == "" {
719 h.SaveAsCB(action, callback)
721 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
729 // Save the buffer to disk
730 func (h *BufPane) Save() bool {
731 return h.SaveCB("Save", nil)
734 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
735 // The callback is only called if the save was successful
736 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
737 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
739 // the filename might or might not be quoted, so unquote first then join the strings.
740 args, err := shellquote.Split(resp)
742 InfoBar.Error("Error parsing arguments: ", err)
746 InfoBar.Error("No filename given")
749 filename := strings.Join(args, " ")
750 noPrompt := h.saveBufToFile(filename, action, callback)
752 h.completeAction(action)
759 // SaveAs saves the buffer to disk with the given name
760 func (h *BufPane) SaveAs() bool {
761 return h.SaveAsCB("SaveAs", nil)
764 // This function saves the buffer to `filename` and changes the buffer's path and name
765 // to `filename` if the save is successful
766 // The callback is only called if the save was successful
767 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
768 err := h.Buf.SaveAs(filename)
770 if strings.HasSuffix(err.Error(), "permission denied") {
771 saveWithSudo := func() {
772 err = h.Buf.SaveAsWithSudo(filename)
776 h.Buf.Path = filename
777 h.Buf.SetName(filename)
778 InfoBar.Message("Saved " + filename)
784 if h.Buf.Settings["autosu"].(bool) {
787 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
788 if yes && !canceled {
790 h.completeAction(action)
799 h.Buf.Path = filename
800 h.Buf.SetName(filename)
801 InfoBar.Message("Saved " + filename)
809 // Find opens a prompt and searches forward for the input
810 func (h *BufPane) Find() bool {
814 // FindLiteral is the same as Find() but does not support regular expressions
815 func (h *BufPane) FindLiteral() bool {
819 // Search searches for a given string/regex in the buffer and selects the next
820 // match if a match is found
821 // This function affects lastSearch and lastSearchRegex (saved searches) for
822 // use with FindNext and FindPrevious
823 func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
824 match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
829 h.Cursor.SetSelectionStart(match[0])
830 h.Cursor.SetSelectionEnd(match[1])
831 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
832 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
833 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
835 h.lastSearchRegex = useRegex
838 h.Cursor.ResetSelection()
843 func (h *BufPane) find(useRegex bool) bool {
844 h.searchOrig = h.Cursor.Loc
847 prompt = "Find (regex): "
849 InfoBar.Prompt(prompt, "", "Find", func(resp string) {
851 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
853 h.Cursor.SetSelectionStart(match[0])
854 h.Cursor.SetSelectionEnd(match[1])
855 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
856 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
857 h.Cursor.GotoLoc(match[1])
859 h.Cursor.GotoLoc(h.searchOrig)
860 h.Cursor.ResetSelection()
863 }, func(resp string, canceled bool) {
866 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
871 h.Cursor.SetSelectionStart(match[0])
872 h.Cursor.SetSelectionEnd(match[1])
873 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
874 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
875 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
877 h.lastSearchRegex = useRegex
879 h.Cursor.ResetSelection()
880 InfoBar.Message("No matches found")
883 h.Cursor.ResetSelection()
891 // FindNext searches forwards for the last used search term
892 func (h *BufPane) FindNext() bool {
893 // If the cursor is at the start of a selection and we search we want
894 // to search from the end of the selection in the case that
895 // the selection is a search result in which case we wouldn't move at
896 // at all which would be bad
897 searchLoc := h.Cursor.Loc
898 if h.Cursor.HasSelection() {
899 searchLoc = h.Cursor.CurSelection[1]
901 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
906 h.Cursor.SetSelectionStart(match[0])
907 h.Cursor.SetSelectionEnd(match[1])
908 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
909 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
910 h.Cursor.Loc = h.Cursor.CurSelection[1]
912 h.Cursor.ResetSelection()
918 // FindPrevious searches backwards for the last used search term
919 func (h *BufPane) FindPrevious() bool {
920 // If the cursor is at the end of a selection and we search we want
921 // to search from the beginning of the selection in the case that
922 // the selection is a search result in which case we wouldn't move at
923 // at all which would be bad
924 searchLoc := h.Cursor.Loc
925 if h.Cursor.HasSelection() {
926 searchLoc = h.Cursor.CurSelection[0]
928 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
933 h.Cursor.SetSelectionStart(match[0])
934 h.Cursor.SetSelectionEnd(match[1])
935 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
936 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
937 h.Cursor.Loc = h.Cursor.CurSelection[1]
939 h.Cursor.ResetSelection()
945 // Undo undoes the last action
946 func (h *BufPane) Undo() bool {
948 InfoBar.Message("Undid action")
953 // Redo redoes the last action
954 func (h *BufPane) Redo() bool {
956 InfoBar.Message("Redid action")
961 // Copy the selection to the system clipboard
962 func (h *BufPane) Copy() bool {
963 if h.Cursor.HasSelection() {
964 h.Cursor.CopySelection(clipboard.ClipboardReg)
966 InfoBar.Message("Copied selection")
972 // Copy the current line to the clipboard
973 func (h *BufPane) CopyLine() bool {
974 if h.Cursor.HasSelection() {
977 h.Cursor.SelectLine()
978 h.Cursor.CopySelection(clipboard.ClipboardReg)
980 InfoBar.Message("Copied line")
982 h.Cursor.Deselect(true)
987 // CutLine cuts the current line to the clipboard
988 func (h *BufPane) CutLine() bool {
989 h.Cursor.SelectLine()
990 if !h.Cursor.HasSelection() {
993 if h.freshClip == true {
994 if h.Cursor.HasSelection() {
995 if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
998 clipboard.Write(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg)
1001 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
1005 h.lastCutTime = time.Now()
1006 h.Cursor.DeleteSelection()
1007 h.Cursor.ResetSelection()
1008 InfoBar.Message("Cut line")
1013 // Cut the selection to the system clipboard
1014 func (h *BufPane) Cut() bool {
1015 if h.Cursor.HasSelection() {
1016 h.Cursor.CopySelection(clipboard.ClipboardReg)
1017 h.Cursor.DeleteSelection()
1018 h.Cursor.ResetSelection()
1020 InfoBar.Message("Cut selection")
1029 // DuplicateLine duplicates the current line or selection
1030 func (h *BufPane) DuplicateLine() bool {
1031 if h.Cursor.HasSelection() {
1032 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1035 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1039 InfoBar.Message("Duplicated line")
1044 // DeleteLine deletes the current line
1045 func (h *BufPane) DeleteLine() bool {
1046 h.Cursor.SelectLine()
1047 if !h.Cursor.HasSelection() {
1050 h.Cursor.DeleteSelection()
1051 h.Cursor.ResetSelection()
1052 InfoBar.Message("Deleted line")
1057 // MoveLinesUp moves up the current line or selected lines if any
1058 func (h *BufPane) MoveLinesUp() bool {
1059 if h.Cursor.HasSelection() {
1060 if h.Cursor.CurSelection[0].Y == 0 {
1061 InfoBar.Message("Cannot move further up")
1064 start := h.Cursor.CurSelection[0].Y
1065 end := h.Cursor.CurSelection[1].Y
1068 end, start = start, end
1073 if h.Cursor.CurSelection[sel].X != 0 {
1084 h.Cursor.CurSelection[sel].Y -= 1
1087 if h.Cursor.Loc.Y == 0 {
1088 InfoBar.Message("Cannot move further up")
1101 // MoveLinesDown moves down the current line or selected lines if any
1102 func (h *BufPane) MoveLinesDown() bool {
1103 if h.Cursor.HasSelection() {
1104 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1105 InfoBar.Message("Cannot move further down")
1108 start := h.Cursor.CurSelection[0].Y
1109 end := h.Cursor.CurSelection[1].Y
1112 end, start = start, end
1116 if h.Cursor.CurSelection[sel].X != 0 {
1120 h.Buf.MoveLinesDown(
1125 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1126 InfoBar.Message("Cannot move further down")
1129 h.Buf.MoveLinesDown(
1139 // Paste whatever is in the system clipboard into the buffer
1140 // Delete and paste if the user has a selection
1141 func (h *BufPane) Paste() bool {
1142 clip, err := clipboard.Read(clipboard.ClipboardReg)
1151 // PastePrimary pastes from the primary clipboard (only use on linux)
1152 func (h *BufPane) PastePrimary() bool {
1153 clip, err := clipboard.Read(clipboard.PrimaryReg)
1162 func (h *BufPane) paste(clip string) {
1163 if h.Buf.Settings["smartpaste"].(bool) {
1164 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1165 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1166 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
1170 if h.Cursor.HasSelection() {
1171 h.Cursor.DeleteSelection()
1172 h.Cursor.ResetSelection()
1175 h.Buf.Insert(h.Cursor.Loc, clip)
1176 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1178 InfoBar.Message("Pasted clipboard")
1181 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1182 // currently on a brace
1183 func (h *BufPane) JumpToMatchingBrace() bool {
1184 for _, bp := range buffer.BracePairs {
1185 r := h.Cursor.RuneUnder(h.Cursor.X)
1186 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1187 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1188 matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1191 h.Cursor.GotoLoc(matchingBrace)
1193 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1205 // SelectAll selects the entire buffer
1206 func (h *BufPane) SelectAll() bool {
1207 h.Cursor.SetSelectionStart(h.Buf.Start())
1208 h.Cursor.SetSelectionEnd(h.Buf.End())
1209 // Put the cursor at the beginning
1216 // OpenFile opens a new file in the buffer
1217 func (h *BufPane) OpenFile() bool {
1218 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1220 h.HandleCommand(resp)
1226 // OpenFile opens a new file in the buffer
1227 func (h *BufPane) JumpLine() bool {
1228 InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1230 h.HandleCommand(resp)
1236 // Start moves the viewport to the start of the buffer
1237 func (h *BufPane) Start() bool {
1244 // End moves the viewport to the end of the buffer
1245 func (h *BufPane) End() bool {
1246 // TODO: softwrap problems?
1248 if v.Height > h.Buf.LinesNum() {
1252 v.StartLine = h.Buf.LinesNum() - v.Height
1258 // PageUp scrolls the view up a page
1259 func (h *BufPane) PageUp() bool {
1261 if v.StartLine > v.Height {
1262 h.ScrollUp(v.Height)
1270 // PageDown scrolls the view down a page
1271 func (h *BufPane) PageDown() bool {
1273 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1274 h.ScrollDown(v.Height)
1275 } else if h.Buf.LinesNum() >= v.Height {
1276 v.StartLine = h.Buf.LinesNum() - v.Height
1281 // SelectPageUp selects up one page
1282 func (h *BufPane) SelectPageUp() bool {
1283 if !h.Cursor.HasSelection() {
1284 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1286 h.Cursor.UpN(h.GetView().Height)
1287 h.Cursor.SelectTo(h.Cursor.Loc)
1292 // SelectPageDown selects down one page
1293 func (h *BufPane) SelectPageDown() bool {
1294 if !h.Cursor.HasSelection() {
1295 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1297 h.Cursor.DownN(h.GetView().Height)
1298 h.Cursor.SelectTo(h.Cursor.Loc)
1303 // CursorPageUp places the cursor a page up
1304 func (h *BufPane) CursorPageUp() bool {
1305 h.Cursor.Deselect(true)
1307 if h.Cursor.HasSelection() {
1308 h.Cursor.Loc = h.Cursor.CurSelection[0]
1309 h.Cursor.ResetSelection()
1310 h.Cursor.StoreVisualX()
1312 h.Cursor.UpN(h.GetView().Height)
1317 // CursorPageDown places the cursor a page up
1318 func (h *BufPane) CursorPageDown() bool {
1319 h.Cursor.Deselect(false)
1321 if h.Cursor.HasSelection() {
1322 h.Cursor.Loc = h.Cursor.CurSelection[1]
1323 h.Cursor.ResetSelection()
1324 h.Cursor.StoreVisualX()
1326 h.Cursor.DownN(h.GetView().Height)
1331 // HalfPageUp scrolls the view up half a page
1332 func (h *BufPane) HalfPageUp() bool {
1334 if v.StartLine > v.Height/2 {
1335 h.ScrollUp(v.Height / 2)
1343 // HalfPageDown scrolls the view down half a page
1344 func (h *BufPane) HalfPageDown() bool {
1346 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1347 h.ScrollDown(v.Height / 2)
1349 if h.Buf.LinesNum() >= v.Height {
1350 v.StartLine = h.Buf.LinesNum() - v.Height
1357 // ToggleDiffGutter turns the diff gutter off and on
1358 func (h *BufPane) ToggleDiffGutter() bool {
1359 if !h.Buf.Settings["diffgutter"].(bool) {
1360 h.Buf.Settings["diffgutter"] = true
1361 h.Buf.UpdateDiff(func(synchronous bool) {
1364 InfoBar.Message("Enabled diff gutter")
1366 h.Buf.Settings["diffgutter"] = false
1367 InfoBar.Message("Disabled diff gutter")
1372 // ToggleRuler turns line numbers off and on
1373 func (h *BufPane) ToggleRuler() bool {
1374 if !h.Buf.Settings["ruler"].(bool) {
1375 h.Buf.Settings["ruler"] = true
1376 InfoBar.Message("Enabled ruler")
1378 h.Buf.Settings["ruler"] = false
1379 InfoBar.Message("Disabled ruler")
1384 // ClearStatus clears the messenger bar
1385 func (h *BufPane) ClearStatus() bool {
1390 // ToggleHelp toggles the help screen
1391 func (h *BufPane) ToggleHelp() bool {
1392 if h.Buf.Type == buffer.BTHelp {
1400 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1401 func (h *BufPane) ToggleKeyMenu() bool {
1402 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1407 // ShellMode opens a terminal to run a shell command
1408 func (h *BufPane) ShellMode() bool {
1409 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1411 // The true here is for openTerm to make the command interactive
1412 shell.RunInteractiveShell(resp, true, false)
1419 // CommandMode lets the user enter a command
1420 func (h *BufPane) CommandMode() bool {
1421 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1423 h.HandleCommand(resp)
1429 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1430 func (h *BufPane) ToggleOverwriteMode() bool {
1431 h.isOverwriteMode = !h.isOverwriteMode
1435 // Escape leaves current mode
1436 func (h *BufPane) Escape() bool {
1440 // Deselect deselects on the current cursor
1441 func (h *BufPane) Deselect() bool {
1442 h.Cursor.Deselect(true)
1446 // ClearInfo clears the infobar
1447 func (h *BufPane) ClearInfo() bool {
1452 // Quit this will close the current tab or view that is open
1453 func (h *BufPane) Quit() bool {
1456 if len(MainTab().Panes) > 1 {
1458 } else if len(Tabs.List) > 1 {
1459 Tabs.RemoveTab(h.splitID)
1461 screen.Screen.Fini()
1466 if h.Buf.Modified() {
1467 if config.GlobalSettings["autosave"].(float64) > 0 {
1468 // autosave on means we automatically save when quitting
1469 h.SaveCB("Quit", func() {
1473 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1474 if !canceled && !yes {
1476 } else if !canceled && yes {
1477 h.SaveCB("Quit", func() {
1489 // QuitAll quits the whole editor; all splits and tabs
1490 func (h *BufPane) QuitAll() bool {
1491 anyModified := false
1492 for _, b := range buffer.OpenBuffers {
1500 for _, b := range buffer.OpenBuffers {
1503 screen.Screen.Fini()
1509 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1510 if !canceled && yes {
1521 // AddTab adds a new tab with an empty buffer
1522 func (h *BufPane) AddTab() bool {
1523 width, height := screen.Screen.Size()
1524 iOffset := config.GetInfoBarOffset()
1525 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1526 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1528 Tabs.SetActive(len(Tabs.List) - 1)
1533 // PreviousTab switches to the previous tab in the tab list
1534 func (h *BufPane) PreviousTab() bool {
1535 tabsLen := len(Tabs.List)
1536 a := Tabs.Active() + tabsLen
1537 Tabs.SetActive((a - 1) % tabsLen)
1542 // NextTab switches to the next tab in the tab list
1543 func (h *BufPane) NextTab() bool {
1545 Tabs.SetActive((a + 1) % len(Tabs.List))
1550 // VSplitAction opens an empty vertical split
1551 func (h *BufPane) VSplitAction() bool {
1552 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1557 // HSplitAction opens an empty horizontal split
1558 func (h *BufPane) HSplitAction() bool {
1559 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1564 // Unsplit closes all splits in the current tab except the active one
1565 func (h *BufPane) Unsplit() bool {
1567 n := tab.GetNode(h.splitID)
1570 tab.RemovePane(tab.GetPane(h.splitID))
1572 tab.SetActive(len(tab.Panes) - 1)
1579 // NextSplit changes the view to the next split
1580 func (h *BufPane) NextSplit() bool {
1582 if a < len(h.tab.Panes)-1 {
1593 // PreviousSplit changes the view to the previous split
1594 func (h *BufPane) PreviousSplit() bool {
1599 a = len(h.tab.Panes) - 1
1606 var curmacro []interface{}
1607 var recording_macro bool
1609 // ToggleMacro toggles recording of a macro
1610 func (h *BufPane) ToggleMacro() bool {
1611 recording_macro = !recording_macro
1612 if recording_macro {
1613 curmacro = []interface{}{}
1614 InfoBar.Message("Recording")
1616 InfoBar.Message("Stopped recording")
1622 // PlayMacro plays back the most recently recorded macro
1623 func (h *BufPane) PlayMacro() bool {
1624 if recording_macro {
1627 for _, action := range curmacro {
1628 switch t := action.(type) {
1631 case func(*BufPane) bool:
1639 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1640 func (h *BufPane) SpawnMultiCursor() bool {
1641 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1642 if !spawner.HasSelection() {
1643 spawner.SelectWord()
1649 sel := spawner.GetSelection()
1650 searchStart := spawner.CurSelection[1]
1652 search := string(sel)
1653 search = regexp.QuoteMeta(search)
1655 search = "\\b" + search + "\\b"
1657 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1662 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1663 c.SetSelectionStart(match[0])
1664 c.SetSelectionEnd(match[1])
1665 c.OrigSelection[0] = c.CurSelection[0]
1666 c.OrigSelection[1] = c.CurSelection[1]
1667 c.Loc = c.CurSelection[1]
1670 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1671 h.Buf.MergeCursors()
1673 InfoBar.Message("No matches found")
1680 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1681 func (h *BufPane) SpawnMultiCursorUp() bool {
1682 if h.Cursor.Y == 0 {
1685 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1689 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1691 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1692 h.Buf.MergeCursors()
1698 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1699 func (h *BufPane) SpawnMultiCursorDown() bool {
1700 if h.Cursor.Y+1 == h.Buf.LinesNum() {
1703 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1707 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1709 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1710 h.Buf.MergeCursors()
1715 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1716 func (h *BufPane) SpawnMultiCursorSelect() bool {
1717 // Avoid cases where multiple cursors already exist, that would create problems
1718 if h.Buf.NumCursors() > 1 {
1725 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1727 startLine, endLine = b, a
1729 startLine, endLine = a, b
1732 if h.Cursor.HasSelection() {
1733 h.Cursor.ResetSelection()
1734 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1736 for i := startLine; i <= endLine; i++ {
1737 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1741 h.Buf.MergeCursors()
1745 InfoBar.Message("Added cursors from selection")
1749 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1750 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1752 mx, my := e.Position()
1753 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1754 c := buffer.NewCursor(b, mouseLoc)
1761 // SkipMultiCursor moves the current multiple cursor to the next available position
1762 func (h *BufPane) SkipMultiCursor() bool {
1763 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1764 sel := lastC.GetSelection()
1765 searchStart := lastC.CurSelection[1]
1767 search := string(sel)
1768 search = regexp.QuoteMeta(search)
1770 search = "\\b" + search + "\\b"
1773 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1778 lastC.SetSelectionStart(match[0])
1779 lastC.SetSelectionEnd(match[1])
1780 lastC.OrigSelection[0] = lastC.CurSelection[0]
1781 lastC.OrigSelection[1] = lastC.CurSelection[1]
1782 lastC.Loc = lastC.CurSelection[1]
1784 h.Buf.MergeCursors()
1785 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1787 InfoBar.Message("No matches found")
1793 // RemoveMultiCursor removes the latest multiple cursor
1794 func (h *BufPane) RemoveMultiCursor() bool {
1795 if h.Buf.NumCursors() > 1 {
1796 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1797 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1798 h.Buf.UpdateCursors()
1806 // RemoveAllMultiCursors removes all cursors except the base cursor
1807 func (h *BufPane) RemoveAllMultiCursors() bool {
1808 h.Buf.ClearCursors()
1814 // None is an action that does nothing
1815 func (h *BufPane) None() bool {