12 shellquote "github.com/kballard/go-shellquote"
13 "github.com/zyedidia/micro/v2/internal/buffer"
14 "github.com/zyedidia/micro/v2/internal/clipboard"
15 "github.com/zyedidia/micro/v2/internal/config"
16 "github.com/zyedidia/micro/v2/internal/display"
17 "github.com/zyedidia/micro/v2/internal/screen"
18 "github.com/zyedidia/micro/v2/internal/shell"
19 "github.com/zyedidia/micro/v2/internal/util"
20 "github.com/zyedidia/tcell/v2"
23 // ScrollUp is not an action
24 func (h *BufPane) ScrollUp(n int) {
26 v.StartLine = h.Scroll(v.StartLine, -n)
30 // ScrollDown is not an action
31 func (h *BufPane) ScrollDown(n int) {
33 v.StartLine = h.Scroll(v.StartLine, n)
37 // ScrollAdjust can be used to shift the view so that the last line is at the
38 // bottom if the user has scrolled past the last line.
39 func (h *BufPane) ScrollAdjust() {
41 end := h.SLocFromLoc(h.Buf.End())
42 if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
43 v.StartLine = h.Scroll(end, -h.BufView().Height+1)
48 // MousePress is the event that should happen when a normal click happens
49 // This is almost always bound to left click
50 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
52 mx, my := e.Position()
53 mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
54 h.Cursor.Loc = mouseLoc
56 if b.NumCursors() > 1 {
59 h.Cursor = h.Buf.GetActiveCursor()
60 h.Cursor.Loc = mouseLoc
62 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
65 h.lastClickTime = time.Now()
71 h.Cursor.CopySelection(clipboard.PrimaryReg)
74 h.lastClickTime = time.Now()
80 h.Cursor.CopySelection(clipboard.PrimaryReg)
85 h.lastClickTime = time.Now()
87 h.Cursor.OrigSelection[0] = h.Cursor.Loc
88 h.Cursor.CurSelection[0] = h.Cursor.Loc
89 h.Cursor.CurSelection[1] = h.Cursor.Loc
91 h.mouseReleased = false
92 } else if !h.mouseReleased {
94 h.Cursor.AddLineToSelection()
95 } else if h.doubleClick {
96 h.Cursor.AddWordToSelection()
98 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
102 h.Cursor.StoreVisualX()
108 // ScrollUpAction scrolls the view up
109 func (h *BufPane) ScrollUpAction() bool {
110 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
114 // ScrollDownAction scrolls the view up
115 func (h *BufPane) ScrollDownAction() bool {
116 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
120 // Center centers the view on the cursor
121 func (h *BufPane) Center() bool {
123 v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
129 // MoveCursorUp is not an action
130 func (h *BufPane) MoveCursorUp(n int) {
131 if !h.Buf.Settings["softwrap"].(bool) {
134 vloc := h.VLocFromLoc(h.Cursor.Loc)
135 sloc := h.Scroll(vloc.SLoc, -n)
136 if sloc == vloc.SLoc {
137 // we are at the beginning of buffer
138 h.Cursor.Loc = h.Buf.Start()
139 h.Cursor.LastVisualX = 0
142 vloc.VisualX = h.Cursor.LastVisualX
143 h.Cursor.Loc = h.LocFromVLoc(vloc)
148 // MoveCursorDown is not an action
149 func (h *BufPane) MoveCursorDown(n int) {
150 if !h.Buf.Settings["softwrap"].(bool) {
153 vloc := h.VLocFromLoc(h.Cursor.Loc)
154 sloc := h.Scroll(vloc.SLoc, n)
155 if sloc == vloc.SLoc {
156 // we are at the end of buffer
157 h.Cursor.Loc = h.Buf.End()
158 vloc = h.VLocFromLoc(h.Cursor.Loc)
159 h.Cursor.LastVisualX = vloc.VisualX
162 vloc.VisualX = h.Cursor.LastVisualX
163 h.Cursor.Loc = h.LocFromVLoc(vloc)
168 // CursorUp moves the cursor up
169 func (h *BufPane) CursorUp() bool {
170 h.Cursor.Deselect(true)
176 // CursorDown moves the cursor down
177 func (h *BufPane) CursorDown() bool {
178 h.Cursor.Deselect(true)
184 // CursorLeft moves the cursor left
185 func (h *BufPane) CursorLeft() bool {
186 if h.Cursor.HasSelection() {
187 h.Cursor.Deselect(true)
189 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
190 tabmovement := h.Buf.Settings["tabmovement"].(bool)
191 if tabstospaces && tabmovement {
192 tabsize := int(h.Buf.Settings["tabsize"].(float64))
193 line := h.Buf.LineBytes(h.Cursor.Y)
194 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
195 for i := 0; i < tabsize; i++ {
209 // CursorRight moves the cursor right
210 func (h *BufPane) CursorRight() bool {
211 if h.Cursor.HasSelection() {
212 h.Cursor.Deselect(false)
213 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
215 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
216 tabmovement := h.Buf.Settings["tabmovement"].(bool)
217 if tabstospaces && tabmovement {
218 tabsize := int(h.Buf.Settings["tabsize"].(float64))
219 line := h.Buf.LineBytes(h.Cursor.Y)
220 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]) {
221 for i := 0; i < tabsize; i++ {
236 // WordRight moves the cursor one word to the right
237 func (h *BufPane) WordRight() bool {
238 h.Cursor.Deselect(false)
244 // WordLeft moves the cursor one word to the left
245 func (h *BufPane) WordLeft() bool {
246 h.Cursor.Deselect(true)
252 // SelectUp selects up one line
253 func (h *BufPane) SelectUp() bool {
254 if !h.Cursor.HasSelection() {
255 h.Cursor.OrigSelection[0] = h.Cursor.Loc
258 h.Cursor.SelectTo(h.Cursor.Loc)
263 // SelectDown selects down one line
264 func (h *BufPane) SelectDown() bool {
265 if !h.Cursor.HasSelection() {
266 h.Cursor.OrigSelection[0] = h.Cursor.Loc
269 h.Cursor.SelectTo(h.Cursor.Loc)
274 // SelectLeft selects the character to the left of the cursor
275 func (h *BufPane) SelectLeft() bool {
278 if loc.GreaterThan(count) {
281 if !h.Cursor.HasSelection() {
282 h.Cursor.OrigSelection[0] = loc
285 h.Cursor.SelectTo(h.Cursor.Loc)
290 // SelectRight selects the character to the right of the cursor
291 func (h *BufPane) SelectRight() bool {
294 if loc.GreaterThan(count) {
297 if !h.Cursor.HasSelection() {
298 h.Cursor.OrigSelection[0] = loc
301 h.Cursor.SelectTo(h.Cursor.Loc)
306 // SelectWordRight selects the word to the right of the cursor
307 func (h *BufPane) SelectWordRight() bool {
308 if !h.Cursor.HasSelection() {
309 h.Cursor.OrigSelection[0] = h.Cursor.Loc
312 h.Cursor.SelectTo(h.Cursor.Loc)
317 // SelectWordLeft selects the word to the left of the cursor
318 func (h *BufPane) SelectWordLeft() bool {
319 if !h.Cursor.HasSelection() {
320 h.Cursor.OrigSelection[0] = h.Cursor.Loc
323 h.Cursor.SelectTo(h.Cursor.Loc)
328 // StartOfText moves the cursor to the start of the text of the line
329 func (h *BufPane) StartOfText() bool {
330 h.Cursor.Deselect(true)
331 h.Cursor.StartOfText()
336 // StartOfTextToggle toggles the cursor between the start of the text of the line
337 // and the start of the line
338 func (h *BufPane) StartOfTextToggle() bool {
339 h.Cursor.Deselect(true)
340 if h.Cursor.IsStartOfText() {
343 h.Cursor.StartOfText()
349 // StartOfLine moves the cursor to the start of the line
350 func (h *BufPane) StartOfLine() bool {
351 h.Cursor.Deselect(true)
357 // EndOfLine moves the cursor to the end of the line
358 func (h *BufPane) EndOfLine() bool {
359 h.Cursor.Deselect(true)
365 // SelectLine selects the entire current line
366 func (h *BufPane) SelectLine() bool {
367 h.Cursor.SelectLine()
372 // SelectToStartOfText selects to the start of the text on the current line
373 func (h *BufPane) SelectToStartOfText() bool {
374 if !h.Cursor.HasSelection() {
375 h.Cursor.OrigSelection[0] = h.Cursor.Loc
377 h.Cursor.StartOfText()
378 h.Cursor.SelectTo(h.Cursor.Loc)
383 // SelectToStartOfTextToggle toggles the selection between the start of the text
384 // on the current line and the start of the line
385 func (h *BufPane) SelectToStartOfTextToggle() bool {
386 if !h.Cursor.HasSelection() {
387 h.Cursor.OrigSelection[0] = h.Cursor.Loc
389 if h.Cursor.IsStartOfText() {
392 h.Cursor.StartOfText()
394 h.Cursor.SelectTo(h.Cursor.Loc)
399 // SelectToStartOfLine selects to the start of the current line
400 func (h *BufPane) SelectToStartOfLine() bool {
401 if !h.Cursor.HasSelection() {
402 h.Cursor.OrigSelection[0] = h.Cursor.Loc
405 h.Cursor.SelectTo(h.Cursor.Loc)
410 // SelectToEndOfLine selects to the end of the current line
411 func (h *BufPane) SelectToEndOfLine() bool {
412 if !h.Cursor.HasSelection() {
413 h.Cursor.OrigSelection[0] = h.Cursor.Loc
416 h.Cursor.SelectTo(h.Cursor.Loc)
421 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
422 func (h *BufPane) ParagraphPrevious() bool {
424 for line = h.Cursor.Y; line > 0; line-- {
425 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
431 // If no empty line found. move cursor to end of buffer
433 h.Cursor.Loc = h.Buf.Start()
439 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
440 func (h *BufPane) ParagraphNext() bool {
442 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
443 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
449 // If no empty line found. move cursor to end of buffer
450 if line == h.Buf.LinesNum() {
451 h.Cursor.Loc = h.Buf.End()
457 // Retab changes all tabs to spaces or all spaces to tabs depending
458 // on the user's settings
459 func (h *BufPane) Retab() bool {
465 // CursorStart moves the cursor to the start of the buffer
466 func (h *BufPane) CursorStart() bool {
467 h.Cursor.Deselect(true)
470 h.Cursor.StoreVisualX()
475 // CursorEnd moves the cursor to the end of the buffer
476 func (h *BufPane) CursorEnd() bool {
477 h.Cursor.Deselect(true)
478 h.Cursor.Loc = h.Buf.End()
479 h.Cursor.StoreVisualX()
484 // SelectToStart selects the text from the cursor to the start of the buffer
485 func (h *BufPane) SelectToStart() bool {
486 if !h.Cursor.HasSelection() {
487 h.Cursor.OrigSelection[0] = h.Cursor.Loc
490 h.Cursor.SelectTo(h.Buf.Start())
495 // SelectToEnd selects the text from the cursor to the end of the buffer
496 func (h *BufPane) SelectToEnd() bool {
497 if !h.Cursor.HasSelection() {
498 h.Cursor.OrigSelection[0] = h.Cursor.Loc
501 h.Cursor.SelectTo(h.Buf.End())
506 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
507 func (h *BufPane) InsertNewline() bool {
509 if h.Cursor.HasSelection() {
510 h.Cursor.DeleteSelection()
511 h.Cursor.ResetSelection()
514 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
516 h.Buf.Insert(h.Cursor.Loc, "\n")
519 if h.Buf.Settings["autoindent"].(bool) {
523 h.Buf.Insert(h.Cursor.Loc, string(ws))
524 // for i := 0; i < len(ws); i++ {
528 // Remove the whitespaces if keepautoindent setting is off
529 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
530 line := h.Buf.LineBytes(h.Cursor.Y - 1)
531 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
534 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
539 // Backspace deletes the previous character
540 func (h *BufPane) Backspace() bool {
541 if h.Cursor.HasSelection() {
542 h.Cursor.DeleteSelection()
543 h.Cursor.ResetSelection()
544 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
545 // We have to do something a bit hacky here because we want to
546 // delete the line by first moving left and then deleting backwards
547 // but the undo redo would place the cursor in the wrong place
548 // So instead we move left, save the position, move back, delete
549 // and restore the position
551 // If the user is using spaces instead of tabs and they are deleting
552 // whitespace at the start of the line, we should delete as if it's a
553 // tab (tabSize number of spaces)
554 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
555 tabSize := int(h.Buf.Settings["tabsize"].(float64))
556 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
558 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
561 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
564 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
569 // DeleteWordRight deletes the word to the right of the cursor
570 func (h *BufPane) DeleteWordRight() bool {
572 if h.Cursor.HasSelection() {
573 h.Cursor.DeleteSelection()
574 h.Cursor.ResetSelection()
580 // DeleteWordLeft deletes the word to the left of the cursor
581 func (h *BufPane) DeleteWordLeft() bool {
583 if h.Cursor.HasSelection() {
584 h.Cursor.DeleteSelection()
585 h.Cursor.ResetSelection()
591 // Delete deletes the next character
592 func (h *BufPane) Delete() bool {
593 if h.Cursor.HasSelection() {
594 h.Cursor.DeleteSelection()
595 h.Cursor.ResetSelection()
598 if loc.LessThan(h.Buf.End()) {
599 h.Buf.Remove(loc, loc.Move(1, h.Buf))
606 // IndentSelection indents the current selection
607 func (h *BufPane) IndentSelection() bool {
608 if h.Cursor.HasSelection() {
609 start := h.Cursor.CurSelection[0]
610 end := h.Cursor.CurSelection[1]
612 start, end = end, start
613 h.Cursor.SetSelectionStart(start)
614 h.Cursor.SetSelectionEnd(end)
618 endY := end.Move(-1, h.Buf).Y
619 endX := end.Move(-1, h.Buf).X
620 tabsize := int(h.Buf.Settings["tabsize"].(float64))
621 indentsize := len(h.Buf.IndentString(tabsize))
622 for y := startY; y <= endY; y++ {
623 if len(h.Buf.LineBytes(y)) > 0 {
624 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
625 if y == startY && start.X > 0 {
626 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
629 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
633 h.Buf.RelocateCursors()
641 // IndentLine moves the current line forward one indentation
642 func (h *BufPane) IndentLine() bool {
643 if h.Cursor.HasSelection() {
647 tabsize := int(h.Buf.Settings["tabsize"].(float64))
648 indentstr := h.Buf.IndentString(tabsize)
649 h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
650 h.Buf.RelocateCursors()
655 // OutdentLine moves the current line back one indentation
656 func (h *BufPane) OutdentLine() bool {
657 if h.Cursor.HasSelection() {
661 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
662 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
665 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
667 h.Buf.RelocateCursors()
672 // OutdentSelection takes the current selection and moves it back one indent level
673 func (h *BufPane) OutdentSelection() bool {
674 if h.Cursor.HasSelection() {
675 start := h.Cursor.CurSelection[0]
676 end := h.Cursor.CurSelection[1]
678 start, end = end, start
679 h.Cursor.SetSelectionStart(start)
680 h.Cursor.SetSelectionEnd(end)
684 endY := end.Move(-1, h.Buf).Y
685 for y := startY; y <= endY; y++ {
686 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
687 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
690 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
693 h.Buf.RelocateCursors()
701 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
702 func (h *BufPane) Autocomplete() bool {
705 if h.Cursor.HasSelection() {
712 r := h.Cursor.RuneUnder(h.Cursor.X)
713 prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
714 if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
715 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
719 if b.HasSuggestions {
720 b.CycleAutocomplete(true)
723 return b.Autocomplete(buffer.BufferComplete)
726 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
727 func (h *BufPane) CycleAutocompleteBack() bool {
728 if h.Cursor.HasSelection() {
732 if h.Buf.HasSuggestions {
733 h.Buf.CycleAutocomplete(false)
739 // InsertTab inserts a tab or spaces
740 func (h *BufPane) InsertTab() bool {
742 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
743 tabBytes := len(indent)
744 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
745 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
750 // SaveAll saves all open buffers
751 func (h *BufPane) SaveAll() bool {
752 for _, b := range buffer.OpenBuffers {
758 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
759 func (h *BufPane) SaveCB(action string, callback func()) bool {
760 // If this is an empty buffer, ask for a filename
761 if h.Buf.Path == "" {
762 h.SaveAsCB(action, callback)
764 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
772 // Save the buffer to disk
773 func (h *BufPane) Save() bool {
774 return h.SaveCB("Save", nil)
777 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
778 // The callback is only called if the save was successful
779 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
780 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
782 // the filename might or might not be quoted, so unquote first then join the strings.
783 args, err := shellquote.Split(resp)
785 InfoBar.Error("Error parsing arguments: ", err)
789 InfoBar.Error("No filename given")
792 filename := strings.Join(args, " ")
793 noPrompt := h.saveBufToFile(filename, action, callback)
795 h.completeAction(action)
802 // SaveAs saves the buffer to disk with the given name
803 func (h *BufPane) SaveAs() bool {
804 return h.SaveAsCB("SaveAs", nil)
807 // This function saves the buffer to `filename` and changes the buffer's path and name
808 // to `filename` if the save is successful
809 // The callback is only called if the save was successful
810 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
811 err := h.Buf.SaveAs(filename)
813 if errors.Is(err, fs.ErrPermission) {
814 saveWithSudo := func() {
815 err = h.Buf.SaveAsWithSudo(filename)
819 h.Buf.Path = filename
820 h.Buf.SetName(filename)
821 InfoBar.Message("Saved " + filename)
827 if h.Buf.Settings["autosu"].(bool) {
831 fmt.Sprintf("Permission denied. Do you want to save this file using %s? (y,n)", config.GlobalSettings["sucmd"].(string)),
832 func(yes, canceled bool) {
833 if yes && !canceled {
835 h.completeAction(action)
845 h.Buf.Path = filename
846 h.Buf.SetName(filename)
847 InfoBar.Message("Saved " + filename)
855 // Find opens a prompt and searches forward for the input
856 func (h *BufPane) Find() bool {
860 // FindLiteral is the same as Find() but does not support regular expressions
861 func (h *BufPane) FindLiteral() bool {
865 // Search searches for a given string/regex in the buffer and selects the next
866 // match if a match is found
867 // This function affects lastSearch and lastSearchRegex (saved searches) for
868 // use with FindNext and FindPrevious
869 func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
870 match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
875 h.Cursor.SetSelectionStart(match[0])
876 h.Cursor.SetSelectionEnd(match[1])
877 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
878 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
879 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
881 h.lastSearchRegex = useRegex
884 h.Cursor.ResetSelection()
889 func (h *BufPane) find(useRegex bool) bool {
890 h.searchOrig = h.Cursor.Loc
893 prompt = "Find (regex): "
895 var eventCallback func(resp string)
896 if h.Buf.Settings["incsearch"].(bool) {
897 eventCallback = func(resp string) {
898 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
900 h.Cursor.SetSelectionStart(match[0])
901 h.Cursor.SetSelectionEnd(match[1])
902 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
903 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
904 h.Cursor.GotoLoc(match[1])
906 h.Cursor.GotoLoc(h.searchOrig)
907 h.Cursor.ResetSelection()
912 findCallback := func(resp string, canceled bool) {
915 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
920 h.Cursor.SetSelectionStart(match[0])
921 h.Cursor.SetSelectionEnd(match[1])
922 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
923 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
924 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
926 h.lastSearchRegex = useRegex
928 h.Cursor.ResetSelection()
929 InfoBar.Message("No matches found")
932 h.Cursor.ResetSelection()
936 pattern := string(h.Cursor.GetSelection())
937 if eventCallback != nil && pattern != "" {
938 eventCallback(pattern)
940 InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback)
947 // FindNext searches forwards for the last used search term
948 func (h *BufPane) FindNext() bool {
949 // If the cursor is at the start of a selection and we search we want
950 // to search from the end of the selection in the case that
951 // the selection is a search result in which case we wouldn't move at
952 // at all which would be bad
953 searchLoc := h.Cursor.Loc
954 if h.Cursor.HasSelection() {
955 searchLoc = h.Cursor.CurSelection[1]
957 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
962 h.Cursor.SetSelectionStart(match[0])
963 h.Cursor.SetSelectionEnd(match[1])
964 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
965 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
966 h.Cursor.Loc = h.Cursor.CurSelection[1]
968 h.Cursor.ResetSelection()
974 // FindPrevious searches backwards for the last used search term
975 func (h *BufPane) FindPrevious() bool {
976 // If the cursor is at the end of a selection and we search we want
977 // to search from the beginning of the selection in the case that
978 // the selection is a search result in which case we wouldn't move at
979 // at all which would be bad
980 searchLoc := h.Cursor.Loc
981 if h.Cursor.HasSelection() {
982 searchLoc = h.Cursor.CurSelection[0]
984 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
989 h.Cursor.SetSelectionStart(match[0])
990 h.Cursor.SetSelectionEnd(match[1])
991 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
992 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
993 h.Cursor.Loc = h.Cursor.CurSelection[1]
995 h.Cursor.ResetSelection()
1001 // Undo undoes the last action
1002 func (h *BufPane) Undo() bool {
1004 InfoBar.Message("Undid action")
1009 // Redo redoes the last action
1010 func (h *BufPane) Redo() bool {
1012 InfoBar.Message("Redid action")
1017 // Copy the selection to the system clipboard
1018 func (h *BufPane) Copy() bool {
1019 if h.Cursor.HasSelection() {
1020 h.Cursor.CopySelection(clipboard.ClipboardReg)
1022 InfoBar.Message("Copied selection")
1028 // CopyLine copies the current line to the clipboard
1029 func (h *BufPane) CopyLine() bool {
1030 if h.Cursor.HasSelection() {
1033 h.Cursor.SelectLine()
1034 h.Cursor.CopySelection(clipboard.ClipboardReg)
1036 InfoBar.Message("Copied line")
1038 h.Cursor.Deselect(true)
1043 // CutLine cuts the current line to the clipboard
1044 func (h *BufPane) CutLine() bool {
1045 h.Cursor.SelectLine()
1046 if !h.Cursor.HasSelection() {
1050 if h.Cursor.HasSelection() {
1051 if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
1054 clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1057 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
1061 h.lastCutTime = time.Now()
1062 h.Cursor.DeleteSelection()
1063 h.Cursor.ResetSelection()
1064 InfoBar.Message("Cut line")
1069 // Cut the selection to the system clipboard
1070 func (h *BufPane) Cut() bool {
1071 if h.Cursor.HasSelection() {
1072 h.Cursor.CopySelection(clipboard.ClipboardReg)
1073 h.Cursor.DeleteSelection()
1074 h.Cursor.ResetSelection()
1076 InfoBar.Message("Cut selection")
1084 // DuplicateLine duplicates the current line or selection
1085 func (h *BufPane) DuplicateLine() bool {
1086 if h.Cursor.HasSelection() {
1087 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1090 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1094 InfoBar.Message("Duplicated line")
1099 // DeleteLine deletes the current line
1100 func (h *BufPane) DeleteLine() bool {
1101 h.Cursor.SelectLine()
1102 if !h.Cursor.HasSelection() {
1105 h.Cursor.DeleteSelection()
1106 h.Cursor.ResetSelection()
1107 InfoBar.Message("Deleted line")
1112 // MoveLinesUp moves up the current line or selected lines if any
1113 func (h *BufPane) MoveLinesUp() bool {
1114 if h.Cursor.HasSelection() {
1115 if h.Cursor.CurSelection[0].Y == 0 {
1116 InfoBar.Message("Cannot move further up")
1119 start := h.Cursor.CurSelection[0].Y
1120 end := h.Cursor.CurSelection[1].Y
1123 end, start = start, end
1128 if h.Cursor.CurSelection[sel].X != 0 {
1139 h.Cursor.CurSelection[sel].Y -= 1
1142 if h.Cursor.Loc.Y == 0 {
1143 InfoBar.Message("Cannot move further up")
1156 // MoveLinesDown moves down the current line or selected lines if any
1157 func (h *BufPane) MoveLinesDown() bool {
1158 if h.Cursor.HasSelection() {
1159 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1160 InfoBar.Message("Cannot move further down")
1163 start := h.Cursor.CurSelection[0].Y
1164 end := h.Cursor.CurSelection[1].Y
1167 end, start = start, end
1171 if h.Cursor.CurSelection[sel].X != 0 {
1175 h.Buf.MoveLinesDown(
1180 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1181 InfoBar.Message("Cannot move further down")
1184 h.Buf.MoveLinesDown(
1194 // Paste whatever is in the system clipboard into the buffer
1195 // Delete and paste if the user has a selection
1196 func (h *BufPane) Paste() bool {
1197 clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1207 // PastePrimary pastes from the primary clipboard (only use on linux)
1208 func (h *BufPane) PastePrimary() bool {
1209 clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
1219 func (h *BufPane) paste(clip string) {
1220 if h.Buf.Settings["smartpaste"].(bool) {
1221 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1222 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1223 clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
1227 if h.Cursor.HasSelection() {
1228 h.Cursor.DeleteSelection()
1229 h.Cursor.ResetSelection()
1232 h.Buf.Insert(h.Cursor.Loc, clip)
1233 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1235 InfoBar.Message("Pasted clipboard")
1238 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1239 // currently on a brace
1240 func (h *BufPane) JumpToMatchingBrace() bool {
1241 for _, bp := range buffer.BracePairs {
1242 r := h.Cursor.RuneUnder(h.Cursor.X)
1243 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1244 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1245 matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1248 h.Cursor.GotoLoc(matchingBrace)
1250 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1263 // SelectAll selects the entire buffer
1264 func (h *BufPane) SelectAll() bool {
1265 h.Cursor.SetSelectionStart(h.Buf.Start())
1266 h.Cursor.SetSelectionEnd(h.Buf.End())
1267 // Put the cursor at the beginning
1274 // OpenFile opens a new file in the buffer
1275 func (h *BufPane) OpenFile() bool {
1276 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1278 h.HandleCommand(resp)
1284 // OpenFile opens a new file in the buffer
1285 func (h *BufPane) JumpLine() bool {
1286 InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1288 h.HandleCommand(resp)
1294 // Start moves the viewport to the start of the buffer
1295 func (h *BufPane) Start() bool {
1297 v.StartLine = display.SLoc{0, 0}
1302 // End moves the viewport to the end of the buffer
1303 func (h *BufPane) End() bool {
1305 v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
1310 // PageUp scrolls the view up a page
1311 func (h *BufPane) PageUp() bool {
1312 h.ScrollUp(h.BufView().Height)
1316 // PageDown scrolls the view down a page
1317 func (h *BufPane) PageDown() bool {
1318 h.ScrollDown(h.BufView().Height)
1323 // SelectPageUp selects up one page
1324 func (h *BufPane) SelectPageUp() bool {
1325 if !h.Cursor.HasSelection() {
1326 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1328 h.MoveCursorUp(h.BufView().Height)
1329 h.Cursor.SelectTo(h.Cursor.Loc)
1334 // SelectPageDown selects down one page
1335 func (h *BufPane) SelectPageDown() bool {
1336 if !h.Cursor.HasSelection() {
1337 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1339 h.MoveCursorDown(h.BufView().Height)
1340 h.Cursor.SelectTo(h.Cursor.Loc)
1345 // CursorPageUp places the cursor a page up
1346 func (h *BufPane) CursorPageUp() bool {
1347 h.Cursor.Deselect(true)
1349 if h.Cursor.HasSelection() {
1350 h.Cursor.Loc = h.Cursor.CurSelection[0]
1351 h.Cursor.ResetSelection()
1352 h.Cursor.StoreVisualX()
1354 h.MoveCursorUp(h.BufView().Height)
1359 // CursorPageDown places the cursor a page up
1360 func (h *BufPane) CursorPageDown() bool {
1361 h.Cursor.Deselect(false)
1363 if h.Cursor.HasSelection() {
1364 h.Cursor.Loc = h.Cursor.CurSelection[1]
1365 h.Cursor.ResetSelection()
1366 h.Cursor.StoreVisualX()
1368 h.MoveCursorDown(h.BufView().Height)
1373 // HalfPageUp scrolls the view up half a page
1374 func (h *BufPane) HalfPageUp() bool {
1375 h.ScrollUp(h.BufView().Height / 2)
1379 // HalfPageDown scrolls the view down half a page
1380 func (h *BufPane) HalfPageDown() bool {
1381 h.ScrollDown(h.BufView().Height / 2)
1386 // ToggleDiffGutter turns the diff gutter off and on
1387 func (h *BufPane) ToggleDiffGutter() bool {
1388 if !h.Buf.Settings["diffgutter"].(bool) {
1389 h.Buf.Settings["diffgutter"] = true
1390 h.Buf.UpdateDiff(func(synchronous bool) {
1393 InfoBar.Message("Enabled diff gutter")
1395 h.Buf.Settings["diffgutter"] = false
1396 InfoBar.Message("Disabled diff gutter")
1401 // ToggleRuler turns line numbers off and on
1402 func (h *BufPane) ToggleRuler() bool {
1403 if !h.Buf.Settings["ruler"].(bool) {
1404 h.Buf.Settings["ruler"] = true
1405 InfoBar.Message("Enabled ruler")
1407 h.Buf.Settings["ruler"] = false
1408 InfoBar.Message("Disabled ruler")
1413 // ClearStatus clears the messenger bar
1414 func (h *BufPane) ClearStatus() bool {
1419 // ToggleHelp toggles the help screen
1420 func (h *BufPane) ToggleHelp() bool {
1421 if h.Buf.Type == buffer.BTHelp {
1429 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1430 func (h *BufPane) ToggleKeyMenu() bool {
1431 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1436 // ShellMode opens a terminal to run a shell command
1437 func (h *BufPane) ShellMode() bool {
1438 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1440 // The true here is for openTerm to make the command interactive
1441 shell.RunInteractiveShell(resp, true, false)
1448 // CommandMode lets the user enter a command
1449 func (h *BufPane) CommandMode() bool {
1450 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1452 h.HandleCommand(resp)
1458 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1459 func (h *BufPane) ToggleOverwriteMode() bool {
1460 h.isOverwriteMode = !h.isOverwriteMode
1464 // Escape leaves current mode
1465 func (h *BufPane) Escape() bool {
1469 // Deselect deselects on the current cursor
1470 func (h *BufPane) Deselect() bool {
1471 h.Cursor.Deselect(true)
1475 // ClearInfo clears the infobar
1476 func (h *BufPane) ClearInfo() bool {
1481 // ForceQuit closes the current tab or view even if there are unsaved changes
1483 func (h *BufPane) ForceQuit() bool {
1485 if len(MainTab().Panes) > 1 {
1487 } else if len(Tabs.List) > 1 {
1488 Tabs.RemoveTab(h.splitID)
1490 screen.Screen.Fini()
1497 // Quit this will close the current tab or view that is open
1498 func (h *BufPane) Quit() bool {
1499 if h.Buf.Modified() {
1500 if config.GlobalSettings["autosave"].(float64) > 0 {
1501 // autosave on means we automatically save when quitting
1502 h.SaveCB("Quit", func() {
1506 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1507 if !canceled && !yes {
1509 } else if !canceled && yes {
1510 h.SaveCB("Quit", func() {
1522 // QuitAll quits the whole editor; all splits and tabs
1523 func (h *BufPane) QuitAll() bool {
1524 anyModified := false
1525 for _, b := range buffer.OpenBuffers {
1533 for _, b := range buffer.OpenBuffers {
1536 screen.Screen.Fini()
1542 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1543 if !canceled && yes {
1554 // AddTab adds a new tab with an empty buffer
1555 func (h *BufPane) AddTab() bool {
1556 width, height := screen.Screen.Size()
1557 iOffset := config.GetInfoBarOffset()
1558 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1559 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1561 Tabs.SetActive(len(Tabs.List) - 1)
1566 // PreviousTab switches to the previous tab in the tab list
1567 func (h *BufPane) PreviousTab() bool {
1568 tabsLen := len(Tabs.List)
1569 a := Tabs.Active() + tabsLen
1570 Tabs.SetActive((a - 1) % tabsLen)
1575 // NextTab switches to the next tab in the tab list
1576 func (h *BufPane) NextTab() bool {
1578 Tabs.SetActive((a + 1) % len(Tabs.List))
1583 // VSplitAction opens an empty vertical split
1584 func (h *BufPane) VSplitAction() bool {
1585 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1590 // HSplitAction opens an empty horizontal split
1591 func (h *BufPane) HSplitAction() bool {
1592 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1597 // Unsplit closes all splits in the current tab except the active one
1598 func (h *BufPane) Unsplit() bool {
1600 n := tab.GetNode(h.splitID)
1603 tab.RemovePane(tab.GetPane(h.splitID))
1605 tab.SetActive(len(tab.Panes) - 1)
1612 // NextSplit changes the view to the next split
1613 func (h *BufPane) NextSplit() bool {
1615 if a < len(h.tab.Panes)-1 {
1626 // PreviousSplit changes the view to the previous split
1627 func (h *BufPane) PreviousSplit() bool {
1632 a = len(h.tab.Panes) - 1
1639 var curmacro []interface{}
1640 var recordingMacro bool
1642 // ToggleMacro toggles recording of a macro
1643 func (h *BufPane) ToggleMacro() bool {
1644 recordingMacro = !recordingMacro
1646 curmacro = []interface{}{}
1647 InfoBar.Message("Recording")
1649 InfoBar.Message("Stopped recording")
1655 // PlayMacro plays back the most recently recorded macro
1656 func (h *BufPane) PlayMacro() bool {
1660 for _, action := range curmacro {
1661 switch t := action.(type) {
1664 case func(*BufPane) bool:
1672 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1673 func (h *BufPane) SpawnMultiCursor() bool {
1674 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1675 if !spawner.HasSelection() {
1676 spawner.SelectWord()
1682 sel := spawner.GetSelection()
1683 searchStart := spawner.CurSelection[1]
1685 search := string(sel)
1686 search = regexp.QuoteMeta(search)
1688 search = "\\b" + search + "\\b"
1690 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1695 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1696 c.SetSelectionStart(match[0])
1697 c.SetSelectionEnd(match[1])
1698 c.OrigSelection[0] = c.CurSelection[0]
1699 c.OrigSelection[1] = c.CurSelection[1]
1700 c.Loc = c.CurSelection[1]
1703 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1704 h.Buf.MergeCursors()
1706 InfoBar.Message("No matches found")
1713 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1714 func (h *BufPane) SpawnMultiCursorUp() bool {
1715 if h.Cursor.Y == 0 {
1718 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1721 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1723 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1724 h.Buf.MergeCursors()
1730 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1731 func (h *BufPane) SpawnMultiCursorDown() bool {
1732 if h.Cursor.Y+1 == h.Buf.LinesNum() {
1735 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1738 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1740 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1741 h.Buf.MergeCursors()
1746 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1747 func (h *BufPane) SpawnMultiCursorSelect() bool {
1748 // Avoid cases where multiple cursors already exist, that would create problems
1749 if h.Buf.NumCursors() > 1 {
1756 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1758 startLine, endLine = b, a
1760 startLine, endLine = a, b
1763 if h.Cursor.HasSelection() {
1764 h.Cursor.ResetSelection()
1765 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1767 for i := startLine; i <= endLine; i++ {
1768 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1772 h.Buf.MergeCursors()
1776 InfoBar.Message("Added cursors from selection")
1780 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1781 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1783 mx, my := e.Position()
1784 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1785 c := buffer.NewCursor(b, mouseLoc)
1792 // SkipMultiCursor moves the current multiple cursor to the next available position
1793 func (h *BufPane) SkipMultiCursor() bool {
1794 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1795 sel := lastC.GetSelection()
1796 searchStart := lastC.CurSelection[1]
1798 search := string(sel)
1799 search = regexp.QuoteMeta(search)
1801 search = "\\b" + search + "\\b"
1804 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1809 lastC.SetSelectionStart(match[0])
1810 lastC.SetSelectionEnd(match[1])
1811 lastC.OrigSelection[0] = lastC.CurSelection[0]
1812 lastC.OrigSelection[1] = lastC.CurSelection[1]
1813 lastC.Loc = lastC.CurSelection[1]
1815 h.Buf.MergeCursors()
1816 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1818 InfoBar.Message("No matches found")
1824 // RemoveMultiCursor removes the latest multiple cursor
1825 func (h *BufPane) RemoveMultiCursor() bool {
1826 if h.Buf.NumCursors() > 1 {
1827 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1828 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1829 h.Buf.UpdateCursors()
1837 // RemoveAllMultiCursors removes all cursors except the base cursor
1838 func (h *BufPane) RemoveAllMultiCursors() bool {
1839 h.Buf.ClearCursors()
1845 // None is an action that does nothing
1846 func (h *BufPane) None() bool {