13 shellquote "github.com/kballard/go-shellquote"
14 "github.com/zyedidia/micro/v2/internal/buffer"
15 "github.com/zyedidia/micro/v2/internal/clipboard"
16 "github.com/zyedidia/micro/v2/internal/config"
17 "github.com/zyedidia/micro/v2/internal/display"
18 "github.com/zyedidia/micro/v2/internal/screen"
19 "github.com/zyedidia/micro/v2/internal/shell"
20 "github.com/zyedidia/micro/v2/internal/util"
21 "github.com/zyedidia/tcell/v2"
24 // ScrollUp is not an action
25 func (h *BufPane) ScrollUp(n int) {
27 v.StartLine = h.Scroll(v.StartLine, -n)
31 // ScrollDown is not an action
32 func (h *BufPane) ScrollDown(n int) {
34 v.StartLine = h.Scroll(v.StartLine, n)
38 // ScrollAdjust can be used to shift the view so that the last line is at the
39 // bottom if the user has scrolled past the last line.
40 func (h *BufPane) ScrollAdjust() {
42 end := h.SLocFromLoc(h.Buf.End())
43 if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
44 v.StartLine = h.Scroll(end, -h.BufView().Height+1)
49 // MousePress is the event that should happen when a normal click happens
50 // This is almost always bound to left click
51 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
53 mx, my := e.Position()
54 mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
55 h.Cursor.Loc = mouseLoc
57 if b.NumCursors() > 1 {
60 h.Cursor = h.Buf.GetActiveCursor()
61 h.Cursor.Loc = mouseLoc
63 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
66 h.lastClickTime = time.Now()
72 h.Cursor.CopySelection(clipboard.PrimaryReg)
75 h.lastClickTime = time.Now()
81 h.Cursor.CopySelection(clipboard.PrimaryReg)
86 h.lastClickTime = time.Now()
88 h.Cursor.OrigSelection[0] = h.Cursor.Loc
89 h.Cursor.CurSelection[0] = h.Cursor.Loc
90 h.Cursor.CurSelection[1] = h.Cursor.Loc
92 h.mouseReleased = false
93 } else if !h.mouseReleased {
95 h.Cursor.AddLineToSelection()
96 } else if h.doubleClick {
97 h.Cursor.AddWordToSelection()
99 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
103 h.Cursor.StoreVisualX()
109 // ScrollUpAction scrolls the view up
110 func (h *BufPane) ScrollUpAction() bool {
111 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
115 // ScrollDownAction scrolls the view up
116 func (h *BufPane) ScrollDownAction() bool {
117 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
121 // Center centers the view on the cursor
122 func (h *BufPane) Center() bool {
124 v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
130 // MoveCursorUp is not an action
131 func (h *BufPane) MoveCursorUp(n int) {
132 if !h.Buf.Settings["softwrap"].(bool) {
135 vloc := h.VLocFromLoc(h.Cursor.Loc)
136 sloc := h.Scroll(vloc.SLoc, -n)
137 if sloc == vloc.SLoc {
138 // we are at the beginning of buffer
139 h.Cursor.Loc = h.Buf.Start()
140 h.Cursor.LastVisualX = 0
143 vloc.VisualX = h.Cursor.LastVisualX
144 h.Cursor.Loc = h.LocFromVLoc(vloc)
149 // MoveCursorDown is not an action
150 func (h *BufPane) MoveCursorDown(n int) {
151 if !h.Buf.Settings["softwrap"].(bool) {
154 vloc := h.VLocFromLoc(h.Cursor.Loc)
155 sloc := h.Scroll(vloc.SLoc, n)
156 if sloc == vloc.SLoc {
157 // we are at the end of buffer
158 h.Cursor.Loc = h.Buf.End()
159 vloc = h.VLocFromLoc(h.Cursor.Loc)
160 h.Cursor.LastVisualX = vloc.VisualX
163 vloc.VisualX = h.Cursor.LastVisualX
164 h.Cursor.Loc = h.LocFromVLoc(vloc)
169 // CursorUp moves the cursor up
170 func (h *BufPane) CursorUp() bool {
171 h.Cursor.Deselect(true)
177 // CursorDown moves the cursor down
178 func (h *BufPane) CursorDown() bool {
179 h.Cursor.Deselect(true)
185 // CursorLeft moves the cursor left
186 func (h *BufPane) CursorLeft() bool {
187 if h.Cursor.HasSelection() {
188 h.Cursor.Deselect(true)
190 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
191 tabmovement := h.Buf.Settings["tabmovement"].(bool)
192 if tabstospaces && tabmovement {
193 tabsize := int(h.Buf.Settings["tabsize"].(float64))
194 line := h.Buf.LineBytes(h.Cursor.Y)
195 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
196 for i := 0; i < tabsize; i++ {
210 // CursorRight moves the cursor right
211 func (h *BufPane) CursorRight() bool {
212 if h.Cursor.HasSelection() {
213 h.Cursor.Deselect(false)
214 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
216 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
217 tabmovement := h.Buf.Settings["tabmovement"].(bool)
218 if tabstospaces && tabmovement {
219 tabsize := int(h.Buf.Settings["tabsize"].(float64))
220 line := h.Buf.LineBytes(h.Cursor.Y)
221 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]) {
222 for i := 0; i < tabsize; i++ {
237 // WordRight moves the cursor one word to the right
238 func (h *BufPane) WordRight() bool {
239 h.Cursor.Deselect(false)
245 // WordLeft moves the cursor one word to the left
246 func (h *BufPane) WordLeft() bool {
247 h.Cursor.Deselect(true)
253 // SelectUp selects up one line
254 func (h *BufPane) SelectUp() bool {
255 if !h.Cursor.HasSelection() {
256 h.Cursor.OrigSelection[0] = h.Cursor.Loc
259 h.Cursor.SelectTo(h.Cursor.Loc)
264 // SelectDown selects down one line
265 func (h *BufPane) SelectDown() bool {
266 if !h.Cursor.HasSelection() {
267 h.Cursor.OrigSelection[0] = h.Cursor.Loc
270 h.Cursor.SelectTo(h.Cursor.Loc)
275 // SelectLeft selects the character to the left of the cursor
276 func (h *BufPane) SelectLeft() bool {
279 if loc.GreaterThan(count) {
282 if !h.Cursor.HasSelection() {
283 h.Cursor.OrigSelection[0] = loc
286 h.Cursor.SelectTo(h.Cursor.Loc)
291 // SelectRight selects the character to the right of the cursor
292 func (h *BufPane) SelectRight() bool {
295 if loc.GreaterThan(count) {
298 if !h.Cursor.HasSelection() {
299 h.Cursor.OrigSelection[0] = loc
302 h.Cursor.SelectTo(h.Cursor.Loc)
307 // SelectWordRight selects the word to the right of the cursor
308 func (h *BufPane) SelectWordRight() bool {
309 if !h.Cursor.HasSelection() {
310 h.Cursor.OrigSelection[0] = h.Cursor.Loc
313 h.Cursor.SelectTo(h.Cursor.Loc)
318 // SelectWordLeft selects the word to the left of the cursor
319 func (h *BufPane) SelectWordLeft() bool {
320 if !h.Cursor.HasSelection() {
321 h.Cursor.OrigSelection[0] = h.Cursor.Loc
324 h.Cursor.SelectTo(h.Cursor.Loc)
329 // StartOfText moves the cursor to the start of the text of the line
330 func (h *BufPane) StartOfText() bool {
331 h.Cursor.Deselect(true)
332 h.Cursor.StartOfText()
337 // StartOfTextToggle toggles the cursor between the start of the text of the line
338 // and the start of the line
339 func (h *BufPane) StartOfTextToggle() bool {
340 h.Cursor.Deselect(true)
341 if h.Cursor.IsStartOfText() {
344 h.Cursor.StartOfText()
350 // StartOfLine moves the cursor to the start of the line
351 func (h *BufPane) StartOfLine() bool {
352 h.Cursor.Deselect(true)
358 // EndOfLine moves the cursor to the end of the line
359 func (h *BufPane) EndOfLine() bool {
360 h.Cursor.Deselect(true)
366 // SelectLine selects the entire current line
367 func (h *BufPane) SelectLine() bool {
368 h.Cursor.SelectLine()
373 // SelectToStartOfText selects to the start of the text on the current line
374 func (h *BufPane) SelectToStartOfText() bool {
375 if !h.Cursor.HasSelection() {
376 h.Cursor.OrigSelection[0] = h.Cursor.Loc
378 h.Cursor.StartOfText()
379 h.Cursor.SelectTo(h.Cursor.Loc)
384 // SelectToStartOfTextToggle toggles the selection between the start of the text
385 // on the current line and the start of the line
386 func (h *BufPane) SelectToStartOfTextToggle() bool {
387 if !h.Cursor.HasSelection() {
388 h.Cursor.OrigSelection[0] = h.Cursor.Loc
390 if h.Cursor.IsStartOfText() {
393 h.Cursor.StartOfText()
395 h.Cursor.SelectTo(h.Cursor.Loc)
400 // SelectToStartOfLine selects to the start of the current line
401 func (h *BufPane) SelectToStartOfLine() bool {
402 if !h.Cursor.HasSelection() {
403 h.Cursor.OrigSelection[0] = h.Cursor.Loc
406 h.Cursor.SelectTo(h.Cursor.Loc)
411 // SelectToEndOfLine selects to the end of the current line
412 func (h *BufPane) SelectToEndOfLine() bool {
413 if !h.Cursor.HasSelection() {
414 h.Cursor.OrigSelection[0] = h.Cursor.Loc
417 h.Cursor.SelectTo(h.Cursor.Loc)
422 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
423 func (h *BufPane) ParagraphPrevious() bool {
425 for line = h.Cursor.Y; line > 0; line-- {
426 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
432 // If no empty line found. move cursor to end of buffer
434 h.Cursor.Loc = h.Buf.Start()
440 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
441 func (h *BufPane) ParagraphNext() bool {
443 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
444 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
450 // If no empty line found. move cursor to end of buffer
451 if line == h.Buf.LinesNum() {
452 h.Cursor.Loc = h.Buf.End()
458 // Retab changes all tabs to spaces or all spaces to tabs depending
459 // on the user's settings
460 func (h *BufPane) Retab() bool {
466 // CursorStart moves the cursor to the start of the buffer
467 func (h *BufPane) CursorStart() bool {
468 h.Cursor.Deselect(true)
471 h.Cursor.StoreVisualX()
476 // CursorEnd moves the cursor to the end of the buffer
477 func (h *BufPane) CursorEnd() bool {
478 h.Cursor.Deselect(true)
479 h.Cursor.Loc = h.Buf.End()
480 h.Cursor.StoreVisualX()
485 // SelectToStart selects the text from the cursor to the start of the buffer
486 func (h *BufPane) SelectToStart() bool {
487 if !h.Cursor.HasSelection() {
488 h.Cursor.OrigSelection[0] = h.Cursor.Loc
491 h.Cursor.SelectTo(h.Buf.Start())
496 // SelectToEnd selects the text from the cursor to the end of the buffer
497 func (h *BufPane) SelectToEnd() bool {
498 if !h.Cursor.HasSelection() {
499 h.Cursor.OrigSelection[0] = h.Cursor.Loc
502 h.Cursor.SelectTo(h.Buf.End())
507 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
508 func (h *BufPane) InsertNewline() bool {
510 if h.Cursor.HasSelection() {
511 h.Cursor.DeleteSelection()
512 h.Cursor.ResetSelection()
515 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
517 h.Buf.Insert(h.Cursor.Loc, "\n")
520 if h.Buf.Settings["autoindent"].(bool) {
524 h.Buf.Insert(h.Cursor.Loc, string(ws))
525 // for i := 0; i < len(ws); i++ {
529 // Remove the whitespaces if keepautoindent setting is off
530 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
531 line := h.Buf.LineBytes(h.Cursor.Y - 1)
532 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
535 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
540 // Backspace deletes the previous character
541 func (h *BufPane) Backspace() bool {
542 if h.Cursor.HasSelection() {
543 h.Cursor.DeleteSelection()
544 h.Cursor.ResetSelection()
545 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
546 // We have to do something a bit hacky here because we want to
547 // delete the line by first moving left and then deleting backwards
548 // but the undo redo would place the cursor in the wrong place
549 // So instead we move left, save the position, move back, delete
550 // and restore the position
552 // If the user is using spaces instead of tabs and they are deleting
553 // whitespace at the start of the line, we should delete as if it's a
554 // tab (tabSize number of spaces)
555 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
556 tabSize := int(h.Buf.Settings["tabsize"].(float64))
557 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
559 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
562 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
565 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
570 // DeleteWordRight deletes the word to the right of the cursor
571 func (h *BufPane) DeleteWordRight() bool {
573 if h.Cursor.HasSelection() {
574 h.Cursor.DeleteSelection()
575 h.Cursor.ResetSelection()
581 // DeleteWordLeft deletes the word to the left of the cursor
582 func (h *BufPane) DeleteWordLeft() bool {
584 if h.Cursor.HasSelection() {
585 h.Cursor.DeleteSelection()
586 h.Cursor.ResetSelection()
592 // Delete deletes the next character
593 func (h *BufPane) Delete() bool {
594 if h.Cursor.HasSelection() {
595 h.Cursor.DeleteSelection()
596 h.Cursor.ResetSelection()
599 if loc.LessThan(h.Buf.End()) {
600 h.Buf.Remove(loc, loc.Move(1, h.Buf))
607 // IndentSelection indents the current selection
608 func (h *BufPane) IndentSelection() bool {
609 if h.Cursor.HasSelection() {
610 start := h.Cursor.CurSelection[0]
611 end := h.Cursor.CurSelection[1]
613 start, end = end, start
614 h.Cursor.SetSelectionStart(start)
615 h.Cursor.SetSelectionEnd(end)
619 endY := end.Move(-1, h.Buf).Y
620 endX := end.Move(-1, h.Buf).X
621 tabsize := int(h.Buf.Settings["tabsize"].(float64))
622 indentsize := len(h.Buf.IndentString(tabsize))
623 for y := startY; y <= endY; y++ {
624 if len(h.Buf.LineBytes(y)) > 0 {
625 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
626 if y == startY && start.X > 0 {
627 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
630 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
634 h.Buf.RelocateCursors()
642 // IndentLine moves the current line forward one indentation
643 func (h *BufPane) IndentLine() bool {
644 if h.Cursor.HasSelection() {
648 tabsize := int(h.Buf.Settings["tabsize"].(float64))
649 indentstr := h.Buf.IndentString(tabsize)
650 h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
651 h.Buf.RelocateCursors()
656 // OutdentLine moves the current line back one indentation
657 func (h *BufPane) OutdentLine() bool {
658 if h.Cursor.HasSelection() {
662 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
663 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
666 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
668 h.Buf.RelocateCursors()
673 // OutdentSelection takes the current selection and moves it back one indent level
674 func (h *BufPane) OutdentSelection() bool {
675 if h.Cursor.HasSelection() {
676 start := h.Cursor.CurSelection[0]
677 end := h.Cursor.CurSelection[1]
679 start, end = end, start
680 h.Cursor.SetSelectionStart(start)
681 h.Cursor.SetSelectionEnd(end)
685 endY := end.Move(-1, h.Buf).Y
686 for y := startY; y <= endY; y++ {
687 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
688 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
691 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
694 h.Buf.RelocateCursors()
702 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
703 func (h *BufPane) Autocomplete() bool {
706 if h.Cursor.HasSelection() {
713 r := h.Cursor.RuneUnder(h.Cursor.X)
714 prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
715 if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) {
716 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
720 if b.HasSuggestions {
721 b.CycleAutocomplete(true)
724 return b.Autocomplete(buffer.BufferComplete)
727 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
728 func (h *BufPane) CycleAutocompleteBack() bool {
729 if h.Cursor.HasSelection() {
733 if h.Buf.HasSuggestions {
734 h.Buf.CycleAutocomplete(false)
740 // InsertTab inserts a tab or spaces
741 func (h *BufPane) InsertTab() bool {
743 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
744 tabBytes := len(indent)
745 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
746 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
751 // SaveAll saves all open buffers
752 func (h *BufPane) SaveAll() bool {
753 for _, b := range buffer.OpenBuffers {
759 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
760 func (h *BufPane) SaveCB(action string, callback func()) bool {
761 // If this is an empty buffer, ask for a filename
762 if h.Buf.Path == "" {
763 h.SaveAsCB(action, callback)
765 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
773 // Save the buffer to disk
774 func (h *BufPane) Save() bool {
775 return h.SaveCB("Save", nil)
778 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
779 // The callback is only called if the save was successful
780 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
781 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
783 // the filename might or might not be quoted, so unquote first then join the strings.
784 args, err := shellquote.Split(resp)
786 InfoBar.Error("Error parsing arguments: ", err)
790 InfoBar.Error("No filename given")
793 filename := strings.Join(args, " ")
794 noPrompt := h.saveBufToFile(filename, action, callback)
796 h.completeAction(action)
803 // SaveAs saves the buffer to disk with the given name
804 func (h *BufPane) SaveAs() bool {
805 return h.SaveAsCB("SaveAs", nil)
808 // This function saves the buffer to `filename` and changes the buffer's path and name
809 // to `filename` if the save is successful
810 // The callback is only called if the save was successful
811 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
812 err := h.Buf.SaveAs(filename)
814 if errors.Is(err, fs.ErrPermission) {
815 saveWithSudo := func() {
816 err = h.Buf.SaveAsWithSudo(filename)
820 h.Buf.Path = filename
821 h.Buf.SetName(filename)
822 InfoBar.Message("Saved " + filename)
828 if h.Buf.Settings["autosu"].(bool) {
832 fmt.Sprintf("Permission denied. Do you want to save this file using %s? (y,n)", config.GlobalSettings["sucmd"].(string)),
833 func(yes, canceled bool) {
834 if yes && !canceled {
836 h.completeAction(action)
846 h.Buf.Path = filename
847 h.Buf.SetName(filename)
848 InfoBar.Message("Saved " + filename)
856 // Find opens a prompt and searches forward for the input
857 func (h *BufPane) Find() bool {
861 // FindLiteral is the same as Find() but does not support regular expressions
862 func (h *BufPane) FindLiteral() bool {
866 // Search searches for a given string/regex in the buffer and selects the next
867 // match if a match is found
868 // This function behaves the same way as Find and FindLiteral actions:
869 // it affects the buffer's LastSearch and LastSearchRegex (saved searches)
870 // for use with FindNext and FindPrevious, and turns HighlightSearch on or off
871 // according to hlsearch setting
872 func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error {
873 match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex)
878 h.Cursor.SetSelectionStart(match[0])
879 h.Cursor.SetSelectionEnd(match[1])
880 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
881 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
882 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
883 h.Buf.LastSearch = str
884 h.Buf.LastSearchRegex = useRegex
885 h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
888 h.Cursor.ResetSelection()
893 func (h *BufPane) find(useRegex bool) bool {
894 h.searchOrig = h.Cursor.Loc
897 prompt = "Find (regex): "
899 var eventCallback func(resp string)
900 if h.Buf.Settings["incsearch"].(bool) {
901 eventCallback = func(resp string) {
902 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
904 h.Cursor.SetSelectionStart(match[0])
905 h.Cursor.SetSelectionEnd(match[1])
906 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
907 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
908 h.Cursor.GotoLoc(match[1])
910 h.Cursor.GotoLoc(h.searchOrig)
911 h.Cursor.ResetSelection()
916 findCallback := func(resp string, canceled bool) {
919 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
924 h.Cursor.SetSelectionStart(match[0])
925 h.Cursor.SetSelectionEnd(match[1])
926 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
927 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
928 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
929 h.Buf.LastSearch = resp
930 h.Buf.LastSearchRegex = useRegex
931 h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
933 h.Cursor.ResetSelection()
934 InfoBar.Message("No matches found")
937 h.Cursor.ResetSelection()
941 pattern := string(h.Cursor.GetSelection())
942 if eventCallback != nil && pattern != "" {
943 eventCallback(pattern)
945 InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback)
952 // ToggleHighlightSearch toggles highlighting all instances of the last used search term
953 func (h *BufPane) ToggleHighlightSearch() bool {
954 h.Buf.HighlightSearch = !h.Buf.HighlightSearch
958 // UnhighlightSearch unhighlights all instances of the last used search term
959 func (h *BufPane) UnhighlightSearch() bool {
960 h.Buf.HighlightSearch = false
964 // FindNext searches forwards for the last used search term
965 func (h *BufPane) FindNext() bool {
966 // If the cursor is at the start of a selection and we search we want
967 // to search from the end of the selection in the case that
968 // the selection is a search result in which case we wouldn't move at
969 // at all which would be bad
970 searchLoc := h.Cursor.Loc
971 if h.Cursor.HasSelection() {
972 searchLoc = h.Cursor.CurSelection[1]
974 match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
979 h.Cursor.SetSelectionStart(match[0])
980 h.Cursor.SetSelectionEnd(match[1])
981 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
982 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
983 h.Cursor.Loc = h.Cursor.CurSelection[1]
985 h.Cursor.ResetSelection()
991 // FindPrevious searches backwards for the last used search term
992 func (h *BufPane) FindPrevious() bool {
993 // If the cursor is at the end of a selection and we search we want
994 // to search from the beginning of the selection in the case that
995 // the selection is a search result in which case we wouldn't move at
996 // at all which would be bad
997 searchLoc := h.Cursor.Loc
998 if h.Cursor.HasSelection() {
999 searchLoc = h.Cursor.CurSelection[0]
1001 match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
1006 h.Cursor.SetSelectionStart(match[0])
1007 h.Cursor.SetSelectionEnd(match[1])
1008 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
1009 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
1010 h.Cursor.Loc = h.Cursor.CurSelection[1]
1012 h.Cursor.ResetSelection()
1018 // Undo undoes the last action
1019 func (h *BufPane) Undo() bool {
1021 InfoBar.Message("Undid action")
1026 // Redo redoes the last action
1027 func (h *BufPane) Redo() bool {
1029 InfoBar.Message("Redid action")
1034 // Copy the selection to the system clipboard
1035 func (h *BufPane) Copy() bool {
1036 if h.Cursor.HasSelection() {
1037 h.Cursor.CopySelection(clipboard.ClipboardReg)
1039 InfoBar.Message("Copied selection")
1045 // CopyLine copies the current line to the clipboard
1046 func (h *BufPane) CopyLine() bool {
1047 if h.Cursor.HasSelection() {
1050 h.Cursor.SelectLine()
1051 h.Cursor.CopySelection(clipboard.ClipboardReg)
1053 InfoBar.Message("Copied line")
1055 h.Cursor.Deselect(true)
1060 // CutLine cuts the current line to the clipboard
1061 func (h *BufPane) CutLine() bool {
1062 h.Cursor.SelectLine()
1063 if !h.Cursor.HasSelection() {
1067 if h.Cursor.HasSelection() {
1068 if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
1071 clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1074 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip {
1078 h.lastCutTime = time.Now()
1079 h.Cursor.DeleteSelection()
1080 h.Cursor.ResetSelection()
1081 InfoBar.Message("Cut line")
1086 // Cut the selection to the system clipboard
1087 func (h *BufPane) Cut() bool {
1088 if h.Cursor.HasSelection() {
1089 h.Cursor.CopySelection(clipboard.ClipboardReg)
1090 h.Cursor.DeleteSelection()
1091 h.Cursor.ResetSelection()
1093 InfoBar.Message("Cut selection")
1101 // DuplicateLine duplicates the current line or selection
1102 func (h *BufPane) DuplicateLine() bool {
1103 var infoMessage = "Duplicated line"
1104 if h.Cursor.HasSelection() {
1105 infoMessage = "Duplicated selection"
1106 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
1109 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
1113 InfoBar.Message(infoMessage)
1118 // DeleteLine deletes the current line
1119 func (h *BufPane) DeleteLine() bool {
1120 h.Cursor.SelectLine()
1121 if !h.Cursor.HasSelection() {
1124 h.Cursor.DeleteSelection()
1125 h.Cursor.ResetSelection()
1126 InfoBar.Message("Deleted line")
1131 // MoveLinesUp moves up the current line or selected lines if any
1132 func (h *BufPane) MoveLinesUp() bool {
1133 if h.Cursor.HasSelection() {
1134 if h.Cursor.CurSelection[0].Y == 0 {
1135 InfoBar.Message("Cannot move further up")
1138 start := h.Cursor.CurSelection[0].Y
1139 end := h.Cursor.CurSelection[1].Y
1142 end, start = start, end
1147 if h.Cursor.CurSelection[sel].X != 0 {
1158 h.Cursor.CurSelection[sel].Y -= 1
1161 if h.Cursor.Loc.Y == 0 {
1162 InfoBar.Message("Cannot move further up")
1175 // MoveLinesDown moves down the current line or selected lines if any
1176 func (h *BufPane) MoveLinesDown() bool {
1177 if h.Cursor.HasSelection() {
1178 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1179 InfoBar.Message("Cannot move further down")
1182 start := h.Cursor.CurSelection[0].Y
1183 end := h.Cursor.CurSelection[1].Y
1186 end, start = start, end
1190 if h.Cursor.CurSelection[sel].X != 0 {
1194 h.Buf.MoveLinesDown(
1199 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1200 InfoBar.Message("Cannot move further down")
1203 h.Buf.MoveLinesDown(
1213 // Paste whatever is in the system clipboard into the buffer
1214 // Delete and paste if the user has a selection
1215 func (h *BufPane) Paste() bool {
1216 clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
1226 // PastePrimary pastes from the primary clipboard (only use on linux)
1227 func (h *BufPane) PastePrimary() bool {
1228 clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
1238 func (h *BufPane) paste(clip string) {
1239 if h.Buf.Settings["smartpaste"].(bool) {
1240 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1241 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1242 clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS))
1246 if h.Cursor.HasSelection() {
1247 h.Cursor.DeleteSelection()
1248 h.Cursor.ResetSelection()
1251 h.Buf.Insert(h.Cursor.Loc, clip)
1252 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1254 InfoBar.Message("Pasted clipboard")
1257 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1258 // currently on a brace
1259 func (h *BufPane) JumpToMatchingBrace() bool {
1260 for _, bp := range buffer.BracePairs {
1261 r := h.Cursor.RuneUnder(h.Cursor.X)
1262 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1263 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1264 matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1267 h.Cursor.GotoLoc(matchingBrace)
1269 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1279 // SelectAll selects the entire buffer
1280 func (h *BufPane) SelectAll() bool {
1281 h.Cursor.SetSelectionStart(h.Buf.Start())
1282 h.Cursor.SetSelectionEnd(h.Buf.End())
1283 // Put the cursor at the beginning
1290 // OpenFile opens a new file in the buffer
1291 func (h *BufPane) OpenFile() bool {
1292 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1294 h.HandleCommand(resp)
1300 // OpenFile opens a new file in the buffer
1301 func (h *BufPane) JumpLine() bool {
1302 InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
1304 h.HandleCommand(resp)
1310 // Start moves the viewport to the start of the buffer
1311 func (h *BufPane) Start() bool {
1313 v.StartLine = display.SLoc{0, 0}
1318 // End moves the viewport to the end of the buffer
1319 func (h *BufPane) End() bool {
1321 v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
1326 // PageUp scrolls the view up a page
1327 func (h *BufPane) PageUp() bool {
1328 h.ScrollUp(h.BufView().Height)
1332 // PageDown scrolls the view down a page
1333 func (h *BufPane) PageDown() bool {
1334 h.ScrollDown(h.BufView().Height)
1339 // SelectPageUp selects up one page
1340 func (h *BufPane) SelectPageUp() bool {
1341 if !h.Cursor.HasSelection() {
1342 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1344 h.MoveCursorUp(h.BufView().Height)
1345 h.Cursor.SelectTo(h.Cursor.Loc)
1350 // SelectPageDown selects down one page
1351 func (h *BufPane) SelectPageDown() bool {
1352 if !h.Cursor.HasSelection() {
1353 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1355 h.MoveCursorDown(h.BufView().Height)
1356 h.Cursor.SelectTo(h.Cursor.Loc)
1361 // CursorPageUp places the cursor a page up
1362 func (h *BufPane) CursorPageUp() bool {
1363 h.Cursor.Deselect(true)
1365 if h.Cursor.HasSelection() {
1366 h.Cursor.Loc = h.Cursor.CurSelection[0]
1367 h.Cursor.ResetSelection()
1368 h.Cursor.StoreVisualX()
1370 h.MoveCursorUp(h.BufView().Height)
1375 // CursorPageDown places the cursor a page up
1376 func (h *BufPane) CursorPageDown() bool {
1377 h.Cursor.Deselect(false)
1379 if h.Cursor.HasSelection() {
1380 h.Cursor.Loc = h.Cursor.CurSelection[1]
1381 h.Cursor.ResetSelection()
1382 h.Cursor.StoreVisualX()
1384 h.MoveCursorDown(h.BufView().Height)
1389 // HalfPageUp scrolls the view up half a page
1390 func (h *BufPane) HalfPageUp() bool {
1391 h.ScrollUp(h.BufView().Height / 2)
1395 // HalfPageDown scrolls the view down half a page
1396 func (h *BufPane) HalfPageDown() bool {
1397 h.ScrollDown(h.BufView().Height / 2)
1402 // ToggleDiffGutter turns the diff gutter off and on
1403 func (h *BufPane) ToggleDiffGutter() bool {
1404 if !h.Buf.Settings["diffgutter"].(bool) {
1405 h.Buf.Settings["diffgutter"] = true
1406 h.Buf.UpdateDiff(func(synchronous bool) {
1409 InfoBar.Message("Enabled diff gutter")
1411 h.Buf.Settings["diffgutter"] = false
1412 InfoBar.Message("Disabled diff gutter")
1417 // ToggleRuler turns line numbers off and on
1418 func (h *BufPane) ToggleRuler() bool {
1419 if !h.Buf.Settings["ruler"].(bool) {
1420 h.Buf.Settings["ruler"] = true
1421 InfoBar.Message("Enabled ruler")
1423 h.Buf.Settings["ruler"] = false
1424 InfoBar.Message("Disabled ruler")
1429 // ClearStatus clears the messenger bar
1430 func (h *BufPane) ClearStatus() bool {
1435 // ToggleHelp toggles the help screen
1436 func (h *BufPane) ToggleHelp() bool {
1437 if h.Buf.Type == buffer.BTHelp {
1445 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1446 func (h *BufPane) ToggleKeyMenu() bool {
1447 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1452 // ShellMode opens a terminal to run a shell command
1453 func (h *BufPane) ShellMode() bool {
1454 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1456 // The true here is for openTerm to make the command interactive
1457 shell.RunInteractiveShell(resp, true, false)
1464 // CommandMode lets the user enter a command
1465 func (h *BufPane) CommandMode() bool {
1466 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1468 h.HandleCommand(resp)
1474 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1475 func (h *BufPane) ToggleOverwriteMode() bool {
1476 h.isOverwriteMode = !h.isOverwriteMode
1480 // Escape leaves current mode
1481 func (h *BufPane) Escape() bool {
1485 // Deselect deselects on the current cursor
1486 func (h *BufPane) Deselect() bool {
1487 h.Cursor.Deselect(true)
1491 // ClearInfo clears the infobar
1492 func (h *BufPane) ClearInfo() bool {
1497 // ForceQuit closes the current tab or view even if there are unsaved changes
1499 func (h *BufPane) ForceQuit() bool {
1501 if len(MainTab().Panes) > 1 {
1503 } else if len(Tabs.List) > 1 {
1504 Tabs.RemoveTab(h.splitID)
1506 screen.Screen.Fini()
1513 // Quit this will close the current tab or view that is open
1514 func (h *BufPane) Quit() bool {
1515 if h.Buf.Modified() {
1516 if config.GlobalSettings["autosave"].(float64) > 0 {
1517 // autosave on means we automatically save when quitting
1518 h.SaveCB("Quit", func() {
1522 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1523 if !canceled && !yes {
1525 } else if !canceled && yes {
1526 h.SaveCB("Quit", func() {
1538 // QuitAll quits the whole editor; all splits and tabs
1539 func (h *BufPane) QuitAll() bool {
1540 anyModified := false
1541 for _, b := range buffer.OpenBuffers {
1549 for _, b := range buffer.OpenBuffers {
1552 screen.Screen.Fini()
1558 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1559 if !canceled && yes {
1570 // AddTab adds a new tab with an empty buffer
1571 func (h *BufPane) AddTab() bool {
1572 width, height := screen.Screen.Size()
1573 iOffset := config.GetInfoBarOffset()
1574 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1575 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1577 Tabs.SetActive(len(Tabs.List) - 1)
1582 // PreviousTab switches to the previous tab in the tab list
1583 func (h *BufPane) PreviousTab() bool {
1584 tabsLen := len(Tabs.List)
1585 a := Tabs.Active() + tabsLen
1586 Tabs.SetActive((a - 1) % tabsLen)
1591 // NextTab switches to the next tab in the tab list
1592 func (h *BufPane) NextTab() bool {
1594 Tabs.SetActive((a + 1) % len(Tabs.List))
1599 // VSplitAction opens an empty vertical split
1600 func (h *BufPane) VSplitAction() bool {
1601 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1606 // HSplitAction opens an empty horizontal split
1607 func (h *BufPane) HSplitAction() bool {
1608 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1613 // Unsplit closes all splits in the current tab except the active one
1614 func (h *BufPane) Unsplit() bool {
1616 n := tab.GetNode(h.splitID)
1619 tab.RemovePane(tab.GetPane(h.splitID))
1621 tab.SetActive(len(tab.Panes) - 1)
1628 // NextSplit changes the view to the next split
1629 func (h *BufPane) NextSplit() bool {
1631 if a < len(h.tab.Panes)-1 {
1642 // PreviousSplit changes the view to the previous split
1643 func (h *BufPane) PreviousSplit() bool {
1648 a = len(h.tab.Panes) - 1
1655 var curmacro []interface{}
1656 var recordingMacro bool
1658 // ToggleMacro toggles recording of a macro
1659 func (h *BufPane) ToggleMacro() bool {
1660 recordingMacro = !recordingMacro
1662 curmacro = []interface{}{}
1663 InfoBar.Message("Recording")
1665 InfoBar.Message("Stopped recording")
1671 // PlayMacro plays back the most recently recorded macro
1672 func (h *BufPane) PlayMacro() bool {
1676 for _, action := range curmacro {
1677 switch t := action.(type) {
1680 case func(*BufPane) bool:
1688 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1689 func (h *BufPane) SpawnMultiCursor() bool {
1690 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1691 if !spawner.HasSelection() {
1692 spawner.SelectWord()
1698 sel := spawner.GetSelection()
1699 searchStart := spawner.CurSelection[1]
1701 search := string(sel)
1702 search = regexp.QuoteMeta(search)
1704 search = "\\b" + search + "\\b"
1706 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1711 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1712 c.SetSelectionStart(match[0])
1713 c.SetSelectionEnd(match[1])
1714 c.OrigSelection[0] = c.CurSelection[0]
1715 c.OrigSelection[1] = c.CurSelection[1]
1716 c.Loc = c.CurSelection[1]
1719 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1720 h.Buf.MergeCursors()
1722 InfoBar.Message("No matches found")
1729 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1730 func (h *BufPane) SpawnMultiCursorUp() bool {
1731 if h.Cursor.Y == 0 {
1734 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1737 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1739 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1740 h.Buf.MergeCursors()
1746 // SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
1747 func (h *BufPane) SpawnMultiCursorDown() bool {
1748 if h.Cursor.Y+1 == h.Buf.LinesNum() {
1751 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1754 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1756 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1757 h.Buf.MergeCursors()
1762 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1763 func (h *BufPane) SpawnMultiCursorSelect() bool {
1764 // Avoid cases where multiple cursors already exist, that would create problems
1765 if h.Buf.NumCursors() > 1 {
1772 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1774 startLine, endLine = b, a
1776 startLine, endLine = a, b
1779 if h.Cursor.HasSelection() {
1780 h.Cursor.ResetSelection()
1781 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1783 for i := startLine; i <= endLine; i++ {
1784 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1788 h.Buf.MergeCursors()
1792 InfoBar.Message("Added cursors from selection")
1796 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1797 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1799 mx, my := e.Position()
1800 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1801 c := buffer.NewCursor(b, mouseLoc)
1808 // SkipMultiCursor moves the current multiple cursor to the next available position
1809 func (h *BufPane) SkipMultiCursor() bool {
1810 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1811 sel := lastC.GetSelection()
1812 searchStart := lastC.CurSelection[1]
1814 search := string(sel)
1815 search = regexp.QuoteMeta(search)
1817 search = "\\b" + search + "\\b"
1820 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1825 lastC.SetSelectionStart(match[0])
1826 lastC.SetSelectionEnd(match[1])
1827 lastC.OrigSelection[0] = lastC.CurSelection[0]
1828 lastC.OrigSelection[1] = lastC.CurSelection[1]
1829 lastC.Loc = lastC.CurSelection[1]
1831 h.Buf.MergeCursors()
1832 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1834 InfoBar.Message("No matches found")
1840 // RemoveMultiCursor removes the latest multiple cursor
1841 func (h *BufPane) RemoveMultiCursor() bool {
1842 if h.Buf.NumCursors() > 1 {
1843 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1844 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1845 h.Buf.UpdateCursors()
1853 // RemoveAllMultiCursors removes all cursors except the base cursor
1854 func (h *BufPane) RemoveAllMultiCursors() bool {
1855 h.Buf.ClearCursors()
1861 // None is an action that does nothing
1862 func (h *BufPane) None() bool {