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) {
29 // ScrollDown is not an action
30 func (h *BufPane) ScrollDown(n int) {
32 if v.StartLine <= h.Buf.LinesNum()-1-n {
38 // MousePress is the event that should happen when a normal click happens
39 // This is almost always bound to left click
40 func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
42 mx, my := e.Position()
43 mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
44 h.Cursor.Loc = mouseLoc
46 if b.NumCursors() > 1 {
49 h.Cursor = h.Buf.GetActiveCursor()
50 h.Cursor.Loc = mouseLoc
52 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
55 h.lastClickTime = time.Now()
61 h.Cursor.CopySelection("primary")
64 h.lastClickTime = time.Now()
70 h.Cursor.CopySelection("primary")
75 h.lastClickTime = time.Now()
77 h.Cursor.OrigSelection[0] = h.Cursor.Loc
78 h.Cursor.CurSelection[0] = h.Cursor.Loc
79 h.Cursor.CurSelection[1] = h.Cursor.Loc
81 h.mouseReleased = false
82 } else if !h.mouseReleased {
84 h.Cursor.AddLineToSelection()
85 } else if h.doubleClick {
86 h.Cursor.AddWordToSelection()
88 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
89 h.Cursor.CopySelection("primary")
93 h.Cursor.StoreVisualX()
98 // ScrollUpAction scrolls the view up
99 func (h *BufPane) ScrollUpAction() bool {
100 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
104 // ScrollDownAction scrolls the view up
105 func (h *BufPane) ScrollDownAction() bool {
106 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
110 // Center centers the view on the cursor
111 func (h *BufPane) Center() bool {
113 v.StartLine = h.Cursor.Y - v.Height/2
114 if v.StartLine+v.Height > h.Buf.LinesNum() {
115 v.StartLine = h.Buf.LinesNum() - v.Height
125 // CursorUp moves the cursor up
126 func (h *BufPane) CursorUp() bool {
127 h.Cursor.Deselect(true)
133 // CursorDown moves the cursor down
134 func (h *BufPane) CursorDown() bool {
135 h.Cursor.Deselect(true)
141 // CursorLeft moves the cursor left
142 func (h *BufPane) CursorLeft() bool {
143 if h.Cursor.HasSelection() {
144 h.Cursor.Deselect(true)
146 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
147 tabmovement := h.Buf.Settings["tabmovement"].(bool)
148 if tabstospaces && tabmovement {
149 tabsize := int(h.Buf.Settings["tabsize"].(float64))
150 line := h.Buf.LineBytes(h.Cursor.Y)
151 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
152 for i := 0; i < tabsize; i++ {
166 // CursorRight moves the cursor right
167 func (h *BufPane) CursorRight() bool {
168 if h.Cursor.HasSelection() {
169 h.Cursor.Deselect(false)
170 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
172 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
173 tabmovement := h.Buf.Settings["tabmovement"].(bool)
174 if tabstospaces && tabmovement {
175 tabsize := int(h.Buf.Settings["tabsize"].(float64))
176 line := h.Buf.LineBytes(h.Cursor.Y)
177 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]) {
178 for i := 0; i < tabsize; i++ {
193 // WordRight moves the cursor one word to the right
194 func (h *BufPane) WordRight() bool {
195 h.Cursor.Deselect(false)
201 // WordLeft moves the cursor one word to the left
202 func (h *BufPane) WordLeft() bool {
203 h.Cursor.Deselect(true)
209 // SelectUp selects up one line
210 func (h *BufPane) SelectUp() bool {
211 if !h.Cursor.HasSelection() {
212 h.Cursor.OrigSelection[0] = h.Cursor.Loc
215 h.Cursor.SelectTo(h.Cursor.Loc)
220 // SelectDown selects down one line
221 func (h *BufPane) SelectDown() bool {
222 if !h.Cursor.HasSelection() {
223 h.Cursor.OrigSelection[0] = h.Cursor.Loc
226 h.Cursor.SelectTo(h.Cursor.Loc)
231 // SelectLeft selects the character to the left of the cursor
232 func (h *BufPane) SelectLeft() bool {
235 if loc.GreaterThan(count) {
238 if !h.Cursor.HasSelection() {
239 h.Cursor.OrigSelection[0] = loc
242 h.Cursor.SelectTo(h.Cursor.Loc)
247 // SelectRight selects the character to the right of the cursor
248 func (h *BufPane) SelectRight() bool {
251 if loc.GreaterThan(count) {
254 if !h.Cursor.HasSelection() {
255 h.Cursor.OrigSelection[0] = loc
258 h.Cursor.SelectTo(h.Cursor.Loc)
263 // SelectWordRight selects the word to the right of the cursor
264 func (h *BufPane) SelectWordRight() bool {
265 if !h.Cursor.HasSelection() {
266 h.Cursor.OrigSelection[0] = h.Cursor.Loc
269 h.Cursor.SelectTo(h.Cursor.Loc)
274 // SelectWordLeft selects the word to the left of the cursor
275 func (h *BufPane) SelectWordLeft() bool {
276 if !h.Cursor.HasSelection() {
277 h.Cursor.OrigSelection[0] = h.Cursor.Loc
280 h.Cursor.SelectTo(h.Cursor.Loc)
285 // StartOfLine moves the cursor to the start of the line
286 func (h *BufPane) StartOfLine() bool {
287 h.Cursor.Deselect(true)
288 h.Cursor.StartOfText()
289 // if h.Cursor.X != 0 {
292 // h.Cursor.StartOfText()
298 // EndOfLine moves the cursor to the end of the line
299 func (h *BufPane) EndOfLine() bool {
300 h.Cursor.Deselect(true)
306 // SelectLine selects the entire current line
307 func (h *BufPane) SelectLine() bool {
308 h.Cursor.SelectLine()
313 // SelectToStartOfLine selects to the start of the current line
314 func (h *BufPane) SelectToStartOfLine() bool {
315 if !h.Cursor.HasSelection() {
316 h.Cursor.OrigSelection[0] = h.Cursor.Loc
319 h.Cursor.SelectTo(h.Cursor.Loc)
324 // SelectToEndOfLine selects to the end of the current line
325 func (h *BufPane) SelectToEndOfLine() bool {
326 if !h.Cursor.HasSelection() {
327 h.Cursor.OrigSelection[0] = h.Cursor.Loc
330 h.Cursor.SelectTo(h.Cursor.Loc)
335 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
336 func (h *BufPane) ParagraphPrevious() bool {
338 for line = h.Cursor.Y; line > 0; line-- {
339 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
345 // If no empty line found. move cursor to end of buffer
347 h.Cursor.Loc = h.Buf.Start()
353 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
354 func (h *BufPane) ParagraphNext() bool {
356 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
357 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
363 // If no empty line found. move cursor to end of buffer
364 if line == h.Buf.LinesNum() {
365 h.Cursor.Loc = h.Buf.End()
371 // Retab changes all tabs to spaces or all spaces to tabs depending
372 // on the user's settings
373 func (h *BufPane) Retab() bool {
379 // CursorStart moves the cursor to the start of the buffer
380 func (h *BufPane) CursorStart() bool {
381 h.Cursor.Deselect(true)
388 // CursorEnd moves the cursor to the end of the buffer
389 func (h *BufPane) CursorEnd() bool {
390 h.Cursor.Deselect(true)
391 h.Cursor.Loc = h.Buf.End()
392 h.Cursor.StoreVisualX()
397 // SelectToStart selects the text from the cursor to the start of the buffer
398 func (h *BufPane) SelectToStart() bool {
399 if !h.Cursor.HasSelection() {
400 h.Cursor.OrigSelection[0] = h.Cursor.Loc
403 h.Cursor.SelectTo(h.Buf.Start())
408 // SelectToEnd selects the text from the cursor to the end of the buffer
409 func (h *BufPane) SelectToEnd() bool {
410 if !h.Cursor.HasSelection() {
411 h.Cursor.OrigSelection[0] = h.Cursor.Loc
414 h.Cursor.SelectTo(h.Buf.End())
419 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
420 func (h *BufPane) InsertNewline() bool {
422 if h.Cursor.HasSelection() {
423 h.Cursor.DeleteSelection()
424 h.Cursor.ResetSelection()
427 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
429 h.Buf.Insert(h.Cursor.Loc, "\n")
432 if h.Buf.Settings["autoindent"].(bool) {
436 h.Buf.Insert(h.Cursor.Loc, string(ws))
437 // for i := 0; i < len(ws); i++ {
441 // Remove the whitespaces if keepautoindent setting is off
442 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
443 line := h.Buf.LineBytes(h.Cursor.Y - 1)
444 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
447 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
452 // Backspace deletes the previous character
453 func (h *BufPane) Backspace() bool {
454 if h.Cursor.HasSelection() {
455 h.Cursor.DeleteSelection()
456 h.Cursor.ResetSelection()
457 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
458 // We have to do something a bit hacky here because we want to
459 // delete the line by first moving left and then deleting backwards
460 // but the undo redo would place the cursor in the wrong place
461 // So instead we move left, save the position, move back, delete
462 // and restore the position
464 // If the user is using spaces instead of tabs and they are deleting
465 // whitespace at the start of the line, we should delete as if it's a
466 // tab (tabSize number of spaces)
467 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
468 tabSize := int(h.Buf.Settings["tabsize"].(float64))
469 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
471 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
474 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
477 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
482 // DeleteWordRight deletes the word to the right of the cursor
483 func (h *BufPane) DeleteWordRight() bool {
485 if h.Cursor.HasSelection() {
486 h.Cursor.DeleteSelection()
487 h.Cursor.ResetSelection()
493 // DeleteWordLeft deletes the word to the left of the cursor
494 func (h *BufPane) DeleteWordLeft() bool {
496 if h.Cursor.HasSelection() {
497 h.Cursor.DeleteSelection()
498 h.Cursor.ResetSelection()
504 // Delete deletes the next character
505 func (h *BufPane) Delete() bool {
506 if h.Cursor.HasSelection() {
507 h.Cursor.DeleteSelection()
508 h.Cursor.ResetSelection()
511 if loc.LessThan(h.Buf.End()) {
512 h.Buf.Remove(loc, loc.Move(1, h.Buf))
519 // IndentSelection indents the current selection
520 func (h *BufPane) IndentSelection() bool {
521 if h.Cursor.HasSelection() {
522 start := h.Cursor.CurSelection[0]
523 end := h.Cursor.CurSelection[1]
525 start, end = end, start
526 h.Cursor.SetSelectionStart(start)
527 h.Cursor.SetSelectionEnd(end)
531 endY := end.Move(-1, h.Buf).Y
532 endX := end.Move(-1, h.Buf).X
533 tabsize := int(h.Buf.Settings["tabsize"].(float64))
534 indentsize := len(h.Buf.IndentString(tabsize))
535 for y := startY; y <= endY; y++ {
536 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
537 if y == startY && start.X > 0 {
538 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
541 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
544 h.Buf.RelocateCursors()
552 // OutdentLine moves the current line back one indentation
553 func (h *BufPane) OutdentLine() bool {
554 if h.Cursor.HasSelection() {
558 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
559 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
562 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
564 h.Buf.RelocateCursors()
569 // OutdentSelection takes the current selection and moves it back one indent level
570 func (h *BufPane) OutdentSelection() bool {
571 if h.Cursor.HasSelection() {
572 start := h.Cursor.CurSelection[0]
573 end := h.Cursor.CurSelection[1]
575 start, end = end, start
576 h.Cursor.SetSelectionStart(start)
577 h.Cursor.SetSelectionEnd(end)
581 endY := end.Move(-1, h.Buf).Y
582 for y := startY; y <= endY; y++ {
583 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
584 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
587 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
590 h.Buf.RelocateCursors()
598 // Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
599 func (h *BufPane) Autocomplete() bool {
602 if h.Cursor.HasSelection() {
606 if b.HasSuggestions {
607 b.CycleAutocomplete(true)
610 return b.Autocomplete(buffer.BufferComplete)
613 // InsertTab inserts a tab or spaces
614 func (h *BufPane) InsertTab() bool {
616 l := b.LineBytes(h.Cursor.Y)
617 l = util.SliceStart(l, h.Cursor.X)
618 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
619 tabBytes := len(indent)
620 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
621 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
626 // SaveAll saves all open buffers
627 func (h *BufPane) SaveAll() bool {
628 for _, b := range buffer.OpenBuffers {
634 // Save the buffer to disk
635 func (h *BufPane) Save() bool {
636 // If this is an empty buffer, ask for a filename
637 if h.Buf.Path == "" {
640 h.saveBufToFile(h.Buf.Path)
646 // SaveAs saves the buffer to disk with the given name
647 func (h *BufPane) SaveAs() bool {
648 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
650 // the filename might or might not be quoted, so unquote first then join the strings.
651 args, err := shellwords.Split(resp)
652 filename := strings.Join(args, " ")
654 InfoBar.Error("Error parsing arguments: ", err)
657 h.saveBufToFile(filename)
664 // This function saves the buffer to `filename` and changes the buffer's path and name
665 // to `filename` if the save is successful
666 func (h *BufPane) saveBufToFile(filename string) {
667 err := h.Buf.SaveAs(filename)
669 if strings.HasSuffix(err.Error(), "permission denied") {
670 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
671 if yes && !canceled {
672 err = h.Buf.SaveAsWithSudo(filename)
676 h.Buf.Path = filename
677 h.Buf.SetName(filename)
678 InfoBar.Message("Saved " + filename)
686 h.Buf.Path = filename
687 h.Buf.SetName(filename)
688 InfoBar.Message("Saved " + filename)
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 InfoBar.Message("Copied selection")
817 // CutLine cuts the current line to the clipboard
818 func (h *BufPane) CutLine() bool {
819 h.Cursor.SelectLine()
820 if !h.Cursor.HasSelection() {
823 if h.freshClip == true {
824 if h.Cursor.HasSelection() {
825 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
826 // messenger.Error(err)
828 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
831 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
835 h.lastCutTime = time.Now()
836 h.Cursor.DeleteSelection()
837 h.Cursor.ResetSelection()
838 InfoBar.Message("Cut line")
843 // Cut the selection to the system clipboard
844 func (h *BufPane) Cut() bool {
845 if h.Cursor.HasSelection() {
846 h.Cursor.CopySelection("clipboard")
847 h.Cursor.DeleteSelection()
848 h.Cursor.ResetSelection()
850 InfoBar.Message("Cut selection")
859 // DuplicateLine duplicates the current line or selection
860 func (h *BufPane) DuplicateLine() bool {
861 if h.Cursor.HasSelection() {
862 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
865 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
869 InfoBar.Message("Duplicated line")
874 // DeleteLine deletes the current line
875 func (h *BufPane) DeleteLine() bool {
876 h.Cursor.SelectLine()
877 if !h.Cursor.HasSelection() {
880 h.Cursor.DeleteSelection()
881 h.Cursor.ResetSelection()
882 InfoBar.Message("Deleted line")
887 // MoveLinesUp moves up the current line or selected lines if any
888 func (h *BufPane) MoveLinesUp() bool {
889 if h.Cursor.HasSelection() {
890 if h.Cursor.CurSelection[0].Y == 0 {
891 InfoBar.Message("Cannot move further up")
894 start := h.Cursor.CurSelection[0].Y
895 end := h.Cursor.CurSelection[1].Y
897 end, start = start, end
904 h.Cursor.CurSelection[1].Y -= 1
906 if h.Cursor.Loc.Y == 0 {
907 InfoBar.Message("Cannot move further up")
920 // MoveLinesDown moves down the current line or selected lines if any
921 func (h *BufPane) MoveLinesDown() bool {
922 if h.Cursor.HasSelection() {
923 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
924 InfoBar.Message("Cannot move further down")
927 start := h.Cursor.CurSelection[0].Y
928 end := h.Cursor.CurSelection[1].Y
930 end, start = start, end
938 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
939 InfoBar.Message("Cannot move further down")
952 // Paste whatever is in the system clipboard into the buffer
953 // Delete and paste if the user has a selection
954 func (h *BufPane) Paste() bool {
955 clip, _ := clipboard.ReadAll("clipboard")
961 // PastePrimary pastes from the primary clipboard (only use on linux)
962 func (h *BufPane) PastePrimary() bool {
963 clip, _ := clipboard.ReadAll("primary")
969 func (h *BufPane) paste(clip string) {
970 if h.Buf.Settings["smartpaste"].(bool) {
971 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
972 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
973 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
977 if h.Cursor.HasSelection() {
978 h.Cursor.DeleteSelection()
979 h.Cursor.ResetSelection()
982 h.Buf.Insert(h.Cursor.Loc, clip)
983 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
985 InfoBar.Message("Pasted clipboard")
988 // JumpToMatchingBrace moves the cursor to the matching brace if it is
989 // currently on a brace
990 func (h *BufPane) JumpToMatchingBrace() bool {
991 for _, bp := range buffer.BracePairs {
992 r := h.Cursor.RuneUnder(h.Cursor.X)
993 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
994 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
995 matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
997 h.Cursor.GotoLoc(matchingBrace)
999 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
1008 // SelectAll selects the entire buffer
1009 func (h *BufPane) SelectAll() bool {
1010 h.Cursor.SetSelectionStart(h.Buf.Start())
1011 h.Cursor.SetSelectionEnd(h.Buf.End())
1012 // Put the cursor at the beginning
1019 // OpenFile opens a new file in the buffer
1020 func (h *BufPane) OpenFile() bool {
1021 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
1023 h.HandleCommand(resp)
1029 // Start moves the viewport to the start of the buffer
1030 func (h *BufPane) Start() bool {
1037 // End moves the viewport to the end of the buffer
1038 func (h *BufPane) End() bool {
1039 // TODO: softwrap problems?
1041 if v.Height > h.Buf.LinesNum() {
1045 v.StartLine = h.Buf.LinesNum() - v.Height
1051 // PageUp scrolls the view up a page
1052 func (h *BufPane) PageUp() bool {
1054 if v.StartLine > v.Height {
1055 h.ScrollUp(v.Height)
1063 // PageDown scrolls the view down a page
1064 func (h *BufPane) PageDown() bool {
1066 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1067 h.ScrollDown(v.Height)
1068 } else if h.Buf.LinesNum() >= v.Height {
1069 v.StartLine = h.Buf.LinesNum() - v.Height
1074 // SelectPageUp selects up one page
1075 func (h *BufPane) SelectPageUp() bool {
1076 if !h.Cursor.HasSelection() {
1077 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1079 h.Cursor.UpN(h.GetView().Height)
1080 h.Cursor.SelectTo(h.Cursor.Loc)
1085 // SelectPageDown selects down one page
1086 func (h *BufPane) SelectPageDown() bool {
1087 if !h.Cursor.HasSelection() {
1088 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1090 h.Cursor.DownN(h.GetView().Height)
1091 h.Cursor.SelectTo(h.Cursor.Loc)
1096 // CursorPageUp places the cursor a page up
1097 func (h *BufPane) CursorPageUp() bool {
1098 h.Cursor.Deselect(true)
1100 if h.Cursor.HasSelection() {
1101 h.Cursor.Loc = h.Cursor.CurSelection[0]
1102 h.Cursor.ResetSelection()
1103 h.Cursor.StoreVisualX()
1105 h.Cursor.UpN(h.GetView().Height)
1110 // CursorPageDown places the cursor a page up
1111 func (h *BufPane) CursorPageDown() bool {
1112 h.Cursor.Deselect(false)
1114 if h.Cursor.HasSelection() {
1115 h.Cursor.Loc = h.Cursor.CurSelection[1]
1116 h.Cursor.ResetSelection()
1117 h.Cursor.StoreVisualX()
1119 h.Cursor.DownN(h.GetView().Height)
1124 // HalfPageUp scrolls the view up half a page
1125 func (h *BufPane) HalfPageUp() bool {
1127 if v.StartLine > v.Height/2 {
1128 h.ScrollUp(v.Height / 2)
1136 // HalfPageDown scrolls the view down half a page
1137 func (h *BufPane) HalfPageDown() bool {
1139 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1140 h.ScrollDown(v.Height / 2)
1142 if h.Buf.LinesNum() >= v.Height {
1143 v.StartLine = h.Buf.LinesNum() - v.Height
1150 // ToggleRuler turns line numbers off and on
1151 func (h *BufPane) ToggleRuler() bool {
1152 if !h.Buf.Settings["ruler"].(bool) {
1153 h.Buf.Settings["ruler"] = true
1154 InfoBar.Message("Enabled ruler")
1156 h.Buf.Settings["ruler"] = false
1157 InfoBar.Message("Disabled ruler")
1162 // ClearStatus clears the messenger bar
1163 func (h *BufPane) ClearStatus() bool {
1168 // ToggleHelp toggles the help screen
1169 func (h *BufPane) ToggleHelp() bool {
1170 if h.Buf.Type == buffer.BTHelp {
1178 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1179 func (h *BufPane) ToggleKeyMenu() bool {
1180 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1185 // ShellMode opens a terminal to run a shell command
1186 func (h *BufPane) ShellMode() bool {
1187 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1189 // The true here is for openTerm to make the command interactive
1190 shell.RunInteractiveShell(resp, true, false)
1197 // CommandMode lets the user enter a command
1198 func (h *BufPane) CommandMode() bool {
1199 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1201 h.HandleCommand(resp)
1207 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1208 func (h *BufPane) ToggleOverwriteMode() bool {
1209 h.isOverwriteMode = !h.isOverwriteMode
1213 // Escape leaves current mode
1214 func (h *BufPane) Escape() bool {
1218 // Quit this will close the current tab or view that is open
1219 func (h *BufPane) Quit() bool {
1222 if len(MainTab().Panes) > 1 {
1224 } else if len(Tabs.List) > 1 {
1225 Tabs.RemoveTab(h.splitID)
1227 screen.Screen.Fini()
1232 if h.Buf.Modified() {
1233 if config.GlobalSettings["autosave"].(float64) > 0 {
1234 // autosave on means we automatically save when quitting
1238 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1239 if !canceled && !yes {
1241 } else if !canceled && yes {
1253 // QuitAll quits the whole editor; all splits and tabs
1254 func (h *BufPane) QuitAll() bool {
1255 anyModified := false
1256 for _, b := range buffer.OpenBuffers {
1264 for _, b := range buffer.OpenBuffers {
1267 screen.Screen.Fini()
1273 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1274 if !canceled && yes {
1285 // AddTab adds a new tab with an empty buffer
1286 func (h *BufPane) AddTab() bool {
1287 width, height := screen.Screen.Size()
1288 iOffset := config.GetInfoBarOffset()
1289 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1290 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1292 Tabs.SetActive(len(Tabs.List) - 1)
1297 // PreviousTab switches to the previous tab in the tab list
1298 func (h *BufPane) PreviousTab() bool {
1300 Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1305 // NextTab switches to the next tab in the tab list
1306 func (h *BufPane) NextTab() bool {
1308 Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1312 // VSplitAction opens an empty vertical split
1313 func (h *BufPane) VSplitAction() bool {
1314 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1319 // HSplitAction opens an empty horizontal split
1320 func (h *BufPane) HSplitAction() bool {
1321 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1326 // Unsplit closes all splits in the current tab except the active one
1327 func (h *BufPane) Unsplit() bool {
1328 n := MainTab().GetNode(h.splitID)
1331 MainTab().RemovePane(MainTab().GetPane(h.splitID))
1333 MainTab().SetActive(len(MainTab().Panes) - 1)
1337 // NextSplit changes the view to the next split
1338 func (h *BufPane) NextSplit() bool {
1339 a := MainTab().active
1340 if a < len(MainTab().Panes)-1 {
1346 MainTab().SetActive(a)
1351 // PreviousSplit changes the view to the previous split
1352 func (h *BufPane) PreviousSplit() bool {
1353 a := MainTab().active
1357 a = len(MainTab().Panes) - 1
1359 MainTab().SetActive(a)
1364 var curmacro []interface{}
1365 var recording_macro bool
1367 // ToggleMacro toggles recording of a macro
1368 func (h *BufPane) ToggleMacro() bool {
1369 recording_macro = !recording_macro
1370 if recording_macro {
1371 curmacro = []interface{}{}
1372 InfoBar.Message("Recording")
1374 InfoBar.Message("Stopped recording")
1380 // PlayMacro plays back the most recently recorded macro
1381 func (h *BufPane) PlayMacro() bool {
1382 if recording_macro {
1385 for _, action := range curmacro {
1386 switch t := action.(type) {
1397 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1398 func (h *BufPane) SpawnMultiCursor() bool {
1399 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1400 if !spawner.HasSelection() {
1401 spawner.SelectWord()
1407 sel := spawner.GetSelection()
1408 searchStart := spawner.CurSelection[1]
1410 search := string(sel)
1411 search = regexp.QuoteMeta(search)
1413 search = "\\b" + search + "\\b"
1415 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1420 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1421 c.SetSelectionStart(match[0])
1422 c.SetSelectionEnd(match[1])
1423 c.OrigSelection[0] = c.CurSelection[0]
1424 c.OrigSelection[1] = c.CurSelection[1]
1425 c.Loc = c.CurSelection[1]
1428 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1429 h.Buf.MergeCursors()
1431 InfoBar.Message("No matches found")
1438 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1439 func (h *BufPane) SpawnMultiCursorSelect() bool {
1440 // Avoid cases where multiple cursors already exist, that would create problems
1441 if h.Buf.NumCursors() > 1 {
1448 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1450 startLine, endLine = b, a
1452 startLine, endLine = a, b
1455 if h.Cursor.HasSelection() {
1456 h.Cursor.ResetSelection()
1457 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1459 for i := startLine; i <= endLine; i++ {
1460 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1464 h.Buf.MergeCursors()
1468 InfoBar.Message("Added cursors from selection")
1472 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1473 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1475 mx, my := e.Position()
1476 mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
1477 c := buffer.NewCursor(b, mouseLoc)
1484 // SkipMultiCursor moves the current multiple cursor to the next available position
1485 func (h *BufPane) SkipMultiCursor() bool {
1486 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1487 sel := lastC.GetSelection()
1488 searchStart := lastC.CurSelection[1]
1490 search := string(sel)
1491 search = regexp.QuoteMeta(search)
1493 search = "\\b" + search + "\\b"
1496 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1501 lastC.SetSelectionStart(match[0])
1502 lastC.SetSelectionEnd(match[1])
1503 lastC.OrigSelection[0] = lastC.CurSelection[0]
1504 lastC.OrigSelection[1] = lastC.CurSelection[1]
1505 lastC.Loc = lastC.CurSelection[1]
1507 h.Buf.MergeCursors()
1508 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1510 InfoBar.Message("No matches found")
1516 // RemoveMultiCursor removes the latest multiple cursor
1517 func (h *BufPane) RemoveMultiCursor() bool {
1518 if h.Buf.NumCursors() > 1 {
1519 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1520 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1521 h.Buf.UpdateCursors()
1529 // RemoveAllMultiCursors removes all cursors except the base cursor
1530 func (h *BufPane) RemoveAllMultiCursors() bool {
1531 h.Buf.ClearCursors()
1537 // None is an action that does nothing
1538 func (h *BufPane) None() bool {