10 shellquote "github.com/kballard/go-shellquote"
11 "github.com/zyedidia/clipboard"
12 "github.com/zyedidia/micro/internal/buffer"
13 "github.com/zyedidia/micro/internal/config"
14 "github.com/zyedidia/micro/internal/screen"
15 "github.com/zyedidia/micro/internal/shell"
16 "github.com/zyedidia/micro/internal/util"
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 text of the line
287 func (h *BufPane) StartOfText() bool {
288 h.Cursor.Deselect(true)
289 h.Cursor.StartOfText()
294 // StartOfLine moves the cursor to the start of the line
295 func (h *BufPane) StartOfLine() bool {
296 h.Cursor.Deselect(true)
302 // EndOfLine moves the cursor to the end of the line
303 func (h *BufPane) EndOfLine() bool {
304 h.Cursor.Deselect(true)
310 // SelectLine selects the entire current line
311 func (h *BufPane) SelectLine() bool {
312 h.Cursor.SelectLine()
317 // SelectToStartOfText selects to the start of the text on the current line
318 func (h *BufPane) SelectToStartOfText() bool {
319 if !h.Cursor.HasSelection() {
320 h.Cursor.OrigSelection[0] = h.Cursor.Loc
322 h.Cursor.StartOfText()
323 h.Cursor.SelectTo(h.Cursor.Loc)
328 // SelectToStartOfLine selects to the start of the current line
329 func (h *BufPane) SelectToStartOfLine() bool {
330 if !h.Cursor.HasSelection() {
331 h.Cursor.OrigSelection[0] = h.Cursor.Loc
334 h.Cursor.SelectTo(h.Cursor.Loc)
339 // SelectToEndOfLine selects to the end of the current line
340 func (h *BufPane) SelectToEndOfLine() bool {
341 if !h.Cursor.HasSelection() {
342 h.Cursor.OrigSelection[0] = h.Cursor.Loc
345 h.Cursor.SelectTo(h.Cursor.Loc)
350 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
351 func (h *BufPane) ParagraphPrevious() bool {
353 for line = h.Cursor.Y; line > 0; line-- {
354 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
360 // If no empty line found. move cursor to end of buffer
362 h.Cursor.Loc = h.Buf.Start()
368 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
369 func (h *BufPane) ParagraphNext() bool {
371 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
372 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
378 // If no empty line found. move cursor to end of buffer
379 if line == h.Buf.LinesNum() {
380 h.Cursor.Loc = h.Buf.End()
386 // Retab changes all tabs to spaces or all spaces to tabs depending
387 // on the user's settings
388 func (h *BufPane) Retab() bool {
394 // CursorStart moves the cursor to the start of the buffer
395 func (h *BufPane) CursorStart() bool {
396 h.Cursor.Deselect(true)
403 // CursorEnd moves the cursor to the end of the buffer
404 func (h *BufPane) CursorEnd() bool {
405 h.Cursor.Deselect(true)
406 h.Cursor.Loc = h.Buf.End()
407 h.Cursor.StoreVisualX()
412 // SelectToStart selects the text from the cursor to the start of the buffer
413 func (h *BufPane) SelectToStart() bool {
414 if !h.Cursor.HasSelection() {
415 h.Cursor.OrigSelection[0] = h.Cursor.Loc
418 h.Cursor.SelectTo(h.Buf.Start())
423 // SelectToEnd selects the text from the cursor to the end of the buffer
424 func (h *BufPane) SelectToEnd() bool {
425 if !h.Cursor.HasSelection() {
426 h.Cursor.OrigSelection[0] = h.Cursor.Loc
429 h.Cursor.SelectTo(h.Buf.End())
434 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
435 func (h *BufPane) InsertNewline() bool {
437 if h.Cursor.HasSelection() {
438 h.Cursor.DeleteSelection()
439 h.Cursor.ResetSelection()
442 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
444 h.Buf.Insert(h.Cursor.Loc, "\n")
447 if h.Buf.Settings["autoindent"].(bool) {
451 h.Buf.Insert(h.Cursor.Loc, string(ws))
452 // for i := 0; i < len(ws); i++ {
456 // Remove the whitespaces if keepautoindent setting is off
457 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
458 line := h.Buf.LineBytes(h.Cursor.Y - 1)
459 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
462 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
467 // Backspace deletes the previous character
468 func (h *BufPane) Backspace() bool {
469 if h.Cursor.HasSelection() {
470 h.Cursor.DeleteSelection()
471 h.Cursor.ResetSelection()
472 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
473 // We have to do something a bit hacky here because we want to
474 // delete the line by first moving left and then deleting backwards
475 // but the undo redo would place the cursor in the wrong place
476 // So instead we move left, save the position, move back, delete
477 // and restore the position
479 // If the user is using spaces instead of tabs and they are deleting
480 // whitespace at the start of the line, we should delete as if it's a
481 // tab (tabSize number of spaces)
482 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
483 tabSize := int(h.Buf.Settings["tabsize"].(float64))
484 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
486 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
489 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
492 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
497 // DeleteWordRight deletes the word to the right of the cursor
498 func (h *BufPane) DeleteWordRight() bool {
500 if h.Cursor.HasSelection() {
501 h.Cursor.DeleteSelection()
502 h.Cursor.ResetSelection()
508 // DeleteWordLeft deletes the word to the left of the cursor
509 func (h *BufPane) DeleteWordLeft() bool {
511 if h.Cursor.HasSelection() {
512 h.Cursor.DeleteSelection()
513 h.Cursor.ResetSelection()
519 // Delete deletes the next character
520 func (h *BufPane) Delete() bool {
521 if h.Cursor.HasSelection() {
522 h.Cursor.DeleteSelection()
523 h.Cursor.ResetSelection()
526 if loc.LessThan(h.Buf.End()) {
527 h.Buf.Remove(loc, loc.Move(1, h.Buf))
534 // IndentSelection indents the current selection
535 func (h *BufPane) IndentSelection() bool {
536 if h.Cursor.HasSelection() {
537 start := h.Cursor.CurSelection[0]
538 end := h.Cursor.CurSelection[1]
540 start, end = end, start
541 h.Cursor.SetSelectionStart(start)
542 h.Cursor.SetSelectionEnd(end)
546 endY := end.Move(-1, h.Buf).Y
547 endX := end.Move(-1, h.Buf).X
548 tabsize := int(h.Buf.Settings["tabsize"].(float64))
549 indentsize := len(h.Buf.IndentString(tabsize))
550 for y := startY; y <= endY; y++ {
551 if len(h.Buf.LineBytes(y)) > 0 {
552 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
553 if y == startY && start.X > 0 {
554 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
557 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
561 h.Buf.RelocateCursors()
569 // IndentLine moves the current line forward one indentation
570 func (h *BufPane) IndentLine() bool {
571 if h.Cursor.HasSelection() {
575 tabsize := int(h.Buf.Settings["tabsize"].(float64))
576 indentstr := h.Buf.IndentString(tabsize)
577 h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr)
578 h.Buf.RelocateCursors()
583 // OutdentLine moves the current line back one indentation
584 func (h *BufPane) OutdentLine() bool {
585 if h.Cursor.HasSelection() {
589 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
590 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
593 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
595 h.Buf.RelocateCursors()
600 // OutdentSelection takes the current selection and moves it back one indent level
601 func (h *BufPane) OutdentSelection() bool {
602 if h.Cursor.HasSelection() {
603 start := h.Cursor.CurSelection[0]
604 end := h.Cursor.CurSelection[1]
606 start, end = end, start
607 h.Cursor.SetSelectionStart(start)
608 h.Cursor.SetSelectionEnd(end)
612 endY := end.Move(-1, h.Buf).Y
613 for y := startY; y <= endY; y++ {
614 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
615 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
618 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
621 h.Buf.RelocateCursors()
629 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
630 func (h *BufPane) Autocomplete() bool {
633 if h.Cursor.HasSelection() {
637 if !util.IsNonAlphaNumeric(h.Cursor.RuneUnder(h.Cursor.X)) {
638 // don't autocomplete if cursor is on alpha numeric character (middle of a word)
642 if b.HasSuggestions {
643 b.CycleAutocomplete(true)
646 return b.Autocomplete(buffer.BufferComplete)
649 // CycleAutocompleteBack cycles back in the autocomplete suggestion list
650 func (h *BufPane) CycleAutocompleteBack() bool {
651 if h.Cursor.HasSelection() {
655 if h.Buf.HasSuggestions {
656 h.Buf.CycleAutocomplete(false)
662 // InsertTab inserts a tab or spaces
663 func (h *BufPane) InsertTab() bool {
665 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
666 tabBytes := len(indent)
667 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
668 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
673 // SaveAll saves all open buffers
674 func (h *BufPane) SaveAll() bool {
675 for _, b := range buffer.OpenBuffers {
681 // SaveCB performs a save and does a callback at the very end (after all prompts have been resolved)
682 func (h *BufPane) SaveCB(action string, callback func()) bool {
683 // If this is an empty buffer, ask for a filename
684 if h.Buf.Path == "" {
685 h.SaveAsCB(action, callback)
687 noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
695 // Save the buffer to disk
696 func (h *BufPane) Save() bool {
697 return h.SaveCB("Save", nil)
700 // SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved)
701 func (h *BufPane) SaveAsCB(action string, callback func()) bool {
702 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
704 // the filename might or might not be quoted, so unquote first then join the strings.
705 args, err := shellquote.Split(resp)
707 InfoBar.Error("Error parsing arguments: ", err)
711 InfoBar.Error("No filename given")
714 filename := strings.Join(args, " ")
715 noPrompt := h.saveBufToFile(filename, action, callback)
717 h.completeAction(action)
724 // SaveAs saves the buffer to disk with the given name
725 func (h *BufPane) SaveAs() bool {
726 return h.SaveAsCB("SaveAs", nil)
729 // This function saves the buffer to `filename` and changes the buffer's path and name
730 // to `filename` if the save is successful
731 func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
732 err := h.Buf.SaveAs(filename)
734 if strings.HasSuffix(err.Error(), "permission denied") {
735 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
736 if yes && !canceled {
737 err = h.Buf.SaveAsWithSudo(filename)
741 h.Buf.Path = filename
742 h.Buf.SetName(filename)
743 InfoBar.Message("Saved " + filename)
745 h.completeAction(action)
756 h.Buf.Path = filename
757 h.Buf.SetName(filename)
758 InfoBar.Message("Saved " + filename)
766 // Find opens a prompt and searches forward for the input
767 func (h *BufPane) Find() bool {
768 h.searchOrig = h.Cursor.Loc
769 InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
771 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
773 h.Cursor.SetSelectionStart(match[0])
774 h.Cursor.SetSelectionEnd(match[1])
775 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
776 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
777 h.Cursor.GotoLoc(match[1])
779 h.Cursor.GotoLoc(h.searchOrig)
780 h.Cursor.ResetSelection()
783 }, func(resp string, canceled bool) {
786 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
791 h.Cursor.SetSelectionStart(match[0])
792 h.Cursor.SetSelectionEnd(match[1])
793 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
794 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
795 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
798 h.Cursor.ResetSelection()
799 InfoBar.Message("No matches found")
802 h.Cursor.ResetSelection()
810 // FindNext searches forwards for the last used search term
811 func (h *BufPane) FindNext() bool {
812 // If the cursor is at the start of a selection and we search we want
813 // to search from the end of the selection in the case that
814 // the selection is a search result in which case we wouldn't move at
815 // at all which would be bad
816 searchLoc := h.Cursor.Loc
817 if h.Cursor.HasSelection() {
818 searchLoc = h.Cursor.CurSelection[1]
820 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
825 h.Cursor.SetSelectionStart(match[0])
826 h.Cursor.SetSelectionEnd(match[1])
827 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
828 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
829 h.Cursor.Loc = h.Cursor.CurSelection[1]
831 h.Cursor.ResetSelection()
837 // FindPrevious searches backwards for the last used search term
838 func (h *BufPane) FindPrevious() bool {
839 // If the cursor is at the end of a selection and we search we want
840 // to search from the beginning of the selection in the case that
841 // the selection is a search result in which case we wouldn't move at
842 // at all which would be bad
843 searchLoc := h.Cursor.Loc
844 if h.Cursor.HasSelection() {
845 searchLoc = h.Cursor.CurSelection[0]
847 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
852 h.Cursor.SetSelectionStart(match[0])
853 h.Cursor.SetSelectionEnd(match[1])
854 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
855 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
856 h.Cursor.Loc = h.Cursor.CurSelection[1]
858 h.Cursor.ResetSelection()
864 // Undo undoes the last action
865 func (h *BufPane) Undo() bool {
867 InfoBar.Message("Undid action")
872 // Redo redoes the last action
873 func (h *BufPane) Redo() bool {
875 InfoBar.Message("Redid action")
880 // Copy the selection to the system clipboard
881 func (h *BufPane) Copy() bool {
882 if h.Cursor.HasSelection() {
883 h.Cursor.CopySelection("clipboard")
885 if clipboard.Unsupported {
886 InfoBar.Message("Copied selection (install xclip for external clipboard)")
888 InfoBar.Message("Copied selection")
895 // CutLine cuts the current line to the clipboard
896 func (h *BufPane) CutLine() bool {
897 h.Cursor.SelectLine()
898 if !h.Cursor.HasSelection() {
901 if h.freshClip == true {
902 if h.Cursor.HasSelection() {
903 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
904 // messenger.Error(err)
906 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
909 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
913 h.lastCutTime = time.Now()
914 h.Cursor.DeleteSelection()
915 h.Cursor.ResetSelection()
916 InfoBar.Message("Cut line")
921 // Cut the selection to the system clipboard
922 func (h *BufPane) Cut() bool {
923 if h.Cursor.HasSelection() {
924 h.Cursor.CopySelection("clipboard")
925 h.Cursor.DeleteSelection()
926 h.Cursor.ResetSelection()
928 InfoBar.Message("Cut selection")
937 // DuplicateLine duplicates the current line or selection
938 func (h *BufPane) DuplicateLine() bool {
939 if h.Cursor.HasSelection() {
940 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
943 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
947 InfoBar.Message("Duplicated line")
952 // DeleteLine deletes the current line
953 func (h *BufPane) DeleteLine() bool {
954 h.Cursor.SelectLine()
955 if !h.Cursor.HasSelection() {
958 h.Cursor.DeleteSelection()
959 h.Cursor.ResetSelection()
960 InfoBar.Message("Deleted line")
965 // MoveLinesUp moves up the current line or selected lines if any
966 func (h *BufPane) MoveLinesUp() bool {
967 if h.Cursor.HasSelection() {
968 if h.Cursor.CurSelection[0].Y == 0 {
969 InfoBar.Message("Cannot move further up")
972 start := h.Cursor.CurSelection[0].Y
973 end := h.Cursor.CurSelection[1].Y
975 end, start = start, end
982 h.Cursor.CurSelection[1].Y -= 1
984 if h.Cursor.Loc.Y == 0 {
985 InfoBar.Message("Cannot move further up")
998 // MoveLinesDown moves down the current line or selected lines if any
999 func (h *BufPane) MoveLinesDown() bool {
1000 if h.Cursor.HasSelection() {
1001 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
1002 InfoBar.Message("Cannot move further down")
1005 start := h.Cursor.CurSelection[0].Y
1006 end := h.Cursor.CurSelection[1].Y
1008 end, start = start, end
1011 h.Buf.MoveLinesDown(
1016 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
1017 InfoBar.Message("Cannot move further down")
1020 h.Buf.MoveLinesDown(
1030 // Paste whatever is in the system clipboard into the buffer
1031 // Delete and paste if the user has a selection
1032 func (h *BufPane) Paste() bool {
1033 clip, _ := clipboard.ReadAll("clipboard")
1039 // PastePrimary pastes from the primary clipboard (only use on linux)
1040 func (h *BufPane) PastePrimary() bool {
1041 clip, _ := clipboard.ReadAll("primary")
1047 func (h *BufPane) paste(clip string) {
1048 if h.Buf.Settings["smartpaste"].(bool) {
1049 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
1050 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
1051 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
1055 if h.Cursor.HasSelection() {
1056 h.Cursor.DeleteSelection()
1057 h.Cursor.ResetSelection()
1060 h.Buf.Insert(h.Cursor.Loc, clip)
1061 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
1063 if clipboard.Unsupported {
1064 InfoBar.Message("Pasted clipboard (install xclip for external clipboard)")
1066 InfoBar.Message("Pasted clipboard")
1070 // JumpToMatchingBrace moves the cursor to the matching brace if it is
1071 // currently on a brace
1072 func (h *BufPane) JumpToMatchingBrace() bool {
1073 for _, bp := range buffer.BracePairs {
1074 r := h.Cursor.RuneUnder(h.Cursor.X)
1075 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
1076 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
1077 matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
1080 h.Cursor.GotoLoc(matchingBrace)
1082 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1094 // SelectAll selects the entire buffer
1095 func (h *BufPane) SelectAll() bool {
1096 h.Cursor.SetSelectionStart(h.Buf.Start())
1097 h.Cursor.SetSelectionEnd(h.Buf.End())
1098 // Put the cursor at the beginning
1105 // OpenFile opens a new file in the buffer
1106 func (h *BufPane) OpenFile() bool {
1107 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1109 h.HandleCommand(resp)
1115 // Start moves the viewport to the start of the buffer
1116 func (h *BufPane) Start() bool {
1123 // End moves the viewport to the end of the buffer
1124 func (h *BufPane) End() bool {
1125 // TODO: softwrap problems?
1127 if v.Height > h.Buf.LinesNum() {
1131 v.StartLine = h.Buf.LinesNum() - v.Height
1137 // PageUp scrolls the view up a page
1138 func (h *BufPane) PageUp() bool {
1140 if v.StartLine > v.Height {
1141 h.ScrollUp(v.Height)
1149 // PageDown scrolls the view down a page
1150 func (h *BufPane) PageDown() bool {
1152 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1153 h.ScrollDown(v.Height)
1154 } else if h.Buf.LinesNum() >= v.Height {
1155 v.StartLine = h.Buf.LinesNum() - v.Height
1160 // SelectPageUp selects up one page
1161 func (h *BufPane) SelectPageUp() bool {
1162 if !h.Cursor.HasSelection() {
1163 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1165 h.Cursor.UpN(h.GetView().Height)
1166 h.Cursor.SelectTo(h.Cursor.Loc)
1171 // SelectPageDown selects down one page
1172 func (h *BufPane) SelectPageDown() bool {
1173 if !h.Cursor.HasSelection() {
1174 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1176 h.Cursor.DownN(h.GetView().Height)
1177 h.Cursor.SelectTo(h.Cursor.Loc)
1182 // CursorPageUp places the cursor a page up
1183 func (h *BufPane) CursorPageUp() bool {
1184 h.Cursor.Deselect(true)
1186 if h.Cursor.HasSelection() {
1187 h.Cursor.Loc = h.Cursor.CurSelection[0]
1188 h.Cursor.ResetSelection()
1189 h.Cursor.StoreVisualX()
1191 h.Cursor.UpN(h.GetView().Height)
1196 // CursorPageDown places the cursor a page up
1197 func (h *BufPane) CursorPageDown() bool {
1198 h.Cursor.Deselect(false)
1200 if h.Cursor.HasSelection() {
1201 h.Cursor.Loc = h.Cursor.CurSelection[1]
1202 h.Cursor.ResetSelection()
1203 h.Cursor.StoreVisualX()
1205 h.Cursor.DownN(h.GetView().Height)
1210 // HalfPageUp scrolls the view up half a page
1211 func (h *BufPane) HalfPageUp() bool {
1213 if v.StartLine > v.Height/2 {
1214 h.ScrollUp(v.Height / 2)
1222 // HalfPageDown scrolls the view down half a page
1223 func (h *BufPane) HalfPageDown() bool {
1225 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1226 h.ScrollDown(v.Height / 2)
1228 if h.Buf.LinesNum() >= v.Height {
1229 v.StartLine = h.Buf.LinesNum() - v.Height
1236 // ToggleDiffGutter turns the diff gutter off and on
1237 func (h *BufPane) ToggleDiffGutter() bool {
1238 if !h.Buf.Settings["diffgutter"].(bool) {
1239 h.Buf.Settings["diffgutter"] = true
1240 h.Buf.UpdateDiff(func(synchronous bool) {
1243 InfoBar.Message("Enabled diff gutter")
1245 h.Buf.Settings["diffgutter"] = false
1246 InfoBar.Message("Disabled diff gutter")
1251 // ToggleRuler turns line numbers off and on
1252 func (h *BufPane) ToggleRuler() bool {
1253 if !h.Buf.Settings["ruler"].(bool) {
1254 h.Buf.Settings["ruler"] = true
1255 InfoBar.Message("Enabled ruler")
1257 h.Buf.Settings["ruler"] = false
1258 InfoBar.Message("Disabled ruler")
1263 // ClearStatus clears the messenger bar
1264 func (h *BufPane) ClearStatus() bool {
1269 // ToggleHelp toggles the help screen
1270 func (h *BufPane) ToggleHelp() bool {
1271 if h.Buf.Type == buffer.BTHelp {
1279 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1280 func (h *BufPane) ToggleKeyMenu() bool {
1281 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1286 // ShellMode opens a terminal to run a shell command
1287 func (h *BufPane) ShellMode() bool {
1288 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1290 // The true here is for openTerm to make the command interactive
1291 shell.RunInteractiveShell(resp, true, false)
1298 // CommandMode lets the user enter a command
1299 func (h *BufPane) CommandMode() bool {
1300 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1302 h.HandleCommand(resp)
1308 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1309 func (h *BufPane) ToggleOverwriteMode() bool {
1310 h.isOverwriteMode = !h.isOverwriteMode
1314 // Escape leaves current mode
1315 func (h *BufPane) Escape() bool {
1319 // Quit this will close the current tab or view that is open
1320 func (h *BufPane) Quit() bool {
1323 if len(MainTab().Panes) > 1 {
1325 } else if len(Tabs.List) > 1 {
1326 Tabs.RemoveTab(h.splitID)
1328 screen.Screen.Fini()
1333 if h.Buf.Modified() {
1334 if config.GlobalSettings["autosave"].(float64) > 0 {
1335 // autosave on means we automatically save when quitting
1336 h.SaveCB("Quit", func() {
1340 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1341 if !canceled && !yes {
1343 } else if !canceled && yes {
1344 h.SaveCB("Quit", func() {
1356 // QuitAll quits the whole editor; all splits and tabs
1357 func (h *BufPane) QuitAll() bool {
1358 anyModified := false
1359 for _, b := range buffer.OpenBuffers {
1367 for _, b := range buffer.OpenBuffers {
1370 screen.Screen.Fini()
1376 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1377 if !canceled && yes {
1388 // AddTab adds a new tab with an empty buffer
1389 func (h *BufPane) AddTab() bool {
1390 width, height := screen.Screen.Size()
1391 iOffset := config.GetInfoBarOffset()
1392 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1393 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1395 Tabs.SetActive(len(Tabs.List) - 1)
1400 // PreviousTab switches to the previous tab in the tab list
1401 func (h *BufPane) PreviousTab() bool {
1403 Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1408 // NextTab switches to the next tab in the tab list
1409 func (h *BufPane) NextTab() bool {
1411 Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1415 // VSplitAction opens an empty vertical split
1416 func (h *BufPane) VSplitAction() bool {
1417 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1422 // HSplitAction opens an empty horizontal split
1423 func (h *BufPane) HSplitAction() bool {
1424 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1429 // Unsplit closes all splits in the current tab except the active one
1430 func (h *BufPane) Unsplit() bool {
1432 n := tab.GetNode(h.splitID)
1435 tab.RemovePane(tab.GetPane(h.splitID))
1437 tab.SetActive(len(tab.Panes) - 1)
1444 // NextSplit changes the view to the next split
1445 func (h *BufPane) NextSplit() bool {
1447 if a < len(h.tab.Panes)-1 {
1458 // PreviousSplit changes the view to the previous split
1459 func (h *BufPane) PreviousSplit() bool {
1464 a = len(h.tab.Panes) - 1
1471 var curmacro []interface{}
1472 var recording_macro bool
1474 // ToggleMacro toggles recording of a macro
1475 func (h *BufPane) ToggleMacro() bool {
1476 recording_macro = !recording_macro
1477 if recording_macro {
1478 curmacro = []interface{}{}
1479 InfoBar.Message("Recording")
1481 InfoBar.Message("Stopped recording")
1487 // PlayMacro plays back the most recently recorded macro
1488 func (h *BufPane) PlayMacro() bool {
1489 if recording_macro {
1492 for _, action := range curmacro {
1493 switch t := action.(type) {
1496 case func(*BufPane) bool:
1504 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1505 func (h *BufPane) SpawnMultiCursor() bool {
1506 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1507 if !spawner.HasSelection() {
1508 spawner.SelectWord()
1514 sel := spawner.GetSelection()
1515 searchStart := spawner.CurSelection[1]
1517 search := string(sel)
1518 search = regexp.QuoteMeta(search)
1520 search = "\\b" + search + "\\b"
1522 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1527 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1528 c.SetSelectionStart(match[0])
1529 c.SetSelectionEnd(match[1])
1530 c.OrigSelection[0] = c.CurSelection[0]
1531 c.OrigSelection[1] = c.CurSelection[1]
1532 c.Loc = c.CurSelection[1]
1535 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1536 h.Buf.MergeCursors()
1538 InfoBar.Message("No matches found")
1545 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
1546 func (h *BufPane) SpawnMultiCursorUp() bool {
1547 if h.Cursor.Y == 0 {
1550 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1554 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1556 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1557 h.Buf.MergeCursors()
1563 // SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more.
1564 func (h *BufPane) SpawnMultiCursorDown() bool {
1565 if h.Cursor.Y+1 == h.Buf.LinesNum() {
1568 h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1})
1572 c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1})
1574 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1575 h.Buf.MergeCursors()
1580 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1581 func (h *BufPane) SpawnMultiCursorSelect() bool {
1582 // Avoid cases where multiple cursors already exist, that would create problems
1583 if h.Buf.NumCursors() > 1 {
1590 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1592 startLine, endLine = b, a
1594 startLine, endLine = a, b
1597 if h.Cursor.HasSelection() {
1598 h.Cursor.ResetSelection()
1599 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1601 for i := startLine; i <= endLine; i++ {
1602 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1606 h.Buf.MergeCursors()
1610 InfoBar.Message("Added cursors from selection")
1614 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1615 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1617 mx, my := e.Position()
1618 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1619 c := buffer.NewCursor(b, mouseLoc)
1626 // SkipMultiCursor moves the current multiple cursor to the next available position
1627 func (h *BufPane) SkipMultiCursor() bool {
1628 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1629 sel := lastC.GetSelection()
1630 searchStart := lastC.CurSelection[1]
1632 search := string(sel)
1633 search = regexp.QuoteMeta(search)
1635 search = "\\b" + search + "\\b"
1638 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1643 lastC.SetSelectionStart(match[0])
1644 lastC.SetSelectionEnd(match[1])
1645 lastC.OrigSelection[0] = lastC.CurSelection[0]
1646 lastC.OrigSelection[1] = lastC.CurSelection[1]
1647 lastC.Loc = lastC.CurSelection[1]
1649 h.Buf.MergeCursors()
1650 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1652 InfoBar.Message("No matches found")
1658 // RemoveMultiCursor removes the latest multiple cursor
1659 func (h *BufPane) RemoveMultiCursor() bool {
1660 if h.Buf.NumCursors() > 1 {
1661 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1662 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1663 h.Buf.UpdateCursors()
1671 // RemoveAllMultiCursors removes all cursors except the base cursor
1672 func (h *BufPane) RemoveAllMultiCursors() bool {
1673 h.Buf.ClearCursors()
1679 // None is an action that does nothing
1680 func (h *BufPane) None() bool {