10 "github.com/zyedidia/clipboard"
11 "github.com/zyedidia/micro/internal/buffer"
12 "github.com/zyedidia/micro/internal/config"
13 "github.com/zyedidia/micro/internal/screen"
14 "github.com/zyedidia/micro/internal/shell"
15 "github.com/zyedidia/micro/internal/util"
16 "github.com/zyedidia/micro/pkg/shellwords"
17 "github.com/zyedidia/tcell"
20 // ScrollUp is not an action
21 func (h *BufPane) ScrollUp(n int) {
31 // ScrollDown is not an action
32 func (h *BufPane) ScrollDown(n int) {
34 if v.StartLine <= h.Buf.LinesNum()-1-n {
40 // MousePress is the event that should happen when a normal click happens
41 // This is almost always bound to left click
42 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
44 mx, my := e.Position()
45 mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
46 h.Cursor.Loc = mouseLoc
48 if b.NumCursors() > 1 {
51 h.Cursor = h.Buf.GetActiveCursor()
52 h.Cursor.Loc = mouseLoc
54 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
57 h.lastClickTime = time.Now()
63 h.Cursor.CopySelection("primary")
66 h.lastClickTime = time.Now()
72 h.Cursor.CopySelection("primary")
77 h.lastClickTime = time.Now()
79 h.Cursor.OrigSelection[0] = h.Cursor.Loc
80 h.Cursor.CurSelection[0] = h.Cursor.Loc
81 h.Cursor.CurSelection[1] = h.Cursor.Loc
83 h.mouseReleased = false
84 } else if !h.mouseReleased {
86 h.Cursor.AddLineToSelection()
87 } else if h.doubleClick {
88 h.Cursor.AddWordToSelection()
90 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
94 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 < utf8.RuneCount(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 // StartOfLine moves the cursor to the start of the line
287 func (h *BufPane) StartOfLine() bool {
288 h.Cursor.Deselect(true)
289 h.Cursor.StartOfText()
290 // if h.Cursor.X != 0 {
293 // h.Cursor.StartOfText()
299 // EndOfLine moves the cursor to the end of the line
300 func (h *BufPane) EndOfLine() bool {
301 h.Cursor.Deselect(true)
307 // SelectLine selects the entire current line
308 func (h *BufPane) SelectLine() bool {
309 h.Cursor.SelectLine()
314 // SelectToStartOfLine selects to the start of the current line
315 func (h *BufPane) SelectToStartOfLine() bool {
316 if !h.Cursor.HasSelection() {
317 h.Cursor.OrigSelection[0] = h.Cursor.Loc
320 h.Cursor.SelectTo(h.Cursor.Loc)
325 // SelectToEndOfLine selects to the end of the current line
326 func (h *BufPane) SelectToEndOfLine() bool {
327 if !h.Cursor.HasSelection() {
328 h.Cursor.OrigSelection[0] = h.Cursor.Loc
331 h.Cursor.SelectTo(h.Cursor.Loc)
336 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
337 func (h *BufPane) ParagraphPrevious() bool {
339 for line = h.Cursor.Y; line > 0; line-- {
340 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
346 // If no empty line found. move cursor to end of buffer
348 h.Cursor.Loc = h.Buf.Start()
354 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
355 func (h *BufPane) ParagraphNext() bool {
357 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
358 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
364 // If no empty line found. move cursor to end of buffer
365 if line == h.Buf.LinesNum() {
366 h.Cursor.Loc = h.Buf.End()
372 // Retab changes all tabs to spaces or all spaces to tabs depending
373 // on the user's settings
374 func (h *BufPane) Retab() bool {
380 // CursorStart moves the cursor to the start of the buffer
381 func (h *BufPane) CursorStart() bool {
382 h.Cursor.Deselect(true)
389 // CursorEnd moves the cursor to the end of the buffer
390 func (h *BufPane) CursorEnd() bool {
391 h.Cursor.Deselect(true)
392 h.Cursor.Loc = h.Buf.End()
393 h.Cursor.StoreVisualX()
398 // SelectToStart selects the text from the cursor to the start of the buffer
399 func (h *BufPane) SelectToStart() bool {
400 if !h.Cursor.HasSelection() {
401 h.Cursor.OrigSelection[0] = h.Cursor.Loc
404 h.Cursor.SelectTo(h.Buf.Start())
409 // SelectToEnd selects the text from the cursor to the end of the buffer
410 func (h *BufPane) SelectToEnd() bool {
411 if !h.Cursor.HasSelection() {
412 h.Cursor.OrigSelection[0] = h.Cursor.Loc
415 h.Cursor.SelectTo(h.Buf.End())
420 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
421 func (h *BufPane) InsertNewline() bool {
423 if h.Cursor.HasSelection() {
424 h.Cursor.DeleteSelection()
425 h.Cursor.ResetSelection()
428 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
430 h.Buf.Insert(h.Cursor.Loc, "\n")
433 if h.Buf.Settings["autoindent"].(bool) {
437 h.Buf.Insert(h.Cursor.Loc, string(ws))
438 // for i := 0; i < len(ws); i++ {
442 // Remove the whitespaces if keepautoindent setting is off
443 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
444 line := h.Buf.LineBytes(h.Cursor.Y - 1)
445 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
448 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
453 // Backspace deletes the previous character
454 func (h *BufPane) Backspace() bool {
455 if h.Cursor.HasSelection() {
456 h.Cursor.DeleteSelection()
457 h.Cursor.ResetSelection()
458 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
459 // We have to do something a bit hacky here because we want to
460 // delete the line by first moving left and then deleting backwards
461 // but the undo redo would place the cursor in the wrong place
462 // So instead we move left, save the position, move back, delete
463 // and restore the position
465 // If the user is using spaces instead of tabs and they are deleting
466 // whitespace at the start of the line, we should delete as if it's a
467 // tab (tabSize number of spaces)
468 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
469 tabSize := int(h.Buf.Settings["tabsize"].(float64))
470 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
472 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
475 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
478 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
483 // DeleteWordRight deletes the word to the right of the cursor
484 func (h *BufPane) DeleteWordRight() bool {
486 if h.Cursor.HasSelection() {
487 h.Cursor.DeleteSelection()
488 h.Cursor.ResetSelection()
494 // DeleteWordLeft deletes the word to the left of the cursor
495 func (h *BufPane) DeleteWordLeft() bool {
497 if h.Cursor.HasSelection() {
498 h.Cursor.DeleteSelection()
499 h.Cursor.ResetSelection()
505 // Delete deletes the next character
506 func (h *BufPane) Delete() bool {
507 if h.Cursor.HasSelection() {
508 h.Cursor.DeleteSelection()
509 h.Cursor.ResetSelection()
512 if loc.LessThan(h.Buf.End()) {
513 h.Buf.Remove(loc, loc.Move(1, h.Buf))
520 // IndentSelection indents the current selection
521 func (h *BufPane) IndentSelection() bool {
522 if h.Cursor.HasSelection() {
523 start := h.Cursor.CurSelection[0]
524 end := h.Cursor.CurSelection[1]
526 start, end = end, start
527 h.Cursor.SetSelectionStart(start)
528 h.Cursor.SetSelectionEnd(end)
532 endY := end.Move(-1, h.Buf).Y
533 endX := end.Move(-1, h.Buf).X
534 tabsize := int(h.Buf.Settings["tabsize"].(float64))
535 indentsize := len(h.Buf.IndentString(tabsize))
536 for y := startY; y <= endY; y++ {
537 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
538 if y == startY && start.X > 0 {
539 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
542 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
545 h.Buf.RelocateCursors()
553 // OutdentLine moves the current line back one indentation
554 func (h *BufPane) OutdentLine() bool {
555 if h.Cursor.HasSelection() {
559 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
560 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
563 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
565 h.Buf.RelocateCursors()
570 // OutdentSelection takes the current selection and moves it back one indent level
571 func (h *BufPane) OutdentSelection() bool {
572 if h.Cursor.HasSelection() {
573 start := h.Cursor.CurSelection[0]
574 end := h.Cursor.CurSelection[1]
576 start, end = end, start
577 h.Cursor.SetSelectionStart(start)
578 h.Cursor.SetSelectionEnd(end)
582 endY := end.Move(-1, h.Buf).Y
583 for y := startY; y <= endY; y++ {
584 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
585 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
588 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
591 h.Buf.RelocateCursors()
599 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
600 func (h *BufPane) Autocomplete() bool {
603 if h.Cursor.HasSelection() {
607 if b.HasSuggestions {
608 b.CycleAutocomplete(true)
611 return b.Autocomplete(buffer.BufferComplete)
614 // InsertTab inserts a tab or spaces
615 func (h *BufPane) InsertTab() bool {
617 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
618 tabBytes := len(indent)
619 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
620 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
625 // SaveAll saves all open buffers
626 func (h *BufPane) SaveAll() bool {
627 for _, b := range buffer.OpenBuffers {
633 // Save the buffer to disk
634 func (h *BufPane) Save() bool {
635 // If this is an empty buffer, ask for a filename
636 if h.Buf.Path == "" {
639 h.saveBufToFile(h.Buf.Path, "Save")
645 // SaveAs saves the buffer to disk with the given name
646 func (h *BufPane) SaveAs() bool {
647 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
649 // the filename might or might not be quoted, so unquote first then join the strings.
650 args, err := shellwords.Split(resp)
651 filename := strings.Join(args, " ")
653 InfoBar.Error("Error parsing arguments: ", err)
656 h.saveBufToFile(filename, "SaveAs")
662 // This function saves the buffer to `filename` and changes the buffer's path and name
663 // to `filename` if the save is successful
664 func (h *BufPane) saveBufToFile(filename string, action string) {
665 err := h.Buf.SaveAs(filename)
667 if strings.HasSuffix(err.Error(), "permission denied") {
668 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
669 if yes && !canceled {
670 err = h.Buf.SaveAsWithSudo(filename)
674 h.Buf.Path = filename
675 h.Buf.SetName(filename)
676 InfoBar.Message("Saved " + filename)
678 h.completeAction(action)
685 h.Buf.Path = filename
686 h.Buf.SetName(filename)
687 InfoBar.Message("Saved " + filename)
688 h.completeAction(action)
692 // Find opens a prompt and searches forward for the input
693 func (h *BufPane) Find() bool {
694 h.searchOrig = h.Cursor.Loc
695 InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
697 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
699 h.Cursor.SetSelectionStart(match[0])
700 h.Cursor.SetSelectionEnd(match[1])
701 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
702 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
703 h.Cursor.GotoLoc(match[1])
705 h.Cursor.GotoLoc(h.searchOrig)
706 h.Cursor.ResetSelection()
709 }, func(resp string, canceled bool) {
712 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
717 h.Cursor.SetSelectionStart(match[0])
718 h.Cursor.SetSelectionEnd(match[1])
719 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
720 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
721 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
724 h.Cursor.ResetSelection()
725 InfoBar.Message("No matches found")
728 h.Cursor.ResetSelection()
736 // FindNext searches forwards for the last used search term
737 func (h *BufPane) FindNext() bool {
738 // If the cursor is at the start of a selection and we search we want
739 // to search from the end of the selection in the case that
740 // the selection is a search result in which case we wouldn't move at
741 // at all which would be bad
742 searchLoc := h.Cursor.Loc
743 if h.Cursor.HasSelection() {
744 searchLoc = h.Cursor.CurSelection[1]
746 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
751 h.Cursor.SetSelectionStart(match[0])
752 h.Cursor.SetSelectionEnd(match[1])
753 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
754 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
755 h.Cursor.Loc = h.Cursor.CurSelection[1]
757 h.Cursor.ResetSelection()
763 // FindPrevious searches backwards for the last used search term
764 func (h *BufPane) FindPrevious() bool {
765 // If the cursor is at the end of a selection and we search we want
766 // to search from the beginning of the selection in the case that
767 // the selection is a search result in which case we wouldn't move at
768 // at all which would be bad
769 searchLoc := h.Cursor.Loc
770 if h.Cursor.HasSelection() {
771 searchLoc = h.Cursor.CurSelection[0]
773 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
778 h.Cursor.SetSelectionStart(match[0])
779 h.Cursor.SetSelectionEnd(match[1])
780 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
781 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
782 h.Cursor.Loc = h.Cursor.CurSelection[1]
784 h.Cursor.ResetSelection()
790 // Undo undoes the last action
791 func (h *BufPane) Undo() bool {
793 InfoBar.Message("Undid action")
798 // Redo redoes the last action
799 func (h *BufPane) Redo() bool {
801 InfoBar.Message("Redid action")
806 // Copy the selection to the system clipboard
807 func (h *BufPane) Copy() bool {
808 if h.Cursor.HasSelection() {
809 h.Cursor.CopySelection("clipboard")
811 if clipboard.Unsupported {
812 InfoBar.Message("Copied selection (install xclip for external clipboard)")
814 InfoBar.Message("Copied selection")
821 // CutLine cuts the current line to the clipboard
822 func (h *BufPane) CutLine() bool {
823 h.Cursor.SelectLine()
824 if !h.Cursor.HasSelection() {
827 if h.freshClip == true {
828 if h.Cursor.HasSelection() {
829 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
830 // messenger.Error(err)
832 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
835 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
839 h.lastCutTime = time.Now()
840 h.Cursor.DeleteSelection()
841 h.Cursor.ResetSelection()
842 InfoBar.Message("Cut line")
847 // Cut the selection to the system clipboard
848 func (h *BufPane) Cut() bool {
849 if h.Cursor.HasSelection() {
850 h.Cursor.CopySelection("clipboard")
851 h.Cursor.DeleteSelection()
852 h.Cursor.ResetSelection()
854 InfoBar.Message("Cut selection")
863 // DuplicateLine duplicates the current line or selection
864 func (h *BufPane) DuplicateLine() bool {
865 if h.Cursor.HasSelection() {
866 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
869 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
873 InfoBar.Message("Duplicated line")
878 // DeleteLine deletes the current line
879 func (h *BufPane) DeleteLine() bool {
880 h.Cursor.SelectLine()
881 if !h.Cursor.HasSelection() {
884 h.Cursor.DeleteSelection()
885 h.Cursor.ResetSelection()
886 InfoBar.Message("Deleted line")
891 // MoveLinesUp moves up the current line or selected lines if any
892 func (h *BufPane) MoveLinesUp() bool {
893 if h.Cursor.HasSelection() {
894 if h.Cursor.CurSelection[0].Y == 0 {
895 InfoBar.Message("Cannot move further up")
898 start := h.Cursor.CurSelection[0].Y
899 end := h.Cursor.CurSelection[1].Y
901 end, start = start, end
908 h.Cursor.CurSelection[1].Y -= 1
910 if h.Cursor.Loc.Y == 0 {
911 InfoBar.Message("Cannot move further up")
924 // MoveLinesDown moves down the current line or selected lines if any
925 func (h *BufPane) MoveLinesDown() bool {
926 if h.Cursor.HasSelection() {
927 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
928 InfoBar.Message("Cannot move further down")
931 start := h.Cursor.CurSelection[0].Y
932 end := h.Cursor.CurSelection[1].Y
934 end, start = start, end
942 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
943 InfoBar.Message("Cannot move further down")
956 // Paste whatever is in the system clipboard into the buffer
957 // Delete and paste if the user has a selection
958 func (h *BufPane) Paste() bool {
959 clip, _ := clipboard.ReadAll("clipboard")
965 // PastePrimary pastes from the primary clipboard (only use on linux)
966 func (h *BufPane) PastePrimary() bool {
967 clip, _ := clipboard.ReadAll("primary")
973 func (h *BufPane) paste(clip string) {
974 if h.Buf.Settings["smartpaste"].(bool) {
975 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
976 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
977 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
981 if h.Cursor.HasSelection() {
982 h.Cursor.DeleteSelection()
983 h.Cursor.ResetSelection()
986 h.Buf.Insert(h.Cursor.Loc, clip)
987 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
989 if clipboard.Unsupported {
990 InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
992 InfoBar.Message("Pasted clipboard")
996 // JumpToMatchingBrace moves the cursor to the matching brace if it is
997 // currently on a brace
998 func (h *BufPane) JumpToMatchingBrace() bool {
999 for _, bp := range buffer.BracePairs {
1000 r := h.Cursor.RuneUnder(h.Cursor.X)
1001 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1002 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1003 matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1005 h.Cursor.GotoLoc(matchingBrace)
1007 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1016 // SelectAll selects the entire buffer
1017 func (h *BufPane) SelectAll() bool {
1018 h.Cursor.SetSelectionStart(h.Buf.Start())
1019 h.Cursor.SetSelectionEnd(h.Buf.End())
1020 // Put the cursor at the beginning
1027 // OpenFile opens a new file in the buffer
1028 func (h *BufPane) OpenFile() bool {
1029 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1031 h.HandleCommand(resp)
1037 // Start moves the viewport to the start of the buffer
1038 func (h *BufPane) Start() bool {
1045 // End moves the viewport to the end of the buffer
1046 func (h *BufPane) End() bool {
1047 // TODO: softwrap problems?
1049 if v.Height > h.Buf.LinesNum() {
1053 v.StartLine = h.Buf.LinesNum() - v.Height
1059 // PageUp scrolls the view up a page
1060 func (h *BufPane) PageUp() bool {
1062 if v.StartLine > v.Height {
1063 h.ScrollUp(v.Height)
1071 // PageDown scrolls the view down a page
1072 func (h *BufPane) PageDown() bool {
1074 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1075 h.ScrollDown(v.Height)
1076 } else if h.Buf.LinesNum() >= v.Height {
1077 v.StartLine = h.Buf.LinesNum() - v.Height
1082 // SelectPageUp selects up one page
1083 func (h *BufPane) SelectPageUp() bool {
1084 if !h.Cursor.HasSelection() {
1085 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1087 h.Cursor.UpN(h.GetView().Height)
1088 h.Cursor.SelectTo(h.Cursor.Loc)
1093 // SelectPageDown selects down one page
1094 func (h *BufPane) SelectPageDown() bool {
1095 if !h.Cursor.HasSelection() {
1096 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1098 h.Cursor.DownN(h.GetView().Height)
1099 h.Cursor.SelectTo(h.Cursor.Loc)
1104 // CursorPageUp places the cursor a page up
1105 func (h *BufPane) CursorPageUp() bool {
1106 h.Cursor.Deselect(true)
1108 if h.Cursor.HasSelection() {
1109 h.Cursor.Loc = h.Cursor.CurSelection[0]
1110 h.Cursor.ResetSelection()
1111 h.Cursor.StoreVisualX()
1113 h.Cursor.UpN(h.GetView().Height)
1118 // CursorPageDown places the cursor a page up
1119 func (h *BufPane) CursorPageDown() bool {
1120 h.Cursor.Deselect(false)
1122 if h.Cursor.HasSelection() {
1123 h.Cursor.Loc = h.Cursor.CurSelection[1]
1124 h.Cursor.ResetSelection()
1125 h.Cursor.StoreVisualX()
1127 h.Cursor.DownN(h.GetView().Height)
1132 // HalfPageUp scrolls the view up half a page
1133 func (h *BufPane) HalfPageUp() bool {
1135 if v.StartLine > v.Height/2 {
1136 h.ScrollUp(v.Height / 2)
1144 // HalfPageDown scrolls the view down half a page
1145 func (h *BufPane) HalfPageDown() bool {
1147 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1148 h.ScrollDown(v.Height / 2)
1150 if h.Buf.LinesNum() >= v.Height {
1151 v.StartLine = h.Buf.LinesNum() - v.Height
1158 // ToggleRuler turns line numbers off and on
1159 func (h *BufPane) ToggleRuler() bool {
1160 if !h.Buf.Settings["ruler"].(bool) {
1161 h.Buf.Settings["ruler"] = true
1162 InfoBar.Message("Enabled ruler")
1164 h.Buf.Settings["ruler"] = false
1165 InfoBar.Message("Disabled ruler")
1170 // ClearStatus clears the messenger bar
1171 func (h *BufPane) ClearStatus() bool {
1176 // ToggleHelp toggles the help screen
1177 func (h *BufPane) ToggleHelp() bool {
1178 if h.Buf.Type == buffer.BTHelp {
1186 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1187 func (h *BufPane) ToggleKeyMenu() bool {
1188 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1193 // ShellMode opens a terminal to run a shell command
1194 func (h *BufPane) ShellMode() bool {
1195 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1197 // The true here is for openTerm to make the command interactive
1198 shell.RunInteractiveShell(resp, true, false)
1205 // CommandMode lets the user enter a command
1206 func (h *BufPane) CommandMode() bool {
1207 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1209 h.HandleCommand(resp)
1215 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1216 func (h *BufPane) ToggleOverwriteMode() bool {
1217 h.isOverwriteMode = !h.isOverwriteMode
1221 // Escape leaves current mode
1222 func (h *BufPane) Escape() bool {
1226 // Quit this will close the current tab or view that is open
1227 func (h *BufPane) Quit() bool {
1230 if len(MainTab().Panes) > 1 {
1232 } else if len(Tabs.List) > 1 {
1233 Tabs.RemoveTab(h.splitID)
1235 screen.Screen.Fini()
1240 if h.Buf.Modified() {
1241 // if config.GlobalSettings["autosave"].(float64) > 0 {
1242 // autosave on means we automatically save when quitting
1246 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1247 if !canceled && !yes {
1249 } else if !canceled && yes {
1261 // QuitAll quits the whole editor; all splits and tabs
1262 func (h *BufPane) QuitAll() bool {
1263 anyModified := false
1264 for _, b := range buffer.OpenBuffers {
1272 for _, b := range buffer.OpenBuffers {
1275 screen.Screen.Fini()
1281 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1282 if !canceled && yes {
1293 // AddTab adds a new tab with an empty buffer
1294 func (h *BufPane) AddTab() bool {
1295 width, height := screen.Screen.Size()
1296 iOffset := config.GetInfoBarOffset()
1297 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1298 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1300 Tabs.SetActive(len(Tabs.List) - 1)
1305 // PreviousTab switches to the previous tab in the tab list
1306 func (h *BufPane) PreviousTab() bool {
1308 Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1313 // NextTab switches to the next tab in the tab list
1314 func (h *BufPane) NextTab() bool {
1316 Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1320 // VSplitAction opens an empty vertical split
1321 func (h *BufPane) VSplitAction() bool {
1322 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1327 // HSplitAction opens an empty horizontal split
1328 func (h *BufPane) HSplitAction() bool {
1329 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1334 // Unsplit closes all splits in the current tab except the active one
1335 func (h *BufPane) Unsplit() bool {
1336 n := MainTab().GetNode(h.splitID)
1339 MainTab().RemovePane(MainTab().GetPane(h.splitID))
1341 MainTab().SetActive(len(MainTab().Panes) - 1)
1345 // NextSplit changes the view to the next split
1346 func (h *BufPane) NextSplit() bool {
1347 a := MainTab().active
1348 if a < len(MainTab().Panes)-1 {
1354 MainTab().SetActive(a)
1359 // PreviousSplit changes the view to the previous split
1360 func (h *BufPane) PreviousSplit() bool {
1361 a := MainTab().active
1365 a = len(MainTab().Panes) - 1
1367 MainTab().SetActive(a)
1372 var curmacro []interface{}
1373 var recording_macro bool
1375 // ToggleMacro toggles recording of a macro
1376 func (h *BufPane) ToggleMacro() bool {
1377 recording_macro = !recording_macro
1378 if recording_macro {
1379 curmacro = []interface{}{}
1380 InfoBar.Message("Recording")
1382 InfoBar.Message("Stopped recording")
1388 // PlayMacro plays back the most recently recorded macro
1389 func (h *BufPane) PlayMacro() bool {
1390 if recording_macro {
1393 for _, action := range curmacro {
1394 switch t := action.(type) {
1397 case func(*BufPane) bool:
1405 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1406 func (h *BufPane) SpawnMultiCursor() bool {
1407 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1408 if !spawner.HasSelection() {
1409 spawner.SelectWord()
1415 sel := spawner.GetSelection()
1416 searchStart := spawner.CurSelection[1]
1418 search := string(sel)
1419 search = regexp.QuoteMeta(search)
1421 search = "\\b" + search + "\\b"
1423 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1428 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1429 c.SetSelectionStart(match[0])
1430 c.SetSelectionEnd(match[1])
1431 c.OrigSelection[0] = c.CurSelection[0]
1432 c.OrigSelection[1] = c.CurSelection[1]
1433 c.Loc = c.CurSelection[1]
1436 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1437 h.Buf.MergeCursors()
1439 InfoBar.Message("No matches found")
1446 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1447 func (h *BufPane) SpawnMultiCursorSelect() bool {
1448 // Avoid cases where multiple cursors already exist, that would create problems
1449 if h.Buf.NumCursors() > 1 {
1456 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1458 startLine, endLine = b, a
1460 startLine, endLine = a, b
1463 if h.Cursor.HasSelection() {
1464 h.Cursor.ResetSelection()
1465 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1467 for i := startLine; i <= endLine; i++ {
1468 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1472 h.Buf.MergeCursors()
1476 InfoBar.Message("Added cursors from selection")
1480 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1481 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1483 mx, my := e.Position()
1484 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1485 c := buffer.NewCursor(b, mouseLoc)
1492 // SkipMultiCursor moves the current multiple cursor to the next available position
1493 func (h *BufPane) SkipMultiCursor() bool {
1494 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1495 sel := lastC.GetSelection()
1496 searchStart := lastC.CurSelection[1]
1498 search := string(sel)
1499 search = regexp.QuoteMeta(search)
1501 search = "\\b" + search + "\\b"
1504 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1509 lastC.SetSelectionStart(match[0])
1510 lastC.SetSelectionEnd(match[1])
1511 lastC.OrigSelection[0] = lastC.CurSelection[0]
1512 lastC.OrigSelection[1] = lastC.CurSelection[1]
1513 lastC.Loc = lastC.CurSelection[1]
1515 h.Buf.MergeCursors()
1516 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1518 InfoBar.Message("No matches found")
1524 // RemoveMultiCursor removes the latest multiple cursor
1525 func (h *BufPane) RemoveMultiCursor() bool {
1526 if h.Buf.NumCursors() > 1 {
1527 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1528 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1529 h.Buf.UpdateCursors()
1537 // RemoveAllMultiCursors removes all cursors except the base cursor
1538 func (h *BufPane) RemoveAllMultiCursors() bool {
1539 h.Buf.ClearCursors()
1545 // None is an action that does nothing
1546 func (h *BufPane) None() bool {