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.GetMouseLoc(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
124 // CursorUp moves the cursor up
125 func (h *BufPane) CursorUp() bool {
126 h.Cursor.Deselect(true)
131 // CursorDown moves the cursor down
132 func (h *BufPane) CursorDown() bool {
133 h.Cursor.Deselect(true)
138 // CursorLeft moves the cursor left
139 func (h *BufPane) CursorLeft() bool {
140 if h.Cursor.HasSelection() {
141 h.Cursor.Deselect(true)
143 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
144 tabmovement := h.Buf.Settings["tabmovement"].(bool)
145 if tabstospaces && tabmovement {
146 tabsize := int(h.Buf.Settings["tabsize"].(float64))
147 line := h.Buf.LineBytes(h.Cursor.Y)
148 if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
149 for i := 0; i < tabsize; i++ {
162 // CursorRight moves the cursor right
163 func (h *BufPane) CursorRight() bool {
164 if h.Cursor.HasSelection() {
165 h.Cursor.Deselect(false)
166 h.Cursor.Loc = h.Cursor.Loc.Move(1, h.Buf)
168 tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
169 tabmovement := h.Buf.Settings["tabmovement"].(bool)
170 if tabstospaces && tabmovement {
171 tabsize := int(h.Buf.Settings["tabsize"].(float64))
172 line := h.Buf.LineBytes(h.Cursor.Y)
173 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]) {
174 for i := 0; i < tabsize; i++ {
188 // WordRight moves the cursor one word to the right
189 func (h *BufPane) WordRight() bool {
190 h.Cursor.Deselect(false)
195 // WordLeft moves the cursor one word to the left
196 func (h *BufPane) WordLeft() bool {
197 h.Cursor.Deselect(true)
202 // SelectUp selects up one line
203 func (h *BufPane) SelectUp() bool {
204 if !h.Cursor.HasSelection() {
205 h.Cursor.OrigSelection[0] = h.Cursor.Loc
208 h.Cursor.SelectTo(h.Cursor.Loc)
212 // SelectDown selects down one line
213 func (h *BufPane) SelectDown() bool {
214 if !h.Cursor.HasSelection() {
215 h.Cursor.OrigSelection[0] = h.Cursor.Loc
218 h.Cursor.SelectTo(h.Cursor.Loc)
222 // SelectLeft selects the character to the left of the cursor
223 func (h *BufPane) SelectLeft() bool {
226 if loc.GreaterThan(count) {
229 if !h.Cursor.HasSelection() {
230 h.Cursor.OrigSelection[0] = loc
233 h.Cursor.SelectTo(h.Cursor.Loc)
237 // SelectRight selects the character to the right of the cursor
238 func (h *BufPane) SelectRight() bool {
241 if loc.GreaterThan(count) {
244 if !h.Cursor.HasSelection() {
245 h.Cursor.OrigSelection[0] = loc
248 h.Cursor.SelectTo(h.Cursor.Loc)
252 // SelectWordRight selects the word to the right of the cursor
253 func (h *BufPane) SelectWordRight() bool {
254 if !h.Cursor.HasSelection() {
255 h.Cursor.OrigSelection[0] = h.Cursor.Loc
258 h.Cursor.SelectTo(h.Cursor.Loc)
262 // SelectWordLeft selects the word to the left of the cursor
263 func (h *BufPane) SelectWordLeft() bool {
264 if !h.Cursor.HasSelection() {
265 h.Cursor.OrigSelection[0] = h.Cursor.Loc
268 h.Cursor.SelectTo(h.Cursor.Loc)
272 // StartOfLine moves the cursor to the start of the line
273 func (h *BufPane) StartOfLine() bool {
274 h.Cursor.Deselect(true)
275 h.Cursor.StartOfText()
276 // if h.Cursor.X != 0 {
279 // h.Cursor.StartOfText()
284 // EndOfLine moves the cursor to the end of the line
285 func (h *BufPane) EndOfLine() bool {
286 h.Cursor.Deselect(true)
291 // SelectLine selects the entire current line
292 func (h *BufPane) SelectLine() bool {
293 h.Cursor.SelectLine()
297 // SelectToStartOfLine selects to the start of the current line
298 func (h *BufPane) SelectToStartOfLine() bool {
299 if !h.Cursor.HasSelection() {
300 h.Cursor.OrigSelection[0] = h.Cursor.Loc
303 h.Cursor.SelectTo(h.Cursor.Loc)
307 // SelectToEndOfLine selects to the end of the current line
308 func (h *BufPane) SelectToEndOfLine() bool {
309 if !h.Cursor.HasSelection() {
310 h.Cursor.OrigSelection[0] = h.Cursor.Loc
313 h.Cursor.SelectTo(h.Cursor.Loc)
317 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
318 func (h *BufPane) ParagraphPrevious() bool {
320 for line = h.Cursor.Y; line > 0; line-- {
321 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
327 // If no empty line found. move cursor to end of buffer
329 h.Cursor.Loc = h.Buf.Start()
334 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
335 func (h *BufPane) ParagraphNext() bool {
337 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
338 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
344 // If no empty line found. move cursor to end of buffer
345 if line == h.Buf.LinesNum() {
346 h.Cursor.Loc = h.Buf.End()
351 // Retab changes all tabs to spaces or all spaces to tabs depending
352 // on the user's settings
353 func (h *BufPane) Retab() bool {
358 // CursorStart moves the cursor to the start of the buffer
359 func (h *BufPane) CursorStart() bool {
360 h.Cursor.Deselect(true)
366 // CursorEnd moves the cursor to the end of the buffer
367 func (h *BufPane) CursorEnd() bool {
368 h.Cursor.Deselect(true)
369 h.Cursor.Loc = h.Buf.End()
370 h.Cursor.StoreVisualX()
374 // SelectToStart selects the text from the cursor to the start of the buffer
375 func (h *BufPane) SelectToStart() bool {
376 if !h.Cursor.HasSelection() {
377 h.Cursor.OrigSelection[0] = h.Cursor.Loc
380 h.Cursor.SelectTo(h.Buf.Start())
384 // SelectToEnd selects the text from the cursor to the end of the buffer
385 func (h *BufPane) SelectToEnd() bool {
386 if !h.Cursor.HasSelection() {
387 h.Cursor.OrigSelection[0] = h.Cursor.Loc
390 h.Cursor.SelectTo(h.Buf.End())
394 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
395 func (h *BufPane) InsertNewline() bool {
397 if h.Cursor.HasSelection() {
398 h.Cursor.DeleteSelection()
399 h.Cursor.ResetSelection()
402 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
404 h.Buf.Insert(h.Cursor.Loc, "\n")
407 if h.Buf.Settings["autoindent"].(bool) {
411 h.Buf.Insert(h.Cursor.Loc, string(ws))
412 // for i := 0; i < len(ws); i++ {
416 // Remove the whitespaces if keepautoindent setting is off
417 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
418 line := h.Buf.LineBytes(h.Cursor.Y - 1)
419 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
422 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
426 // Backspace deletes the previous character
427 func (h *BufPane) Backspace() bool {
428 if h.Cursor.HasSelection() {
429 h.Cursor.DeleteSelection()
430 h.Cursor.ResetSelection()
431 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
432 // We have to do something a bit hacky here because we want to
433 // delete the line by first moving left and then deleting backwards
434 // but the undo redo would place the cursor in the wrong place
435 // So instead we move left, save the position, move back, delete
436 // and restore the position
438 // If the user is using spaces instead of tabs and they are deleting
439 // whitespace at the start of the line, we should delete as if it's a
440 // tab (tabSize number of spaces)
441 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
442 tabSize := int(h.Buf.Settings["tabsize"].(float64))
443 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
445 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
448 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
451 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
455 // DeleteWordRight deletes the word to the right of the cursor
456 func (h *BufPane) DeleteWordRight() bool {
458 if h.Cursor.HasSelection() {
459 h.Cursor.DeleteSelection()
460 h.Cursor.ResetSelection()
465 // DeleteWordLeft deletes the word to the left of the cursor
466 func (h *BufPane) DeleteWordLeft() bool {
468 if h.Cursor.HasSelection() {
469 h.Cursor.DeleteSelection()
470 h.Cursor.ResetSelection()
475 // Delete deletes the next character
476 func (h *BufPane) Delete() bool {
477 if h.Cursor.HasSelection() {
478 h.Cursor.DeleteSelection()
479 h.Cursor.ResetSelection()
482 if loc.LessThan(h.Buf.End()) {
483 h.Buf.Remove(loc, loc.Move(1, h.Buf))
489 // IndentSelection indents the current selection
490 func (h *BufPane) IndentSelection() bool {
491 if h.Cursor.HasSelection() {
492 start := h.Cursor.CurSelection[0]
493 end := h.Cursor.CurSelection[1]
495 start, end = end, start
496 h.Cursor.SetSelectionStart(start)
497 h.Cursor.SetSelectionEnd(end)
501 endY := end.Move(-1, h.Buf).Y
502 endX := end.Move(-1, h.Buf).X
503 tabsize := int(h.Buf.Settings["tabsize"].(float64))
504 indentsize := len(h.Buf.IndentString(tabsize))
505 for y := startY; y <= endY; y++ {
506 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
507 if y == startY && start.X > 0 {
508 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
511 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
514 h.Buf.RelocateCursors()
521 // OutdentLine moves the current line back one indentation
522 func (h *BufPane) OutdentLine() bool {
523 if h.Cursor.HasSelection() {
527 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
528 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
531 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
533 h.Buf.RelocateCursors()
537 // OutdentSelection takes the current selection and moves it back one indent level
538 func (h *BufPane) OutdentSelection() bool {
539 if h.Cursor.HasSelection() {
540 start := h.Cursor.CurSelection[0]
541 end := h.Cursor.CurSelection[1]
543 start, end = end, start
544 h.Cursor.SetSelectionStart(start)
545 h.Cursor.SetSelectionEnd(end)
549 endY := end.Move(-1, h.Buf).Y
550 for y := startY; y <= endY; y++ {
551 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
552 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
555 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
558 h.Buf.RelocateCursors()
565 // InsertTab inserts a tab or spaces
566 func (h *BufPane) InsertTab() bool {
568 if b.HasSuggestions {
569 b.CycleAutocomplete(true)
573 l := b.LineBytes(h.Cursor.Y)
574 l = util.SliceStart(l, h.Cursor.X)
575 hasComplete := b.Autocomplete(buffer.BufferComplete)
577 indent := b.IndentString(util.IntOpt(b.Settings["tabsize"]))
578 tabBytes := len(indent)
579 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
580 b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
586 // SaveAll saves all open buffers
587 func (h *BufPane) SaveAll() bool {
588 for _, b := range buffer.OpenBuffers {
594 // Save the buffer to disk
595 func (h *BufPane) Save() bool {
596 // If this is an empty buffer, ask for a filename
597 if h.Buf.Path == "" {
600 h.saveBufToFile(h.Buf.Path)
606 // SaveAs saves the buffer to disk with the given name
607 func (h *BufPane) SaveAs() bool {
608 InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) {
610 // the filename might or might not be quoted, so unquote first then join the strings.
611 args, err := shellwords.Split(resp)
612 filename := strings.Join(args, " ")
614 InfoBar.Error("Error parsing arguments: ", err)
617 h.saveBufToFile(filename)
624 // This function saves the buffer to `filename` and changes the buffer's path and name
625 // to `filename` if the save is successful
626 func (h *BufPane) saveBufToFile(filename string) {
627 err := h.Buf.SaveAs(filename)
629 if strings.HasSuffix(err.Error(), "permission denied") {
630 InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) {
631 if yes && !canceled {
632 err = h.Buf.SaveAsWithSudo(filename)
636 h.Buf.Path = filename
637 h.Buf.SetName(filename)
638 InfoBar.Message("Saved " + filename)
646 h.Buf.Path = filename
647 h.Buf.SetName(filename)
648 InfoBar.Message("Saved " + filename)
652 // Find opens a prompt and searches forward for the input
653 func (h *BufPane) Find() bool {
654 h.searchOrig = h.Cursor.Loc
655 InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
657 match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
659 h.Cursor.SetSelectionStart(match[0])
660 h.Cursor.SetSelectionEnd(match[1])
661 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
662 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
663 h.Cursor.GotoLoc(match[1])
665 h.Cursor.GotoLoc(h.searchOrig)
666 h.Cursor.ResetSelection()
669 }, func(resp string, canceled bool) {
672 match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true)
677 h.Cursor.SetSelectionStart(match[0])
678 h.Cursor.SetSelectionEnd(match[1])
679 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
680 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
681 h.Cursor.GotoLoc(h.Cursor.CurSelection[1])
684 h.Cursor.ResetSelection()
685 InfoBar.Message("No matches found")
688 h.Cursor.ResetSelection()
696 // FindNext searches forwards for the last used search term
697 func (h *BufPane) FindNext() bool {
698 // If the cursor is at the start of a selection and we search we want
699 // to search from the end of the selection in the case that
700 // the selection is a search result in which case we wouldn't move at
701 // at all which would be bad
702 searchLoc := h.Cursor.Loc
703 if h.Cursor.HasSelection() {
704 searchLoc = h.Cursor.CurSelection[1]
706 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true)
711 h.Cursor.SetSelectionStart(match[0])
712 h.Cursor.SetSelectionEnd(match[1])
713 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
714 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
715 h.Cursor.Loc = h.Cursor.CurSelection[1]
717 h.Cursor.ResetSelection()
722 // FindPrevious searches backwards for the last used search term
723 func (h *BufPane) FindPrevious() bool {
724 // If the cursor is at the end of a selection and we search we want
725 // to search from the beginning of the selection in the case that
726 // the selection is a search result in which case we wouldn't move at
727 // at all which would be bad
728 searchLoc := h.Cursor.Loc
729 if h.Cursor.HasSelection() {
730 searchLoc = h.Cursor.CurSelection[0]
732 match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true)
737 h.Cursor.SetSelectionStart(match[0])
738 h.Cursor.SetSelectionEnd(match[1])
739 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
740 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
741 h.Cursor.Loc = h.Cursor.CurSelection[1]
743 h.Cursor.ResetSelection()
748 // Undo undoes the last action
749 func (h *BufPane) Undo() bool {
751 InfoBar.Message("Undid action")
755 // Redo redoes the last action
756 func (h *BufPane) Redo() bool {
758 InfoBar.Message("Redid action")
762 // Copy the selection to the system clipboard
763 func (h *BufPane) Copy() bool {
764 if h.Cursor.HasSelection() {
765 h.Cursor.CopySelection("clipboard")
767 InfoBar.Message("Copied selection")
772 // CutLine cuts the current line to the clipboard
773 func (h *BufPane) CutLine() bool {
774 h.Cursor.SelectLine()
775 if !h.Cursor.HasSelection() {
778 if h.freshClip == true {
779 if h.Cursor.HasSelection() {
780 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
781 // messenger.Error(err)
783 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
786 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
790 h.lastCutTime = time.Now()
791 h.Cursor.DeleteSelection()
792 h.Cursor.ResetSelection()
793 InfoBar.Message("Cut line")
797 // Cut the selection to the system clipboard
798 func (h *BufPane) Cut() bool {
799 if h.Cursor.HasSelection() {
800 h.Cursor.CopySelection("clipboard")
801 h.Cursor.DeleteSelection()
802 h.Cursor.ResetSelection()
804 InfoBar.Message("Cut selection")
812 // DuplicateLine duplicates the current line or selection
813 func (h *BufPane) DuplicateLine() bool {
814 if h.Cursor.HasSelection() {
815 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
818 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
822 InfoBar.Message("Duplicated line")
826 // DeleteLine deletes the current line
827 func (h *BufPane) DeleteLine() bool {
828 h.Cursor.SelectLine()
829 if !h.Cursor.HasSelection() {
832 h.Cursor.DeleteSelection()
833 h.Cursor.ResetSelection()
834 InfoBar.Message("Deleted line")
838 // MoveLinesUp moves up the current line or selected lines if any
839 func (h *BufPane) MoveLinesUp() bool {
840 if h.Cursor.HasSelection() {
841 if h.Cursor.CurSelection[0].Y == 0 {
842 InfoBar.Message("Can not move further up")
845 start := h.Cursor.CurSelection[0].Y
846 end := h.Cursor.CurSelection[1].Y
848 end, start = start, end
855 h.Cursor.CurSelection[1].Y -= 1
857 if h.Cursor.Loc.Y == 0 {
858 InfoBar.Message("Can not move further up")
870 // MoveLinesDown moves down the current line or selected lines if any
871 func (h *BufPane) MoveLinesDown() bool {
872 if h.Cursor.HasSelection() {
873 if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
874 InfoBar.Message("Can not move further down")
877 start := h.Cursor.CurSelection[0].Y
878 end := h.Cursor.CurSelection[1].Y
880 end, start = start, end
888 if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
889 InfoBar.Message("Can not move further down")
901 // Paste whatever is in the system clipboard into the buffer
902 // Delete and paste if the user has a selection
903 func (h *BufPane) Paste() bool {
904 clip, _ := clipboard.ReadAll("clipboard")
909 // PastePrimary pastes from the primary clipboard (only use on linux)
910 func (h *BufPane) PastePrimary() bool {
911 clip, _ := clipboard.ReadAll("primary")
916 func (h *BufPane) paste(clip string) {
917 if h.Buf.Settings["smartpaste"].(bool) {
918 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
919 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
920 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
924 if h.Cursor.HasSelection() {
925 h.Cursor.DeleteSelection()
926 h.Cursor.ResetSelection()
929 h.Buf.Insert(h.Cursor.Loc, clip)
930 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
932 InfoBar.Message("Pasted clipboard")
935 // JumpToMatchingBrace moves the cursor to the matching brace if it is
936 // currently on a brace
937 func (h *BufPane) JumpToMatchingBrace() bool {
938 for _, bp := range buffer.BracePairs {
939 r := h.Cursor.RuneUnder(h.Cursor.X)
940 rl := h.Cursor.RuneUnder(h.Cursor.X - 1)
941 if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] {
942 matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc)
944 h.Cursor.GotoLoc(matchingBrace)
946 h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
954 // SelectAll selects the entire buffer
955 func (h *BufPane) SelectAll() bool {
956 h.Cursor.SetSelectionStart(h.Buf.Start())
957 h.Cursor.SetSelectionEnd(h.Buf.End())
958 // Put the cursor at the beginning
964 // OpenFile opens a new file in the buffer
965 func (h *BufPane) OpenFile() bool {
966 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
968 h.HandleCommand(resp)
974 // Start moves the viewport to the start of the buffer
975 func (h *BufPane) Start() bool {
982 // End moves the viewport to the end of the buffer
983 func (h *BufPane) End() bool {
984 // TODO: softwrap problems?
986 if v.Height > h.Buf.LinesNum() {
990 v.StartLine = h.Buf.LinesNum() - v.Height
996 // PageUp scrolls the view up a page
997 func (h *BufPane) PageUp() bool {
999 if v.StartLine > v.Height {
1000 h.ScrollUp(v.Height)
1008 // PageDown scrolls the view down a page
1009 func (h *BufPane) PageDown() bool {
1011 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
1012 h.ScrollDown(v.Height)
1013 } else if h.Buf.LinesNum() >= v.Height {
1014 v.StartLine = h.Buf.LinesNum() - v.Height
1019 // SelectPageUp selects up one page
1020 func (h *BufPane) SelectPageUp() bool {
1021 if !h.Cursor.HasSelection() {
1022 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1024 h.Cursor.UpN(h.GetView().Height)
1025 h.Cursor.SelectTo(h.Cursor.Loc)
1029 // SelectPageDown selects down one page
1030 func (h *BufPane) SelectPageDown() bool {
1031 if !h.Cursor.HasSelection() {
1032 h.Cursor.OrigSelection[0] = h.Cursor.Loc
1034 h.Cursor.DownN(h.GetView().Height)
1035 h.Cursor.SelectTo(h.Cursor.Loc)
1039 // CursorPageUp places the cursor a page up
1040 func (h *BufPane) CursorPageUp() bool {
1041 h.Cursor.Deselect(true)
1043 if h.Cursor.HasSelection() {
1044 h.Cursor.Loc = h.Cursor.CurSelection[0]
1045 h.Cursor.ResetSelection()
1046 h.Cursor.StoreVisualX()
1048 h.Cursor.UpN(h.GetView().Height)
1052 // CursorPageDown places the cursor a page up
1053 func (h *BufPane) CursorPageDown() bool {
1054 h.Cursor.Deselect(false)
1056 if h.Cursor.HasSelection() {
1057 h.Cursor.Loc = h.Cursor.CurSelection[1]
1058 h.Cursor.ResetSelection()
1059 h.Cursor.StoreVisualX()
1061 h.Cursor.DownN(h.GetView().Height)
1065 // HalfPageUp scrolls the view up half a page
1066 func (h *BufPane) HalfPageUp() bool {
1068 if v.StartLine > v.Height/2 {
1069 h.ScrollUp(v.Height / 2)
1077 // HalfPageDown scrolls the view down half a page
1078 func (h *BufPane) HalfPageDown() bool {
1080 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
1081 h.ScrollDown(v.Height / 2)
1083 if h.Buf.LinesNum() >= v.Height {
1084 v.StartLine = h.Buf.LinesNum() - v.Height
1091 // ToggleRuler turns line numbers off and on
1092 func (h *BufPane) ToggleRuler() bool {
1093 if !h.Buf.Settings["ruler"].(bool) {
1094 h.Buf.Settings["ruler"] = true
1095 InfoBar.Message("Enabled ruler")
1097 h.Buf.Settings["ruler"] = false
1098 InfoBar.Message("Disabled ruler")
1103 // ClearStatus clears the messenger bar
1104 func (h *BufPane) ClearStatus() bool {
1109 // ToggleHelp toggles the help screen
1110 func (h *BufPane) ToggleHelp() bool {
1111 if h.Buf.Type == buffer.BTHelp {
1119 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
1120 func (h *BufPane) ToggleKeyMenu() bool {
1121 config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
1126 // ShellMode opens a terminal to run a shell command
1127 func (h *BufPane) ShellMode() bool {
1128 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
1130 // The true here is for openTerm to make the command interactive
1131 shell.RunInteractiveShell(resp, true, false)
1138 // CommandMode lets the user enter a command
1139 func (h *BufPane) CommandMode() bool {
1140 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
1142 h.HandleCommand(resp)
1148 // ToggleOverwriteMode lets the user toggle the text overwrite mode
1149 func (h *BufPane) ToggleOverwriteMode() bool {
1150 h.isOverwriteMode = !h.isOverwriteMode
1154 // Escape leaves current mode
1155 func (h *BufPane) Escape() bool {
1159 // Quit this will close the current tab or view that is open
1160 func (h *BufPane) Quit() bool {
1163 if len(MainTab().Panes) > 1 {
1165 } else if len(Tabs.List) > 1 {
1166 Tabs.RemoveTab(h.splitID)
1168 screen.Screen.Fini()
1173 if h.Buf.Modified() {
1174 if config.GlobalSettings["autosave"].(float64) > 0 {
1175 // autosave on means we automatically save when quitting
1179 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1180 if !canceled && !yes {
1182 } else if !canceled && yes {
1194 // QuitAll quits the whole editor; all splits and tabs
1195 func (h *BufPane) QuitAll() bool {
1196 anyModified := false
1197 for _, b := range buffer.OpenBuffers {
1205 for _, b := range buffer.OpenBuffers {
1208 screen.Screen.Fini()
1214 InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
1215 if !canceled && yes {
1226 // AddTab adds a new tab with an empty buffer
1227 func (h *BufPane) AddTab() bool {
1228 width, height := screen.Screen.Size()
1229 iOffset := config.GetInfoBarOffset()
1230 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1231 tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
1233 Tabs.SetActive(len(Tabs.List) - 1)
1238 // PreviousTab switches to the previous tab in the tab list
1239 func (h *BufPane) PreviousTab() bool {
1241 Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1246 // NextTab switches to the next tab in the tab list
1247 func (h *BufPane) NextTab() bool {
1249 Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1253 // VSplitAction opens an empty vertical split
1254 func (h *BufPane) VSplitAction() bool {
1255 h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1260 // HSplitAction opens an empty horizontal split
1261 func (h *BufPane) HSplitAction() bool {
1262 h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
1267 // Unsplit closes all splits in the current tab except the active one
1268 func (h *BufPane) Unsplit() bool {
1269 n := MainTab().GetNode(h.splitID)
1272 MainTab().RemovePane(MainTab().GetPane(h.splitID))
1274 MainTab().SetActive(len(MainTab().Panes) - 1)
1278 // NextSplit changes the view to the next split
1279 func (h *BufPane) NextSplit() bool {
1280 a := MainTab().active
1281 if a < len(MainTab().Panes)-1 {
1287 MainTab().SetActive(a)
1292 // PreviousSplit changes the view to the previous split
1293 func (h *BufPane) PreviousSplit() bool {
1294 a := MainTab().active
1298 a = len(MainTab().Panes) - 1
1300 MainTab().SetActive(a)
1305 var curmacro []interface{}
1306 var recording_macro bool
1308 // ToggleMacro toggles recording of a macro
1309 func (h *BufPane) ToggleMacro() bool {
1310 recording_macro = !recording_macro
1311 if recording_macro {
1312 curmacro = []interface{}{}
1313 InfoBar.Message("Recording")
1315 InfoBar.Message("Stopped recording")
1320 // PlayMacro plays back the most recently recorded macro
1321 func (h *BufPane) PlayMacro() bool {
1322 if recording_macro {
1325 for _, action := range curmacro {
1326 switch t := action.(type) {
1336 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1337 func (h *BufPane) SpawnMultiCursor() bool {
1338 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1339 if !spawner.HasSelection() {
1340 spawner.SelectWord()
1345 sel := spawner.GetSelection()
1346 searchStart := spawner.CurSelection[1]
1348 search := string(sel)
1349 search = regexp.QuoteMeta(search)
1351 search = "\\b" + search + "\\b"
1353 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1358 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1359 c.SetSelectionStart(match[0])
1360 c.SetSelectionEnd(match[1])
1361 c.OrigSelection[0] = c.CurSelection[0]
1362 c.OrigSelection[1] = c.CurSelection[1]
1363 c.Loc = c.CurSelection[1]
1366 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1367 h.Buf.MergeCursors()
1369 InfoBar.Message("No matches found")
1375 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1376 func (h *BufPane) SpawnMultiCursorSelect() bool {
1377 // Avoid cases where multiple cursors already exist, that would create problems
1378 if h.Buf.NumCursors() > 1 {
1385 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1387 startLine, endLine = b, a
1389 startLine, endLine = a, b
1392 if h.Cursor.HasSelection() {
1393 h.Cursor.ResetSelection()
1394 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1396 for i := startLine; i <= endLine; i++ {
1397 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1401 h.Buf.MergeCursors()
1405 InfoBar.Message("Added cursors from selection")
1409 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1410 func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
1412 mx, my := e.Position()
1413 mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
1414 c := buffer.NewCursor(b, mouseLoc)
1421 // SkipMultiCursor moves the current multiple cursor to the next available position
1422 func (h *BufPane) SkipMultiCursor() bool {
1423 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1424 sel := lastC.GetSelection()
1425 searchStart := lastC.CurSelection[1]
1427 search := string(sel)
1428 search = regexp.QuoteMeta(search)
1430 search = "\\b" + search + "\\b"
1433 match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
1438 lastC.SetSelectionStart(match[0])
1439 lastC.SetSelectionEnd(match[1])
1440 lastC.OrigSelection[0] = lastC.CurSelection[0]
1441 lastC.OrigSelection[1] = lastC.CurSelection[1]
1442 lastC.Loc = lastC.CurSelection[1]
1444 h.Buf.MergeCursors()
1445 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1447 InfoBar.Message("No matches found")
1452 // RemoveMultiCursor removes the latest multiple cursor
1453 func (h *BufPane) RemoveMultiCursor() bool {
1454 if h.Buf.NumCursors() > 1 {
1455 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1456 h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
1457 h.Buf.UpdateCursors()
1464 // RemoveAllMultiCursors removes all cursors except the base cursor
1465 func (h *BufPane) RemoveAllMultiCursors() bool {
1466 h.Buf.ClearCursors()
1471 func (h *BufPane) None() bool {