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/v2"
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()
99 // ScrollUpAction scrolls the view up
100 func (h *BufPane) ScrollUpAction() bool {
101 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
105 // ScrollDownAction scrolls the view up
106 func (h *BufPane) ScrollDownAction() bool {
107 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
111 // Center centers the view on the cursor
112 func (h *BufPane) Center() bool {
114 v.StartLine = h.Cursor.Y - v.Height/2
115 if v.StartLine+v.Height > h.Buf.LinesNum() {
116 v.StartLine = h.Buf.LinesNum() - v.Height
126 // CursorUp moves the cursor up
127 func (h *BufPane) CursorUp() bool {
128 h.Cursor.Deselect(true)
134 // CursorDown moves the cursor down
135 func (h *BufPane) CursorDown() bool {
136 h.Cursor.Deselect(true)
142 // CursorLeft moves the cursor left
143 func (h *BufPane) CursorLeft() bool {
144 if h.Cursor.HasSelection() {
145 h.Cursor.Deselect(true)
147 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
148 tabmovement := h.Buf.Settings["tabmovement"].(bool)
149 if tabstospaces && tabmovement {
150 tabsize := int(h.Buf.Settings["tabsize"].(float64))
151 line := h.Buf.LineBytes(h.Cursor.Y)
152 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
153 for i := 0; i < tabsize; i++ {
167 // CursorRight moves the cursor right
168 func (h *BufPane) CursorRight() bool {
169 if h.Cursor.HasSelection() {
170 h.Cursor.Deselect(false)
171 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
173 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
174 tabmovement := h.Buf.Settings["tabmovement"].(bool)
175 if tabstospaces && tabmovement {
176 tabsize := int(h.Buf.Settings["tabsize"].(float64))
177 line := h.Buf.LineBytes(h.Cursor.Y)
178 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]) {
179 for i := 0; i < tabsize; i++ {
194 // WordRight moves the cursor one word to the right
195 func (h *BufPane) WordRight() bool {
196 h.Cursor.Deselect(false)
202 // WordLeft moves the cursor one word to the left
203 func (h *BufPane) WordLeft() bool {
204 h.Cursor.Deselect(true)
210 // SelectUp selects up one line
211 func (h *BufPane) SelectUp() bool {
212 if !h.Cursor.HasSelection() {
213 h.Cursor.OrigSelection[0] = h.Cursor.Loc
216 h.Cursor.SelectTo(h.Cursor.Loc)
221 // SelectDown selects down one line
222 func (h *BufPane) SelectDown() bool {
223 if !h.Cursor.HasSelection() {
224 h.Cursor.OrigSelection[0] = h.Cursor.Loc
227 h.Cursor.SelectTo(h.Cursor.Loc)
232 // SelectLeft selects the character to the left of the cursor
233 func (h *BufPane) SelectLeft() bool {
236 if loc.GreaterThan(count) {
239 if !h.Cursor.HasSelection() {
240 h.Cursor.OrigSelection[0] = loc
243 h.Cursor.SelectTo(h.Cursor.Loc)
248 // SelectRight selects the character to the right of the cursor
249 func (h *BufPane) SelectRight() bool {
252 if loc.GreaterThan(count) {
255 if !h.Cursor.HasSelection() {
256 h.Cursor.OrigSelection[0] = loc
259 h.Cursor.SelectTo(h.Cursor.Loc)
264 // SelectWordRight selects the word to the right of the cursor
265 func (h *BufPane) SelectWordRight() bool {
266 if !h.Cursor.HasSelection() {
267 h.Cursor.OrigSelection[0] = h.Cursor.Loc
270 h.Cursor.SelectTo(h.Cursor.Loc)
275 // SelectWordLeft selects the word to the left of the cursor
276 func (h *BufPane) SelectWordLeft() bool {
277 if !h.Cursor.HasSelection() {
278 h.Cursor.OrigSelection[0] = h.Cursor.Loc
281 h.Cursor.SelectTo(h.Cursor.Loc)
286 // StartOfText moves the cursor to the start of the text of the line
287 func (h *BufPane) StartOfText() bool {
288 h.Cursor.Deselect(true)
289 h.Cursor.StartOfText()
294 // StartOfTextToggle toggles the cursor between the start of the text of the line
295 // and the start of the line
296 func (h *BufPane) StartOfTextToggle() bool {
297 h.Cursor.Deselect(true)
298 if h.Cursor.IsStartOfText() {
301 h.Cursor.StartOfText()
307 // StartOfLine moves the cursor to the start of the line
308 func (h *BufPane) StartOfLine() bool {
309 h.Cursor.Deselect(true)
315 // EndOfLine moves the cursor to the end of the line
316 func (h *BufPane) EndOfLine() bool {
317 h.Cursor.Deselect(true)
323 // SelectLine selects the entire current line
324 func (h *BufPane) SelectLine() bool {
325 h.Cursor.SelectLine()
330 // SelectToStartOfText selects to the start of the text on the current line
331 func (h *BufPane) SelectToStartOfText() bool {
332 if !h.Cursor.HasSelection() {
333 h.Cursor.OrigSelection[0] = h.Cursor.Loc
335 h.Cursor.StartOfText()
336 h.Cursor.SelectTo(h.Cursor.Loc)
341 // SelectToStartOfTextToggle toggles the selection between the start of the text
342 // on the current line and the start of the line
343 func (h *BufPane) SelectToStartOfTextToggle() bool {
344 if !h.Cursor.HasSelection() {
345 h.Cursor.OrigSelection[0] = h.Cursor.Loc
347 if h.Cursor.IsStartOfText() {
350 h.Cursor.StartOfText()
352 h.Cursor.SelectTo(h.Cursor.Loc)
357 // SelectToStartOfLine selects to the start of the current line
358 func (h *BufPane) SelectToStartOfLine() bool {
359 if !h.Cursor.HasSelection() {
360 h.Cursor.OrigSelection[0] = h.Cursor.Loc
363 h.Cursor.SelectTo(h.Cursor.Loc)
368 // SelectToEndOfLine selects to the end of the current line
369 func (h *BufPane) SelectToEndOfLine() bool {
370 if !h.Cursor.HasSelection() {
371 h.Cursor.OrigSelection[0] = h.Cursor.Loc
374 h.Cursor.SelectTo(h.Cursor.Loc)
379 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
380 func (h *BufPane) ParagraphPrevious() bool {
382 for line = h.Cursor.Y; line > 0; line-- {
383 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
389 // If no empty line found. move cursor to end of buffer
391 h.Cursor.Loc = h.Buf.Start()
397 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
398 func (h *BufPane) ParagraphNext() bool {
400 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
401 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
407 // If no empty line found. move cursor to end of buffer
408 if line == h.Buf.LinesNum() {
409 h.Cursor.Loc = h.Buf.End()
415 // Retab changes all tabs to spaces or all spaces to tabs depending
416 // on the user's settings
417 func (h *BufPane) Retab() bool {
423 // CursorStart moves the cursor to the start of the buffer
424 func (h *BufPane) CursorStart() bool {
425 h.Cursor.Deselect(true)
428 h.Cursor.StoreVisualX()
433 // CursorEnd moves the cursor to the end of the buffer
434 func (h *BufPane) CursorEnd() bool {
435 h.Cursor.Deselect(true)
436 h.Cursor.Loc = h.Buf.End()
437 h.Cursor.StoreVisualX()
442 // SelectToStart selects the text from the cursor to the start of the buffer
443 func (h *BufPane) SelectToStart() bool {
444 if !h.Cursor.HasSelection() {
445 h.Cursor.OrigSelection[0] = h.Cursor.Loc
448 h.Cursor.SelectTo(h.Buf.Start())
453 // SelectToEnd selects the text from the cursor to the end of the buffer
454 func (h *BufPane) SelectToEnd() bool {
455 if !h.Cursor.HasSelection() {
456 h.Cursor.OrigSelection[0] = h.Cursor.Loc
459 h.Cursor.SelectTo(h.Buf.End())
464 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
465 func (h *BufPane) InsertNewline() bool {
467 if h.Cursor.HasSelection() {
468 h.Cursor.DeleteSelection()
469 h.Cursor.ResetSelection()
472 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
474 h.Buf.Insert(h.Cursor.Loc, "\n")
477 if h.Buf.Settings["autoindent"].(bool) {
481 h.Buf.Insert(h.Cursor.Loc, string(ws))
482 // for i := 0; i < len(ws); i++ {
486 // Remove the whitespaces if keepautoindent setting is off
487 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
488 line := h.Buf.LineBytes(h.Cursor.Y - 1)
489 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
492 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
497 // Backspace deletes the previous character
498 func (h *BufPane) Backspace() bool {
499 if h.Cursor.HasSelection() {
500 h.Cursor.DeleteSelection()
501 h.Cursor.ResetSelection()
502 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
503 // We have to do something a bit hacky here because we want to
504 // delete the line by first moving left and then deleting backwards
505 // but the undo redo would place the cursor in the wrong place
506 // So instead we move left, save the position, move back, delete
507 // and restore the position
509 // If the user is using spaces instead of tabs and they are deleting
510 // whitespace at the start of the line, we should delete as if it's a
511 // tab (tabSize number of spaces)
512 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
513 tabSize := int(h.Buf.Settings["tabsize"].(float64))
514 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
516 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
519 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
522 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
527 // DeleteWordRight deletes the word to the right of the cursor
528 func (h *BufPane) DeleteWordRight() bool {
530 if h.Cursor.HasSelection() {
531 h.Cursor.DeleteSelection()
532 h.Cursor.ResetSelection()
538 // DeleteWordLeft deletes the word to the left of the cursor
539 func (h *BufPane) DeleteWordLeft() bool {
541 if h.Cursor.HasSelection() {
542 h.Cursor.DeleteSelection()
543 h.Cursor.ResetSelection()
549 // Delete deletes the next character
550 func (h *BufPane) Delete() bool {
551 if h.Cursor.HasSelection() {
552 h.Cursor.DeleteSelection()
553 h.Cursor.ResetSelection()
556 if loc.LessThan(h.Buf.End()) {
557 h.Buf.Remove(loc, loc.Move(1, h.Buf))
564 // IndentSelection indents the current selection
565 func (h *BufPane) IndentSelection() bool {
566 if h.Cursor.HasSelection() {
567 start := h.Cursor.CurSelection[0]
568 end := h.Cursor.CurSelection[1]
570 start, end = end, start
571 h.Cursor.SetSelectionStart(start)
572 h.Cursor.SetSelectionEnd(end)
576 endY := end.Move(-1, h.Buf).Y
577 endX := end.Move(-1, h.Buf).X
578 tabsize := int(h.Buf.Settings["tabsize"].(float64))
579 indentsize := len(h.Buf.IndentString(tabsize))
580 for y := startY; y <= endY; y++ {
581 if len(h.Buf.LineBytes(y)) > 0 {
582 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
583 if y == startY && start.X > 0 {
584 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
587 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
591 h.Buf.RelocateCursors()
599 // IndentLine moves the current line forward one indentation
600 func (h *BufPane) IndentLine() bool {
601 if h.Cursor.HasSelection() {
605 tabsize := int(h.Buf.Settings["tabsize"].(float64))
606 indentstr := h.Buf.IndentString(tabsize)
607 h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
608 h.Buf.RelocateCursors()
613 // OutdentLine moves the current line back one indentation
614 func (h *BufPane) OutdentLine() bool {
615 if h.Cursor.HasSelection() {
619 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
620 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
623 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
625 h.Buf.RelocateCursors()
630 // OutdentSelection takes the current selection and moves it back one indent level
631 func (h *BufPane) OutdentSelection() bool {
632 if h.Cursor.HasSelection() {
633 start := h.Cursor.CurSelection[0]
634 end := h.Cursor.CurSelection[1]
636 start, end = end, start
637 h.Cursor.SetSelectionStart(start)
638 h.Cursor.SetSelectionEnd(end)
642 endY := end.Move(-1, h.Buf).Y
643 for y := startY; y <= endY; y++ {
644 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
645 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
648 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
651 h.Buf.RelocateCursors()
659 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
660 func (h *BufPane) Autocomplete() bool {
663 if h.Cursor.HasSelection() {
670 r := h.Cursor.RuneUnder(h.Cursor.X)
671 prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
672 if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
673 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
677 if b.HasSuggestions {
678 b.CycleAutocomplete(true)
681 return b.Autocomplete(buffer.BufferComplete)
684 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
685 func (h *BufPane) CycleAutocompleteBack() bool {
686 if h.Cursor.HasSelection() {
690 if h.Buf.HasSuggestions {
691 h.Buf.CycleAutocomplete(false)
697 // InsertTab inserts a tab or spaces
698 func (h *BufPane) InsertTab() bool {
700 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
701 tabBytes := len(indent)
702 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
703 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
708 // SaveAll saves all open buffers
709 func (h *BufPane) SaveAll() bool {
710 for _, b := range buffer.OpenBuffers {
716 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
717 func (h *BufPane) SaveCB(action string, callback func()) bool {
718 // If this is an empty buffer, ask for a filename
719 if h.Buf.Path == "" {
720 h.SaveAsCB(action, callback)
722 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
730 // Save the buffer to disk
731 func (h *BufPane) Save() bool {
732 return h.SaveCB("Save", nil)
735 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
736 // The callback is only called if the save was successful
737 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
738 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
740 // the filename might or might not be quoted, so unquote first then join the strings.
741 args, err := shellquote.Split(resp)
743 InfoBar.Error("Error parsing arguments: ", err)
747 InfoBar.Error("No filename given")
750 filename := strings.Join(args, " ")
751 noPrompt := h.saveBufToFile(filename, action, callback)
753 h.completeAction(action)
760 // SaveAs saves the buffer to disk with the given name
761 func (h *BufPane) SaveAs() bool {
762 return h.SaveAsCB("SaveAs", nil)
765 // This function saves the buffer to `filename` and changes the buffer's path and name
766 // to `filename` if the save is successful
767 // The callback is only called if the save was successful
768 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
769 err := h.Buf.SaveAs(filename)
771 if strings.HasSuffix(err.Error(), "permission denied") {
772 saveWithSudo := func() {
773 err = h.Buf.SaveAsWithSudo(filename)
777 h.Buf.Path = filename
778 h.Buf.SetName(filename)
779 InfoBar.Message("Saved " + filename)
785 if h.Buf.Settings["autosu"].(bool) {
788 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
789 if yes && !canceled {
791 h.completeAction(action)
800 h.Buf.Path = filename
801 h.Buf.SetName(filename)
802 InfoBar.Message("Saved " + filename)
810 // Find opens a prompt and searches forward for the input
811 func (h *BufPane) Find() bool {
815 // FindLiteral is the same as Find() but does not support regular expressions
816 func (h *BufPane) FindLiteral() bool {
820 // Search searches for a given string/regex in the buffer and selects the next
821 // match if a match is found
822 // This function affects lastSearch and lastSearchRegex (saved searches) for
823 // use with FindNext and FindPrevious
824 func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
825 match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
830 h.Cursor.SetSelectionStart(match[0])
831 h.Cursor.SetSelectionEnd(match[1])
832 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
833 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
834 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
836 h.lastSearchRegex = useRegex
839 h.Cursor.ResetSelection()
844 func (h *BufPane) find(useRegex bool) bool {
845 h.searchOrig = h.Cursor.Loc
848 prompt = "Find (regex): "
850 var eventCallback func(resp string)
851 if h.Buf.Settings["findontype"].(bool) {
852 eventCallback = func(resp string) {
853 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
855 h.Cursor.SetSelectionStart(match[0])
856 h.Cursor.SetSelectionEnd(match[1])
857 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
858 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
859 h.Cursor.GotoLoc(match[1])
861 h.Cursor.GotoLoc(h.searchOrig)
862 h.Cursor.ResetSelection()
867 InfoBar.Prompt(prompt, "", "Find", eventCallback, func(resp string, canceled bool) {
870 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, 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
883 h.Cursor.ResetSelection()
884 InfoBar.Message("No matches found")
887 h.Cursor.ResetSelection()
895 // FindNext searches forwards for the last used search term
896 func (h *BufPane) FindNext() bool {
897 // If the cursor is at the start of a selection and we search we want
898 // to search from the end of the selection in the case that
899 // the selection is a search result in which case we wouldn't move at
900 // at all which would be bad
901 searchLoc := h.Cursor.Loc
902 if h.Cursor.HasSelection() {
903 searchLoc = h.Cursor.CurSelection[1]
905 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex)
910 h.Cursor.SetSelectionStart(match[0])
911 h.Cursor.SetSelectionEnd(match[1])
912 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
913 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
914 h.Cursor.Loc = h.Cursor.CurSelection[1]
916 h.Cursor.ResetSelection()
922 // FindPrevious searches backwards for the last used search term
923 func (h *BufPane) FindPrevious() bool {
924 // If the cursor is at the end of a selection and we search we want
925 // to search from the beginning of the selection in the case that
926 // the selection is a search result in which case we wouldn't move at
927 // at all which would be bad
928 searchLoc := h.Cursor.Loc
929 if h.Cursor.HasSelection() {
930 searchLoc = h.Cursor.CurSelection[0]
932 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex)
937 h.Cursor.SetSelectionStart(match[0])
938 h.Cursor.SetSelectionEnd(match[1])
939 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
940 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
941 h.Cursor.Loc = h.Cursor.CurSelection[1]
943 h.Cursor.ResetSelection()
949 // Undo undoes the last action
950 func (h *BufPane) Undo() bool {
952 InfoBar.Message("Undid action")
957 // Redo redoes the last action
958 func (h *BufPane) Redo() bool {
960 InfoBar.Message("Redid action")
965 // Copy the selection to the system clipboard
966 func (h *BufPane) Copy() bool {
967 if h.Cursor.HasSelection() {
968 h.Cursor.CopySelection(clipboard.ClipboardReg)
970 InfoBar.Message("Copied selection")
976 // Copy the current line to the clipboard
977 func (h *BufPane) CopyLine() bool {
978 if h.Cursor.HasSelection() {
981 h.Cursor.SelectLine()
982 h.Cursor.CopySelection(clipboard.ClipboardReg)
984 InfoBar.Message("Copied line")
986 h.Cursor.Deselect(true)
991 // CutLine cuts the current line to the clipboard
992 func (h *BufPane) CutLine() bool {
993 h.Cursor.SelectLine()
994 if !h.Cursor.HasSelection() {
998 if h.Cursor.HasSelection() {
999 if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
1002 clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1005 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
1009 h.lastCutTime = time.Now()
1010 h.Cursor.DeleteSelection()
1011 h.Cursor.ResetSelection()
1012 InfoBar.Message("Cut line")
1017 // Cut the selection to the system clipboard
1018 func (h *BufPane) Cut() bool {
1019 if h.Cursor.HasSelection() {
1020 h.Cursor.CopySelection(clipboard.ClipboardReg)
1021 h.Cursor.DeleteSelection()
1022 h.Cursor.ResetSelection()
1024 InfoBar.Message("Cut selection")
1033 // DuplicateLine duplicates the current line or selection
1034 func (h *BufPane) DuplicateLine() bool {
1035 if h.Cursor.HasSelection() {
1036 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1039 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1043 InfoBar.Message("Duplicated line")
1048 // DeleteLine deletes the current line
1049 func (h *BufPane) DeleteLine() bool {
1050 h.Cursor.SelectLine()
1051 if !h.Cursor.HasSelection() {
1054 h.Cursor.DeleteSelection()
1055 h.Cursor.ResetSelection()
1056 InfoBar.Message("Deleted line")
1061 // MoveLinesUp moves up the current line or selected lines if any
1062 func (h *BufPane) MoveLinesUp() bool {
1063 if h.Cursor.HasSelection() {
1064 if h.Cursor.CurSelection[0].Y == 0 {
1065 InfoBar.Message("Cannot move further up")
1068 start := h.Cursor.CurSelection[0].Y
1069 end := h.Cursor.CurSelection[1].Y
1072 end, start = start, end
1077 if h.Cursor.CurSelection[sel].X != 0 {
1088 h.Cursor.CurSelection[sel].Y -= 1
1091 if h.Cursor.Loc.Y == 0 {
1092 InfoBar.Message("Cannot move further up")
1105 // MoveLinesDown moves down the current line or selected lines if any
1106 func (h *BufPane) MoveLinesDown() bool {
1107 if h.Cursor.HasSelection() {
1108 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1109 InfoBar.Message("Cannot move further down")
1112 start := h.Cursor.CurSelection[0].Y
1113 end := h.Cursor.CurSelection[1].Y
1116 end, start = start, end
1120 if h.Cursor.CurSelection[sel].X != 0 {
1124 h.Buf.MoveLinesDown(
1129 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1130 InfoBar.Message("Cannot move further down")
1133 h.Buf.MoveLinesDown(
1143 // Paste whatever is in the system clipboard into the buffer
1144 // Delete and paste if the user has a selection
1145 func (h *BufPane) Paste() bool {
1146 clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1156 // PastePrimary pastes from the primary clipboard (only use on linux)
1157 func (h *BufPane) PastePrimary() bool {
1158 clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
1168 func (h *BufPane) paste(clip string) {
1169 if h.Buf.Settings["smartpaste"].(bool) {
1170 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1171 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1172 clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
1176 if h.Cursor.HasSelection() {
1177 h.Cursor.DeleteSelection()
1178 h.Cursor.ResetSelection()
1181 h.Buf.Insert(h.Cursor.Loc, clip)
1182 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1184 InfoBar.Message("Pasted clipboard")
1187 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1188 // currently on a brace
1189 func (h *BufPane) JumpToMatchingBrace() bool {
1190 for _, bp := range buffer.BracePairs {
1191 r := h.Cursor.RuneUnder(h.Cursor.X)
1192 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1193 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1194 matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1197 h.Cursor.GotoLoc(matchingBrace)
1199 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1212 // SelectAll selects the entire buffer
1213 func (h *BufPane) SelectAll() bool {
1214 h.Cursor.SetSelectionStart(h.Buf.Start())
1215 h.Cursor.SetSelectionEnd(h.Buf.End())
1216 // Put the cursor at the beginning
1223 // OpenFile opens a new file in the buffer
1224 func (h *BufPane) OpenFile() bool {
1225 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1227 h.HandleCommand(resp)
1233 // OpenFile opens a new file in the buffer
1234 func (h *BufPane) JumpLine() bool {
1235 InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1237 h.HandleCommand(resp)
1243 // Start moves the viewport to the start of the buffer
1244 func (h *BufPane) Start() bool {
1251 // End moves the viewport to the end of the buffer
1252 func (h *BufPane) End() bool {
1253 // TODO: softwrap problems?
1255 if v.Height > h.Buf.LinesNum() {
1259 v.StartLine = h.Buf.LinesNum() - v.Height
1265 // PageUp scrolls the view up a page
1266 func (h *BufPane) PageUp() bool {
1268 if v.StartLine > v.Height {
1269 h.ScrollUp(v.Height)
1277 // PageDown scrolls the view down a page
1278 func (h *BufPane) PageDown() bool {
1280 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1281 h.ScrollDown(v.Height)
1282 } else if h.Buf.LinesNum() >= v.Height {
1283 v.StartLine = h.Buf.LinesNum() - v.Height
1288 // SelectPageUp selects up one page
1289 func (h *BufPane) SelectPageUp() bool {
1290 if !h.Cursor.HasSelection() {
1291 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1293 h.Cursor.UpN(h.GetView().Height)
1294 h.Cursor.SelectTo(h.Cursor.Loc)
1299 // SelectPageDown selects down one page
1300 func (h *BufPane) SelectPageDown() bool {
1301 if !h.Cursor.HasSelection() {
1302 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1304 h.Cursor.DownN(h.GetView().Height)
1305 h.Cursor.SelectTo(h.Cursor.Loc)
1310 // CursorPageUp places the cursor a page up
1311 func (h *BufPane) CursorPageUp() bool {
1312 h.Cursor.Deselect(true)
1314 if h.Cursor.HasSelection() {
1315 h.Cursor.Loc = h.Cursor.CurSelection[0]
1316 h.Cursor.ResetSelection()
1317 h.Cursor.StoreVisualX()
1319 h.Cursor.UpN(h.GetView().Height)
1324 // CursorPageDown places the cursor a page up
1325 func (h *BufPane) CursorPageDown() bool {
1326 h.Cursor.Deselect(false)
1328 if h.Cursor.HasSelection() {
1329 h.Cursor.Loc = h.Cursor.CurSelection[1]
1330 h.Cursor.ResetSelection()
1331 h.Cursor.StoreVisualX()
1333 h.Cursor.DownN(h.GetView().Height)
1338 // HalfPageUp scrolls the view up half a page
1339 func (h *BufPane) HalfPageUp() bool {
1341 if v.StartLine > v.Height/2 {
1342 h.ScrollUp(v.Height / 2)
1350 // HalfPageDown scrolls the view down half a page
1351 func (h *BufPane) HalfPageDown() bool {
1353 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1354 h.ScrollDown(v.Height / 2)
1355 } else if h.Buf.LinesNum() >= v.Height {
1356 v.StartLine = h.Buf.LinesNum() - v.Height
1362 // ToggleDiffGutter turns the diff gutter off and on
1363 func (h *BufPane) ToggleDiffGutter() bool {
1364 if !h.Buf.Settings["diffgutter"].(bool) {
1365 h.Buf.Settings["diffgutter"] = true
1366 h.Buf.UpdateDiff(func(synchronous bool) {
1369 InfoBar.Message("Enabled diff gutter")
1371 h.Buf.Settings["diffgutter"] = false
1372 InfoBar.Message("Disabled diff gutter")
1377 // ToggleRuler turns line numbers off and on
1378 func (h *BufPane) ToggleRuler() bool {
1379 if !h.Buf.Settings["ruler"].(bool) {
1380 h.Buf.Settings["ruler"] = true
1381 InfoBar.Message("Enabled ruler")
1383 h.Buf.Settings["ruler"] = false
1384 InfoBar.Message("Disabled ruler")
1389 // ClearStatus clears the messenger bar
1390 func (h *BufPane) ClearStatus() bool {
1395 // ToggleHelp toggles the help screen
1396 func (h *BufPane) ToggleHelp() bool {
1397 if h.Buf.Type == buffer.BTHelp {
1405 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1406 func (h *BufPane) ToggleKeyMenu() bool {
1407 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1412 // ShellMode opens a terminal to run a shell command
1413 func (h *BufPane) ShellMode() bool {
1414 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1416 // The true here is for openTerm to make the command interactive
1417 shell.RunInteractiveShell(resp, true, false)
1424 // CommandMode lets the user enter a command
1425 func (h *BufPane) CommandMode() bool {
1426 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1428 h.HandleCommand(resp)
1434 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1435 func (h *BufPane) ToggleOverwriteMode() bool {
1436 h.isOverwriteMode = !h.isOverwriteMode
1440 // Escape leaves current mode
1441 func (h *BufPane) Escape() bool {
1445 // Deselect deselects on the current cursor
1446 func (h *BufPane) Deselect() bool {
1447 h.Cursor.Deselect(true)
1451 // ClearInfo clears the infobar
1452 func (h *BufPane) ClearInfo() bool {
1457 // Quit this will close the current tab or view that is open
1458 func (h *BufPane) Quit() bool {
1461 if len(MainTab().Panes) > 1 {
1463 } else if len(Tabs.List) > 1 {
1464 Tabs.RemoveTab(h.splitID)
1466 screen.Screen.Fini()
1471 if h.Buf.Modified() {
1472 if config.GlobalSettings["autosave"].(float64) > 0 {
1473 // autosave on means we automatically save when quitting
1474 h.SaveCB("Quit", func() {
1478 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1479 if !canceled && !yes {
1481 } else if !canceled && yes {
1482 h.SaveCB("Quit", func() {
1494 // QuitAll quits the whole editor; all splits and tabs
1495 func (h *BufPane) QuitAll() bool {
1496 anyModified := false
1497 for _, b := range buffer.OpenBuffers {
1505 for _, b := range buffer.OpenBuffers {
1508 screen.Screen.Fini()
1514 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1515 if !canceled && yes {
1526 // AddTab adds a new tab with an empty buffer
1527 func (h *BufPane) AddTab() bool {
1528 width, height := screen.Screen.Size()
1529 iOffset := config.GetInfoBarOffset()
1530 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1531 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1533 Tabs.SetActive(len(Tabs.List) - 1)
1538 // PreviousTab switches to the previous tab in the tab list
1539 func (h *BufPane) PreviousTab() bool {
1540 tabsLen := len(Tabs.List)
1541 a := Tabs.Active() + tabsLen
1542 Tabs.SetActive((a - 1) % tabsLen)
1547 // NextTab switches to the next tab in the tab list
1548 func (h *BufPane) NextTab() bool {
1550 Tabs.SetActive((a + 1) % len(Tabs.List))
1555 // VSplitAction opens an empty vertical split
1556 func (h *BufPane) VSplitAction() bool {
1557 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1562 // HSplitAction opens an empty horizontal split
1563 func (h *BufPane) HSplitAction() bool {
1564 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1569 // Unsplit closes all splits in the current tab except the active one
1570 func (h *BufPane) Unsplit() bool {
1572 n := tab.GetNode(h.splitID)
1575 tab.RemovePane(tab.GetPane(h.splitID))
1577 tab.SetActive(len(tab.Panes) - 1)
1584 // NextSplit changes the view to the next split
1585 func (h *BufPane) NextSplit() bool {
1587 if a < len(h.tab.Panes)-1 {
1598 // PreviousSplit changes the view to the previous split
1599 func (h *BufPane) PreviousSplit() bool {
1604 a = len(h.tab.Panes) - 1
1611 var curmacro []interface{}
1612 var recording_macro bool
1614 // ToggleMacro toggles recording of a macro
1615 func (h *BufPane) ToggleMacro() bool {
1616 recording_macro = !recording_macro
1617 if recording_macro {
1618 curmacro = []interface{}{}
1619 InfoBar.Message("Recording")
1621 InfoBar.Message("Stopped recording")
1627 // PlayMacro plays back the most recently recorded macro
1628 func (h *BufPane) PlayMacro() bool {
1629 if recording_macro {
1632 for _, action := range curmacro {
1633 switch t := action.(type) {
1636 case func(*BufPane) bool:
1644 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1645 func (h *BufPane) SpawnMultiCursor() bool {
1646 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1647 if !spawner.HasSelection() {
1648 spawner.SelectWord()
1654 sel := spawner.GetSelection()
1655 searchStart := spawner.CurSelection[1]
1657 search := string(sel)
1658 search = regexp.QuoteMeta(search)
1660 search = "\\b" + search + "\\b"
1662 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1667 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1668 c.SetSelectionStart(match[0])
1669 c.SetSelectionEnd(match[1])
1670 c.OrigSelection[0] = c.CurSelection[0]
1671 c.OrigSelection[1] = c.CurSelection[1]
1672 c.Loc = c.CurSelection[1]
1675 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1676 h.Buf.MergeCursors()
1678 InfoBar.Message("No matches found")
1685 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1686 func (h *BufPane) SpawnMultiCursorUp() bool {
1687 if h.Cursor.Y == 0 {
1690 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1694 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1696 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1697 h.Buf.MergeCursors()
1703 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1704 func (h *BufPane) SpawnMultiCursorDown() bool {
1705 if h.Cursor.Y+1 == h.Buf.LinesNum() {
1708 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1712 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1714 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1715 h.Buf.MergeCursors()
1720 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1721 func (h *BufPane) SpawnMultiCursorSelect() bool {
1722 // Avoid cases where multiple cursors already exist, that would create problems
1723 if h.Buf.NumCursors() > 1 {
1730 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1732 startLine, endLine = b, a
1734 startLine, endLine = a, b
1737 if h.Cursor.HasSelection() {
1738 h.Cursor.ResetSelection()
1739 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1741 for i := startLine; i <= endLine; i++ {
1742 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1746 h.Buf.MergeCursors()
1750 InfoBar.Message("Added cursors from selection")
1754 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1755 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1757 mx, my := e.Position()
1758 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1759 c := buffer.NewCursor(b, mouseLoc)
1766 // SkipMultiCursor moves the current multiple cursor to the next available position
1767 func (h *BufPane) SkipMultiCursor() bool {
1768 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1769 sel := lastC.GetSelection()
1770 searchStart := lastC.CurSelection[1]
1772 search := string(sel)
1773 search = regexp.QuoteMeta(search)
1775 search = "\\b" + search + "\\b"
1778 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1783 lastC.SetSelectionStart(match[0])
1784 lastC.SetSelectionEnd(match[1])
1785 lastC.OrigSelection[0] = lastC.CurSelection[0]
1786 lastC.OrigSelection[1] = lastC.CurSelection[1]
1787 lastC.Loc = lastC.CurSelection[1]
1789 h.Buf.MergeCursors()
1790 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1792 InfoBar.Message("No matches found")
1798 // RemoveMultiCursor removes the latest multiple cursor
1799 func (h *BufPane) RemoveMultiCursor() bool {
1800 if h.Buf.NumCursors() > 1 {
1801 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1802 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1803 h.Buf.UpdateCursors()
1811 // RemoveAllMultiCursors removes all cursors except the base cursor
1812 func (h *BufPane) RemoveAllMultiCursors() bool {
1813 h.Buf.ClearCursors()
1819 // None is an action that does nothing
1820 func (h *BufPane) None() bool {