9 "github.com/zyedidia/clipboard"
10 "github.com/zyedidia/micro/cmd/micro/buffer"
11 "github.com/zyedidia/micro/cmd/micro/config"
12 "github.com/zyedidia/micro/cmd/micro/screen"
13 "github.com/zyedidia/micro/cmd/micro/shell"
14 "github.com/zyedidia/micro/cmd/micro/util"
15 "github.com/zyedidia/tcell"
18 // ScrollUp is not an action
19 func (h *BufHandler) ScrollUp(n int) {
27 // ScrollDown is not an action
28 func (h *BufHandler) ScrollDown(n int) {
30 if v.StartLine <= h.Buf.LinesNum()-1-n {
36 // MousePress is the event that should happen when a normal click happens
37 // This is almost always bound to left click
38 func (h *BufHandler) MousePress(e *tcell.EventMouse) bool {
40 mx, my := e.Position()
41 mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my})
42 h.Cursor.Loc = mouseLoc
44 if b.NumCursors() > 1 {
48 if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) {
51 h.lastClickTime = time.Now()
57 h.Cursor.CopySelection("primary")
60 h.lastClickTime = time.Now()
66 h.Cursor.CopySelection("primary")
71 h.lastClickTime = time.Now()
73 h.Cursor.OrigSelection[0] = h.Cursor.Loc
74 h.Cursor.CurSelection[0] = h.Cursor.Loc
75 h.Cursor.CurSelection[1] = h.Cursor.Loc
77 h.mouseReleased = false
78 } else if !h.mouseReleased {
80 h.Cursor.AddLineToSelection()
81 } else if h.doubleClick {
82 h.Cursor.AddWordToSelection()
84 h.Cursor.SetSelectionEnd(h.Cursor.Loc)
85 h.Cursor.CopySelection("primary")
93 // ScrollUpAction scrolls the view up
94 func (h *BufHandler) ScrollUpAction() bool {
95 h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
99 // ScrollDownAction scrolls the view up
100 func (h *BufHandler) ScrollDownAction() bool {
101 h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
105 // Center centers the view on the cursor
106 func (h *BufHandler) Center() bool {
108 v.StartLine = h.Cursor.Y - v.Height/2
109 if v.StartLine+v.Height > h.Buf.LinesNum() {
110 v.StartLine = h.Buf.LinesNum() - v.Height
119 // CursorUp moves the cursor up
120 func (h *BufHandler) CursorUp() bool {
121 h.Cursor.Deselect(true)
126 // CursorDown moves the cursor down
127 func (h *BufHandler) CursorDown() bool {
128 h.Cursor.Deselect(true)
133 // CursorLeft moves the cursor left
134 func (h *BufHandler) CursorLeft() bool {
135 h.Cursor.Deselect(true)
140 // CursorRight moves the cursor right
141 func (h *BufHandler) CursorRight() bool {
142 h.Cursor.Deselect(false)
147 // WordRight moves the cursor one word to the right
148 func (h *BufHandler) WordRight() bool {
149 h.Cursor.Deselect(false)
154 // WordLeft moves the cursor one word to the left
155 func (h *BufHandler) WordLeft() bool {
156 h.Cursor.Deselect(true)
161 // SelectUp selects up one line
162 func (h *BufHandler) SelectUp() bool {
163 if !h.Cursor.HasSelection() {
164 h.Cursor.OrigSelection[0] = h.Cursor.Loc
167 h.Cursor.SelectTo(h.Cursor.Loc)
171 // SelectDown selects down one line
172 func (h *BufHandler) SelectDown() bool {
173 if !h.Cursor.HasSelection() {
174 h.Cursor.OrigSelection[0] = h.Cursor.Loc
177 h.Cursor.SelectTo(h.Cursor.Loc)
181 // SelectLeft selects the character to the left of the cursor
182 func (h *BufHandler) SelectLeft() bool {
185 if loc.GreaterThan(count) {
188 if !h.Cursor.HasSelection() {
189 h.Cursor.OrigSelection[0] = loc
192 h.Cursor.SelectTo(h.Cursor.Loc)
196 // SelectRight selects the character to the right of the cursor
197 func (h *BufHandler) SelectRight() bool {
200 if loc.GreaterThan(count) {
203 if !h.Cursor.HasSelection() {
204 h.Cursor.OrigSelection[0] = loc
207 h.Cursor.SelectTo(h.Cursor.Loc)
211 // SelectWordRight selects the word to the right of the cursor
212 func (h *BufHandler) SelectWordRight() bool {
213 if !h.Cursor.HasSelection() {
214 h.Cursor.OrigSelection[0] = h.Cursor.Loc
217 h.Cursor.SelectTo(h.Cursor.Loc)
221 // SelectWordLeft selects the word to the left of the cursor
222 func (h *BufHandler) SelectWordLeft() bool {
223 if !h.Cursor.HasSelection() {
224 h.Cursor.OrigSelection[0] = h.Cursor.Loc
227 h.Cursor.SelectTo(h.Cursor.Loc)
231 // StartOfLine moves the cursor to the start of the line
232 func (h *BufHandler) StartOfLine() bool {
233 h.Cursor.Deselect(true)
237 h.Cursor.StartOfText()
242 // EndOfLine moves the cursor to the end of the line
243 func (h *BufHandler) EndOfLine() bool {
244 h.Cursor.Deselect(true)
249 // SelectLine selects the entire current line
250 func (h *BufHandler) SelectLine() bool {
251 h.Cursor.SelectLine()
255 // SelectToStartOfLine selects to the start of the current line
256 func (h *BufHandler) SelectToStartOfLine() bool {
257 if !h.Cursor.HasSelection() {
258 h.Cursor.OrigSelection[0] = h.Cursor.Loc
261 h.Cursor.SelectTo(h.Cursor.Loc)
265 // SelectToEndOfLine selects to the end of the current line
266 func (h *BufHandler) SelectToEndOfLine() bool {
267 if !h.Cursor.HasSelection() {
268 h.Cursor.OrigSelection[0] = h.Cursor.Loc
271 h.Cursor.SelectTo(h.Cursor.Loc)
275 // ParagraphPrevious moves the cursor to the previous empty line, or beginning of the buffer if there's none
276 func (h *BufHandler) ParagraphPrevious() bool {
278 for line = h.Cursor.Y; line > 0; line-- {
279 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
285 // If no empty line found. move cursor to end of buffer
287 h.Cursor.Loc = h.Buf.Start()
292 // ParagraphNext moves the cursor to the next empty line, or end of the buffer if there's none
293 func (h *BufHandler) ParagraphNext() bool {
295 for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
296 if len(h.Buf.LineBytes(line)) == 0 && line != h.Cursor.Y {
302 // If no empty line found. move cursor to end of buffer
303 if line == h.Buf.LinesNum() {
304 h.Cursor.Loc = h.Buf.End()
309 // Retab changes all tabs to spaces or all spaces to tabs depending
310 // on the user's settings
311 func (h *BufHandler) Retab() bool {
313 // toSpaces := b.Settings["tabstospaces"].(bool)
314 // tabsize := util.IntOpt(b.Settings["tabsize"])
317 // for i := 0; i < b.LinesNum(); i++ {
318 // l := b.LineBytes(i)
320 // ws := util.GetLeadingWhitespace(l)
323 // ws = bytes.Replace(ws, []byte("\t"), []byte(util.Spaces(tabsize)), -1)
325 // ws = bytes.Replace(ws, []byte(util.Spaces(tabsize)), []byte("\t"), -1)
329 // l = bytes.TrimLeft(l, " \t")
330 // b.lines[i].data = append(ws, l...)
334 // b.IsModified = dirty
338 // CursorStart moves the cursor to the start of the buffer
339 func (h *BufHandler) CursorStart() bool {
340 h.Cursor.Deselect(true)
346 // CursorEnd moves the cursor to the end of the buffer
347 func (h *BufHandler) CursorEnd() bool {
348 h.Cursor.Deselect(true)
349 h.Cursor.Loc = h.Buf.End()
350 h.Cursor.StoreVisualX()
354 // SelectToStart selects the text from the cursor to the start of the buffer
355 func (h *BufHandler) SelectToStart() bool {
356 if !h.Cursor.HasSelection() {
357 h.Cursor.OrigSelection[0] = h.Cursor.Loc
360 h.Cursor.SelectTo(h.Buf.Start())
364 // SelectToEnd selects the text from the cursor to the end of the buffer
365 func (h *BufHandler) SelectToEnd() bool {
366 if !h.Cursor.HasSelection() {
367 h.Cursor.OrigSelection[0] = h.Cursor.Loc
370 h.Cursor.SelectTo(h.Buf.End())
374 // InsertNewline inserts a newline plus possible some whitespace if autoindent is on
375 func (h *BufHandler) InsertNewline() bool {
376 if h.Buf.Type == buffer.BTInfo {
377 InfoBar.DonePrompt(false)
382 if h.Cursor.HasSelection() {
383 h.Cursor.DeleteSelection()
384 h.Cursor.ResetSelection()
387 ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
389 h.Buf.Insert(h.Cursor.Loc, "\n")
392 if h.Buf.Settings["autoindent"].(bool) {
396 h.Buf.Insert(h.Cursor.Loc, string(ws))
397 // for i := 0; i < len(ws); i++ {
401 // Remove the whitespaces if keepautoindent setting is off
402 if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) {
403 line := h.Buf.LineBytes(h.Cursor.Y - 1)
404 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1})
407 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
411 // Backspace deletes the previous character
412 func (h *BufHandler) Backspace() bool {
413 if h.Cursor.HasSelection() {
414 h.Cursor.DeleteSelection()
415 h.Cursor.ResetSelection()
416 } else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
417 // We have to do something a bit hacky here because we want to
418 // delete the line by first moving left and then deleting backwards
419 // but the undo redo would place the cursor in the wrong place
420 // So instead we move left, save the position, move back, delete
421 // and restore the position
423 // If the user is using spaces instead of tabs and they are deleting
424 // whitespace at the start of the line, we should delete as if it's a
425 // tab (tabSize number of spaces)
426 lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
427 tabSize := int(h.Buf.Settings["tabsize"].(float64))
428 if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 {
430 h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
433 h.Buf.Remove(loc.Move(-1, h.Buf), loc)
436 h.Cursor.LastVisualX = h.Cursor.GetVisualX()
440 // DeleteWordRight deletes the word to the right of the cursor
441 func (h *BufHandler) DeleteWordRight() bool {
443 if h.Cursor.HasSelection() {
444 h.Cursor.DeleteSelection()
445 h.Cursor.ResetSelection()
450 // DeleteWordLeft deletes the word to the left of the cursor
451 func (h *BufHandler) DeleteWordLeft() bool {
453 if h.Cursor.HasSelection() {
454 h.Cursor.DeleteSelection()
455 h.Cursor.ResetSelection()
460 // Delete deletes the next character
461 func (h *BufHandler) Delete() bool {
462 if h.Cursor.HasSelection() {
463 h.Cursor.DeleteSelection()
464 h.Cursor.ResetSelection()
467 if loc.LessThan(h.Buf.End()) {
468 h.Buf.Remove(loc, loc.Move(1, h.Buf))
474 // IndentSelection indents the current selection
475 func (h *BufHandler) IndentSelection() bool {
476 if h.Cursor.HasSelection() {
477 start := h.Cursor.CurSelection[0]
478 end := h.Cursor.CurSelection[1]
480 start, end = end, start
481 h.Cursor.SetSelectionStart(start)
482 h.Cursor.SetSelectionEnd(end)
486 endY := end.Move(-1, h.Buf).Y
487 endX := end.Move(-1, h.Buf).X
488 tabsize := int(h.Buf.Settings["tabsize"].(float64))
489 indentsize := len(h.Buf.IndentString(tabsize))
490 for y := startY; y <= endY; y++ {
491 h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize))
492 if y == startY && start.X > 0 {
493 h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf))
496 h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY})
506 // OutdentLine moves the current line back one indentation
507 func (h *BufHandler) OutdentLine() bool {
508 if h.Cursor.HasSelection() {
512 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
513 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
516 h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
522 // OutdentSelection takes the current selection and moves it back one indent level
523 func (h *BufHandler) OutdentSelection() bool {
524 if h.Cursor.HasSelection() {
525 start := h.Cursor.CurSelection[0]
526 end := h.Cursor.CurSelection[1]
528 start, end = end, start
529 h.Cursor.SetSelectionStart(start)
530 h.Cursor.SetSelectionEnd(end)
534 endY := end.Move(-1, h.Buf).Y
535 for y := startY; y <= endY; y++ {
536 for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
537 if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
540 h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
550 // InsertTab inserts a tab or spaces
551 func (h *BufHandler) InsertTab() bool {
552 indent := h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))
553 tabBytes := len(indent)
554 bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes)
555 h.Buf.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
559 // SaveAll saves all open buffers
560 func (h *BufHandler) SaveAll() bool {
564 // Save the buffer to disk
565 func (h *BufHandler) Save() bool {
570 // SaveAs saves the buffer to disk with the given name
571 func (h *BufHandler) SaveAs() bool {
575 // Find opens a prompt and searches forward for the input
576 func (h *BufHandler) Find() bool {
577 InfoBar.Prompt("Find: ", "", "Find", func(resp string) {
579 match, found, _ := h.Buf.FindNext(resp, h.Cursor.Loc, true)
581 h.Cursor.SetSelectionStart(match[0])
582 h.Cursor.SetSelectionEnd(match[1])
583 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
584 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
586 h.Cursor.ResetSelection()
588 }, func(resp string, canceled bool) {
591 match, found, err := h.Buf.FindNext(resp, h.Cursor.Loc, true)
596 h.Cursor.SetSelectionStart(match[0])
597 h.Cursor.SetSelectionEnd(match[1])
598 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
599 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
600 h.Cursor.Loc = h.Cursor.CurSelection[1]
603 h.Cursor.ResetSelection()
604 InfoBar.Message("No matches found")
607 h.Cursor.ResetSelection()
614 // FindNext searches forwards for the last used search term
615 func (h *BufHandler) FindNext() bool {
616 // If the cursor is at the start of a selection and we search we want
617 // to search from the end of the selection in the case that
618 // the selection is a search result in which case we wouldn't move at
619 // at all which would be bad
620 searchLoc := h.Cursor.Loc
621 if h.Cursor.HasSelection() {
622 searchLoc = h.Cursor.CurSelection[1]
624 match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, true)
629 h.Cursor.SetSelectionStart(match[0])
630 h.Cursor.SetSelectionEnd(match[1])
631 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
632 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
633 h.Cursor.Loc = h.Cursor.CurSelection[1]
635 h.Cursor.ResetSelection()
640 // FindPrevious searches backwards for the last used search term
641 func (h *BufHandler) FindPrevious() bool {
642 // If the cursor is at the end of a selection and we search we want
643 // to search from the beginning of the selection in the case that
644 // the selection is a search result in which case we wouldn't move at
645 // at all which would be bad
646 searchLoc := h.Cursor.Loc
647 if h.Cursor.HasSelection() {
648 searchLoc = h.Cursor.CurSelection[0]
650 match, found, err := h.Buf.FindNext(h.lastSearch, searchLoc, false)
655 h.Cursor.SetSelectionStart(match[0])
656 h.Cursor.SetSelectionEnd(match[1])
657 h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0]
658 h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1]
659 h.Cursor.Loc = h.Cursor.CurSelection[1]
661 h.Cursor.ResetSelection()
666 // Undo undoes the last action
667 func (h *BufHandler) Undo() bool {
669 InfoBar.Message("Undid action")
673 // Redo redoes the last action
674 func (h *BufHandler) Redo() bool {
675 // TODO: clear cursors and message
677 InfoBar.Message("Redid action")
681 // Copy the selection to the system clipboard
682 func (h *BufHandler) Copy() bool {
683 if h.Cursor.HasSelection() {
684 h.Cursor.CopySelection("clipboard")
686 InfoBar.Message("Copied selection")
691 // CutLine cuts the current line to the clipboard
692 func (h *BufHandler) CutLine() bool {
693 h.Cursor.SelectLine()
694 if !h.Cursor.HasSelection() {
697 if h.freshClip == true {
698 if h.Cursor.HasSelection() {
699 if clip, err := clipboard.ReadAll("clipboard"); err != nil {
700 // messenger.Error(err)
702 clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard")
705 } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false {
709 h.lastCutTime = time.Now()
710 h.Cursor.DeleteSelection()
711 h.Cursor.ResetSelection()
712 InfoBar.Message("Cut line")
716 // Cut the selection to the system clipboard
717 func (h *BufHandler) Cut() bool {
718 if h.Cursor.HasSelection() {
719 h.Cursor.CopySelection("clipboard")
720 h.Cursor.DeleteSelection()
721 h.Cursor.ResetSelection()
723 InfoBar.Message("Cut selection")
731 // DuplicateLine duplicates the current line or selection
732 func (h *BufHandler) DuplicateLine() bool {
733 if h.Cursor.HasSelection() {
734 h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
737 h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
741 InfoBar.Message("Duplicated line")
745 // DeleteLine deletes the current line
746 func (h *BufHandler) DeleteLine() bool {
747 h.Cursor.SelectLine()
748 if !h.Cursor.HasSelection() {
751 h.Cursor.DeleteSelection()
752 h.Cursor.ResetSelection()
753 InfoBar.Message("Deleted line")
757 // MoveLinesUp moves up the current line or selected lines if any
758 func (h *BufHandler) MoveLinesUp() bool {
762 // MoveLinesDown moves down the current line or selected lines if any
763 func (h *BufHandler) MoveLinesDown() bool {
767 // Paste whatever is in the system clipboard into the buffer
768 // Delete and paste if the user has a selection
769 func (h *BufHandler) Paste() bool {
770 clip, _ := clipboard.ReadAll("clipboard")
775 // PastePrimary pastes from the primary clipboard (only use on linux)
776 func (h *BufHandler) PastePrimary() bool {
777 clip, _ := clipboard.ReadAll("primary")
782 func (h *BufHandler) paste(clip string) {
783 if h.Buf.Settings["smartpaste"].(bool) {
784 if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 {
785 leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
786 clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1)
790 if h.Cursor.HasSelection() {
791 h.Cursor.DeleteSelection()
792 h.Cursor.ResetSelection()
795 h.Buf.Insert(h.Cursor.Loc, clip)
796 // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
798 InfoBar.Message("Pasted clipboard")
801 // JumpToMatchingBrace moves the cursor to the matching brace if it is
802 // currently on a brace
803 func (h *BufHandler) JumpToMatchingBrace() bool {
807 // SelectAll selects the entire buffer
808 func (h *BufHandler) SelectAll() bool {
809 h.Cursor.SetSelectionStart(h.Buf.Start())
810 h.Cursor.SetSelectionEnd(h.Buf.End())
811 // Put the cursor at the beginning
817 // OpenFile opens a new file in the buffer
818 func (h *BufHandler) OpenFile() bool {
819 InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
821 h.HandleCommand(resp)
827 // Start moves the viewport to the start of the buffer
828 func (h *BufHandler) Start() bool {
835 // End moves the viewport to the end of the buffer
836 func (h *BufHandler) End() bool {
837 // TODO: softwrap problems?
839 if v.Height > h.Buf.LinesNum() {
843 h.StartLine = h.Buf.LinesNum() - v.Height
848 // PageUp scrolls the view up a page
849 func (h *BufHandler) PageUp() bool {
851 if v.StartLine > v.Height {
860 // PageDown scrolls the view down a page
861 func (h *BufHandler) PageDown() bool {
863 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height {
864 h.ScrollDown(v.Height)
865 } else if h.Buf.LinesNum() >= v.Height {
866 v.StartLine = h.Buf.LinesNum() - v.Height
871 // SelectPageUp selects up one page
872 func (h *BufHandler) SelectPageUp() bool {
873 if !h.Cursor.HasSelection() {
874 h.Cursor.OrigSelection[0] = h.Cursor.Loc
876 h.Cursor.UpN(h.GetView().Height)
877 h.Cursor.SelectTo(h.Cursor.Loc)
881 // SelectPageDown selects down one page
882 func (h *BufHandler) SelectPageDown() bool {
883 if !h.Cursor.HasSelection() {
884 h.Cursor.OrigSelection[0] = h.Cursor.Loc
886 h.Cursor.DownN(h.GetView().Height)
887 h.Cursor.SelectTo(h.Cursor.Loc)
891 // CursorPageUp places the cursor a page up
892 func (h *BufHandler) CursorPageUp() bool {
893 h.Cursor.Deselect(true)
895 if h.Cursor.HasSelection() {
896 h.Cursor.Loc = h.Cursor.CurSelection[0]
897 h.Cursor.ResetSelection()
898 h.Cursor.StoreVisualX()
900 h.Cursor.UpN(h.GetView().Height)
904 // CursorPageDown places the cursor a page up
905 func (h *BufHandler) CursorPageDown() bool {
906 h.Cursor.Deselect(false)
908 if h.Cursor.HasSelection() {
909 h.Cursor.Loc = h.Cursor.CurSelection[1]
910 h.Cursor.ResetSelection()
911 h.Cursor.StoreVisualX()
913 h.Cursor.DownN(h.GetView().Height)
917 // HalfPageUp scrolls the view up half a page
918 func (h *BufHandler) HalfPageUp() bool {
920 if v.StartLine > v.Height/2 {
921 h.ScrollUp(v.Height / 2)
929 // HalfPageDown scrolls the view down half a page
930 func (h *BufHandler) HalfPageDown() bool {
932 if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 {
933 h.ScrollDown(v.Height / 2)
935 if h.Buf.LinesNum() >= v.Height {
936 v.StartLine = h.Buf.LinesNum() - v.Height
943 // ToggleRuler turns line numbers off and on
944 func (h *BufHandler) ToggleRuler() bool {
945 if !h.Buf.Settings["ruler"].(bool) {
946 h.Buf.Settings["ruler"] = true
947 InfoBar.Message("Enabled ruler")
949 h.Buf.Settings["ruler"] = false
950 InfoBar.Message("Disabled ruler")
955 // JumpLine jumps to a line and moves the view accordingly.
956 func (h *BufHandler) JumpLine() bool {
960 // ClearStatus clears the messenger bar
961 func (h *BufHandler) ClearStatus() bool {
966 // ToggleHelp toggles the help screen
967 func (h *BufHandler) ToggleHelp() bool {
971 // ToggleKeyMenu toggles the keymenu option and resizes all tabs
972 func (h *BufHandler) ToggleKeyMenu() bool {
976 // ShellMode opens a terminal to run a shell command
977 func (h *BufHandler) ShellMode() bool {
978 InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
980 // The true here is for openTerm to make the command interactive
981 shell.RunInteractiveShell(resp, true, false)
988 // CommandMode lets the user enter a command
989 func (h *BufHandler) CommandMode() bool {
990 InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
992 h.HandleCommand(resp)
998 // ToggleOverwriteMode lets the user toggle the text overwrite mode
999 func (h *BufHandler) ToggleOverwriteMode() bool {
1000 h.isOverwriteMode = !h.isOverwriteMode
1004 // Escape leaves current mode
1005 func (h *BufHandler) Escape() bool {
1009 // Quit this will close the current tab or view that is open
1010 func (h *BufHandler) Quit() bool {
1012 if len(MainTab().Panes) > 1 {
1014 } else if len(Tabs.List) > 1 {
1015 Tabs.RemoveTab(h.splitID)
1017 screen.Screen.Fini()
1022 if h.Buf.Modified() {
1023 InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
1024 if !canceled && !yes {
1026 } else if !canceled && yes {
1037 // QuitAll quits the whole editor; all splits and tabs
1038 func (h *BufHandler) QuitAll() bool {
1042 // AddTab adds a new tab with an empty buffer
1043 func (h *BufHandler) AddTab() bool {
1044 width, height := screen.Screen.Size()
1045 b := buffer.NewBufferFromString("", "", buffer.BTDefault)
1046 tp := NewTabFromBuffer(0, 0, width, height-1, b)
1048 Tabs.SetActive(len(Tabs.List) - 1)
1053 // PreviousTab switches to the previous tab in the tab list
1054 func (h *BufHandler) PreviousTab() bool {
1056 Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1))
1061 // NextTab switches to the next tab in the tab list
1062 func (h *BufHandler) NextTab() bool {
1064 Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1))
1068 // VSplitBinding opens an empty vertical split
1069 func (h *BufHandler) VSplitBinding() bool {
1070 h.vsplit(buffer.NewBufferFromString("", "", buffer.BTDefault))
1075 // HSplitBinding opens an empty horizontal split
1076 func (h *BufHandler) HSplitBinding() bool {
1077 h.hsplit(buffer.NewBufferFromString("", "", buffer.BTDefault))
1082 // Unsplit closes all splits in the current tab except the active one
1083 func (h *BufHandler) Unsplit() bool {
1084 n := MainTab().GetNode(h.splitID)
1087 MainTab().RemovePane(MainTab().GetPane(h.splitID))
1089 MainTab().SetActive(len(MainTab().Panes) - 1)
1093 // NextSplit changes the view to the next split
1094 func (h *BufHandler) NextSplit() bool {
1095 a := MainTab().active
1096 a = util.Clamp(a+1, 0, len(MainTab().Panes))
1097 MainTab().SetActive(a)
1102 // PreviousSplit changes the view to the previous split
1103 func (h *BufHandler) PreviousSplit() bool {
1104 a := MainTab().active
1105 a = util.Clamp(a-1, 0, len(MainTab().Panes))
1106 MainTab().SetActive(a)
1111 var curMacro []interface{}
1112 var recordingMacro bool
1114 // ToggleMacro toggles recording of a macro
1115 func (h *BufHandler) ToggleMacro() bool {
1119 // PlayMacro plays back the most recently recorded macro
1120 func (h *BufHandler) PlayMacro() bool {
1124 // SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
1125 func (h *BufHandler) SpawnMultiCursor() bool {
1126 spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1127 if !spawner.HasSelection() {
1128 spawner.SelectWord()
1133 sel := spawner.GetSelection()
1134 searchStart := spawner.CurSelection[1]
1136 search := string(sel)
1138 search = "\\b" + search + "\\b"
1140 match, found, err := h.Buf.FindNext(search, searchStart, true)
1145 c := buffer.NewCursor(h.Buf, buffer.Loc{})
1146 c.SetSelectionStart(match[0])
1147 c.SetSelectionEnd(match[1])
1148 c.OrigSelection[0] = c.CurSelection[0]
1149 c.OrigSelection[1] = c.CurSelection[1]
1150 c.Loc = c.CurSelection[1]
1153 h.Buf.MergeCursors()
1155 InfoBar.Message("No matches found")
1161 // SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
1162 func (h *BufHandler) SpawnMultiCursorSelect() bool {
1163 // Avoid cases where multiple cursors already exist, that would create problems
1164 if h.Buf.NumCursors() > 1 {
1171 a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
1173 startLine, endLine = b, a
1175 startLine, endLine = a, b
1178 if h.Cursor.HasSelection() {
1179 h.Cursor.ResetSelection()
1180 h.Cursor.GotoLoc(buffer.Loc{0, startLine})
1182 for i := startLine; i <= endLine; i++ {
1183 c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
1187 h.Buf.MergeCursors()
1191 InfoBar.Message("Added cursors from selection")
1195 // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position
1196 func (h *BufHandler) MouseMultiCursor(e *tcell.EventMouse) bool {
1198 mx, my := e.Position()
1199 mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my})
1200 c := buffer.NewCursor(b, mouseLoc)
1207 // SkipMultiCursor moves the current multiple cursor to the next available position
1208 func (h *BufHandler) SkipMultiCursor() bool {
1209 lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
1210 sel := lastC.GetSelection()
1211 searchStart := lastC.CurSelection[1]
1213 match, found, err := h.Buf.FindNext(string(sel), searchStart, true)
1218 lastC.SetSelectionStart(match[0])
1219 lastC.SetSelectionEnd(match[1])
1220 lastC.OrigSelection[0] = lastC.CurSelection[0]
1221 lastC.OrigSelection[1] = lastC.CurSelection[1]
1222 lastC.Loc = lastC.CurSelection[1]
1224 h.Buf.MergeCursors()
1227 InfoBar.Message("No matches found")
1232 // RemoveMultiCursor removes the latest multiple cursor
1233 func (h *BufHandler) RemoveMultiCursor() bool {
1234 if h.Buf.NumCursors() > 1 {
1235 h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
1236 h.Buf.UpdateCursors()
1243 // RemoveAllMultiCursors removes all cursors except the base cursor
1244 func (h *BufHandler) RemoveAllMultiCursors() bool {
1245 h.Buf.ClearCursors()