X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=internal%2Faction%2Factions.go;h=5d7b31e0238b9f0dd70ab9ca2a4515791a2eefea;hb=f143418267df07c0f9424e02cc942edfdf3f450f;hp=bacb04ad366270c569074cd64e1876feab582e8d;hpb=8d85cae4c0d65b610d226e9c75c8a5c28bd31039;p=micro.git diff --git a/internal/action/actions.go b/internal/action/actions.go index bacb04ad..5d7b31e0 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1,19 +1,18 @@ package action import ( - "os" "regexp" + "runtime" "strings" "time" - "unicode/utf8" - - "github.com/zyedidia/clipboard" - "github.com/zyedidia/micro/internal/buffer" - "github.com/zyedidia/micro/internal/config" - "github.com/zyedidia/micro/internal/screen" - "github.com/zyedidia/micro/internal/shell" - "github.com/zyedidia/micro/internal/util" - "github.com/zyedidia/micro/pkg/shellwords" + + shellquote "github.com/kballard/go-shellquote" + "github.com/zyedidia/micro/v2/internal/buffer" + "github.com/zyedidia/micro/v2/internal/clipboard" + "github.com/zyedidia/micro/v2/internal/config" + "github.com/zyedidia/micro/v2/internal/screen" + "github.com/zyedidia/micro/v2/internal/shell" + "github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/tcell" ) @@ -23,6 +22,8 @@ func (h *BufPane) ScrollUp(n int) { if v.StartLine >= n { v.StartLine -= n h.SetView(v) + } else { + v.StartLine = 0 } } @@ -40,12 +41,14 @@ func (h *BufPane) ScrollDown(n int) { func (h *BufPane) MousePress(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() - mouseLoc := h.GetMouseLoc(buffer.Loc{mx, my}) + mouseLoc := h.LocFromVisual(buffer.Loc{mx, my}) h.Cursor.Loc = mouseLoc if h.mouseReleased { if b.NumCursors() > 1 { b.ClearCursors() h.Relocate() + h.Cursor = h.Buf.GetActiveCursor() + h.Cursor.Loc = mouseLoc } if time.Since(h.lastClickTime)/time.Millisecond < config.DoubleClickThreshold && (mouseLoc.X == h.lastLoc.X && mouseLoc.Y == h.lastLoc.Y) { if h.doubleClick { @@ -56,7 +59,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { h.doubleClick = false h.Cursor.SelectLine() - h.Cursor.CopySelection("primary") + h.Cursor.CopySelection(clipboard.PrimaryReg) } else { // Double click h.lastClickTime = time.Now() @@ -65,7 +68,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { h.tripleClick = false h.Cursor.SelectWord() - h.Cursor.CopySelection("primary") + h.Cursor.CopySelection(clipboard.PrimaryReg) } } else { h.doubleClick = false @@ -84,24 +87,24 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { h.Cursor.AddWordToSelection() } else { h.Cursor.SetSelectionEnd(h.Cursor.Loc) - h.Cursor.CopySelection("primary") } } + h.Cursor.StoreVisualX() h.lastLoc = mouseLoc - return false + return true } // ScrollUpAction scrolls the view up func (h *BufPane) ScrollUpAction() bool { h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"])) - return false + return true } // ScrollDownAction scrolls the view up func (h *BufPane) ScrollDownAction() bool { h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"])) - return false + return true } // Center centers the view on the cursor @@ -115,6 +118,7 @@ func (h *BufPane) Center() bool { v.StartLine = 0 } h.SetView(v) + h.Relocate() return true } @@ -122,6 +126,7 @@ func (h *BufPane) Center() bool { func (h *BufPane) CursorUp() bool { h.Cursor.Deselect(true) h.Cursor.Up() + h.Relocate() return true } @@ -129,6 +134,7 @@ func (h *BufPane) CursorUp() bool { func (h *BufPane) CursorDown() bool { h.Cursor.Deselect(true) h.Cursor.Down() + h.Relocate() return true } @@ -153,6 +159,7 @@ func (h *BufPane) CursorLeft() bool { h.Cursor.Left() } } + h.Relocate() return true } @@ -167,7 +174,7 @@ func (h *BufPane) CursorRight() bool { if tabstospaces && tabmovement { tabsize := int(h.Buf.Settings["tabsize"].(float64)) line := h.Buf.LineBytes(h.Cursor.Y) - 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]) { + if h.Cursor.X+tabsize < util.CharacterCount(line) && util.IsSpaces(line[h.Cursor.X:h.Cursor.X+tabsize]) && util.IsBytesWhitespace(line[0:h.Cursor.X]) { for i := 0; i < tabsize; i++ { h.Cursor.Right() } @@ -179,6 +186,7 @@ func (h *BufPane) CursorRight() bool { } } + h.Relocate() return true } @@ -186,6 +194,7 @@ func (h *BufPane) CursorRight() bool { func (h *BufPane) WordRight() bool { h.Cursor.Deselect(false) h.Cursor.WordRight() + h.Relocate() return true } @@ -193,6 +202,7 @@ func (h *BufPane) WordRight() bool { func (h *BufPane) WordLeft() bool { h.Cursor.Deselect(true) h.Cursor.WordLeft() + h.Relocate() return true } @@ -203,6 +213,7 @@ func (h *BufPane) SelectUp() bool { } h.Cursor.Up() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -213,6 +224,7 @@ func (h *BufPane) SelectDown() bool { } h.Cursor.Down() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -228,6 +240,7 @@ func (h *BufPane) SelectLeft() bool { } h.Cursor.Left() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -243,6 +256,7 @@ func (h *BufPane) SelectRight() bool { } h.Cursor.Right() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -253,6 +267,7 @@ func (h *BufPane) SelectWordRight() bool { } h.Cursor.WordRight() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -263,17 +278,36 @@ func (h *BufPane) SelectWordLeft() bool { } h.Cursor.WordLeft() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } -// StartOfLine moves the cursor to the start of the line -func (h *BufPane) StartOfLine() bool { +// StartOfText moves the cursor to the start of the text of the line +func (h *BufPane) StartOfText() bool { h.Cursor.Deselect(true) - if h.Cursor.X != 0 { + h.Cursor.StartOfText() + h.Relocate() + return true +} + +// StartOfTextToggle toggles the cursor between the start of the text of the line +// and the start of the line +func (h *BufPane) StartOfTextToggle() bool { + h.Cursor.Deselect(true) + if h.Cursor.IsStartOfText() { h.Cursor.Start() } else { h.Cursor.StartOfText() } + h.Relocate() + return true +} + +// StartOfLine moves the cursor to the start of the line +func (h *BufPane) StartOfLine() bool { + h.Cursor.Deselect(true) + h.Cursor.Start() + h.Relocate() return true } @@ -281,12 +315,41 @@ func (h *BufPane) StartOfLine() bool { func (h *BufPane) EndOfLine() bool { h.Cursor.Deselect(true) h.Cursor.End() + h.Relocate() return true } // SelectLine selects the entire current line func (h *BufPane) SelectLine() bool { h.Cursor.SelectLine() + h.Relocate() + return true +} + +// SelectToStartOfText selects to the start of the text on the current line +func (h *BufPane) SelectToStartOfText() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + h.Cursor.StartOfText() + h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() + return true +} + +// SelectToStartOfTextToggle toggles the selection between the start of the text +// on the current line and the start of the line +func (h *BufPane) SelectToStartOfTextToggle() bool { + if !h.Cursor.HasSelection() { + h.Cursor.OrigSelection[0] = h.Cursor.Loc + } + if h.Cursor.IsStartOfText() { + h.Cursor.Start() + } else { + h.Cursor.StartOfText() + } + h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -297,6 +360,7 @@ func (h *BufPane) SelectToStartOfLine() bool { } h.Cursor.Start() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -307,6 +371,7 @@ func (h *BufPane) SelectToEndOfLine() bool { } h.Cursor.End() h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -324,6 +389,7 @@ func (h *BufPane) ParagraphPrevious() bool { if line == 0 { h.Cursor.Loc = h.Buf.Start() } + h.Relocate() return true } @@ -341,6 +407,7 @@ func (h *BufPane) ParagraphNext() bool { if line == h.Buf.LinesNum() { h.Cursor.Loc = h.Buf.End() } + h.Relocate() return true } @@ -348,6 +415,7 @@ func (h *BufPane) ParagraphNext() bool { // on the user's settings func (h *BufPane) Retab() bool { h.Buf.Retab() + h.Relocate() return true } @@ -356,6 +424,8 @@ func (h *BufPane) CursorStart() bool { h.Cursor.Deselect(true) h.Cursor.X = 0 h.Cursor.Y = 0 + h.Cursor.StoreVisualX() + h.Relocate() return true } @@ -364,6 +434,7 @@ func (h *BufPane) CursorEnd() bool { h.Cursor.Deselect(true) h.Cursor.Loc = h.Buf.End() h.Cursor.StoreVisualX() + h.Relocate() return true } @@ -374,6 +445,7 @@ func (h *BufPane) SelectToStart() bool { } h.CursorStart() h.Cursor.SelectTo(h.Buf.Start()) + h.Relocate() return true } @@ -384,6 +456,7 @@ func (h *BufPane) SelectToEnd() bool { } h.CursorEnd() h.Cursor.SelectTo(h.Buf.End()) + h.Relocate() return true } @@ -412,10 +485,11 @@ func (h *BufPane) InsertNewline() bool { // Remove the whitespaces if keepautoindent setting is off if util.IsSpacesOrTabs(h.Buf.LineBytes(h.Cursor.Y-1)) && !h.Buf.Settings["keepautoindent"].(bool) { line := h.Buf.LineBytes(h.Cursor.Y - 1) - h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: utf8.RuneCount(line), Y: h.Cursor.Y - 1}) + h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1}) } } h.Cursor.LastVisualX = h.Cursor.GetVisualX() + h.Relocate() return true } @@ -436,7 +510,7 @@ func (h *BufPane) Backspace() bool { // tab (tabSize number of spaces) lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X) tabSize := int(h.Buf.Settings["tabsize"].(float64)) - if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && utf8.RuneCount(lineStart)%tabSize == 0 { + if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 { loc := h.Cursor.Loc h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc) } else { @@ -445,6 +519,7 @@ func (h *BufPane) Backspace() bool { } } h.Cursor.LastVisualX = h.Cursor.GetVisualX() + h.Relocate() return true } @@ -455,6 +530,7 @@ func (h *BufPane) DeleteWordRight() bool { h.Cursor.DeleteSelection() h.Cursor.ResetSelection() } + h.Relocate() return true } @@ -465,6 +541,7 @@ func (h *BufPane) DeleteWordLeft() bool { h.Cursor.DeleteSelection() h.Cursor.ResetSelection() } + h.Relocate() return true } @@ -479,6 +556,7 @@ func (h *BufPane) Delete() bool { h.Buf.Remove(loc, loc.Move(1, h.Buf)) } } + h.Relocate() return true } @@ -499,21 +577,38 @@ func (h *BufPane) IndentSelection() bool { tabsize := int(h.Buf.Settings["tabsize"].(float64)) indentsize := len(h.Buf.IndentString(tabsize)) for y := startY; y <= endY; y++ { - h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize)) - if y == startY && start.X > 0 { - h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf)) - } - if y == endY { - h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY}) + if len(h.Buf.LineBytes(y)) > 0 { + h.Buf.Insert(buffer.Loc{X: 0, Y: y}, h.Buf.IndentString(tabsize)) + if y == startY && start.X > 0 { + h.Cursor.SetSelectionStart(start.Move(indentsize, h.Buf)) + } + if y == endY { + h.Cursor.SetSelectionEnd(buffer.Loc{X: endX + indentsize + 1, Y: endY}) + } } } h.Buf.RelocateCursors() + h.Relocate() return true } return false } +// IndentLine moves the current line forward one indentation +func (h *BufPane) IndentLine() bool { + if h.Cursor.HasSelection() { + return false + } + + tabsize := int(h.Buf.Settings["tabsize"].(float64)) + indentstr := h.Buf.IndentString(tabsize) + h.Buf.Insert(buffer.Loc{X: 0, Y: h.Cursor.Y}, indentstr) + h.Buf.RelocateCursors() + h.Relocate() + return true +} + // OutdentLine moves the current line back one indentation func (h *BufPane) OutdentLine() bool { if h.Cursor.HasSelection() { @@ -527,6 +622,7 @@ func (h *BufPane) OutdentLine() bool { h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y}) } h.Buf.RelocateCursors() + h.Relocate() return true } @@ -553,29 +649,58 @@ func (h *BufPane) OutdentSelection() bool { } h.Buf.RelocateCursors() + h.Relocate() return true } return false } -// InsertTab inserts a tab or spaces -func (h *BufPane) InsertTab() bool { +// Autocomplete cycles the suggestions and performs autocompletion if there are suggestions +func (h *BufPane) Autocomplete() bool { b := h.Buf + + if h.Cursor.HasSelection() { + return false + } + + if h.Cursor.X == 0 { + return false + } + r := h.Cursor.RuneUnder(h.Cursor.X) + prev := h.Cursor.RuneUnder(h.Cursor.X - 1) + if !util.IsAutocomplete(prev) || !util.IsNonAlphaNumeric(r) { + // don't autocomplete if cursor is on alpha numeric character (middle of a word) + return false + } + if b.HasSuggestions { b.CycleAutocomplete(true) return true } + return b.Autocomplete(buffer.BufferComplete) +} - l := b.LineBytes(h.Cursor.Y) - l = util.SliceStart(l, h.Cursor.X) - hasComplete := b.Autocomplete(buffer.BufferComplete) - if !hasComplete { - indent := b.IndentString(util.IntOpt(b.Settings["tabsize"])) - tabBytes := len(indent) - bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes) - b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent]) +// CycleAutocompleteBack cycles back in the autocomplete suggestion list +func (h *BufPane) CycleAutocompleteBack() bool { + if h.Cursor.HasSelection() { + return false + } + + if h.Buf.HasSuggestions { + h.Buf.CycleAutocomplete(false) return true } + return false +} + +// InsertTab inserts a tab or spaces +func (h *BufPane) InsertTab() bool { + b := h.Buf + indent := b.IndentString(util.IntOpt(b.Settings["tabsize"])) + tabBytes := len(indent) + bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX() % tabBytes) + b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent]) + h.Relocate() return true } @@ -584,57 +709,89 @@ func (h *BufPane) SaveAll() bool { for _, b := range buffer.OpenBuffers { b.Save() } - return false + return true } -// Save the buffer to disk -func (h *BufPane) Save() bool { +// SaveCB performs a save and does a callback at the very end (after all prompts have been resolved) +func (h *BufPane) SaveCB(action string, callback func()) bool { // If this is an empty buffer, ask for a filename if h.Buf.Path == "" { - h.SaveAs() + h.SaveAsCB(action, callback) } else { - h.saveBufToFile(h.Buf.Path) + noPrompt := h.saveBufToFile(h.Buf.Path, action, callback) + if noPrompt { + return true + } } - return false } -// SaveAs saves the buffer to disk with the given name -func (h *BufPane) SaveAs() bool { +// Save the buffer to disk +func (h *BufPane) Save() bool { + return h.SaveCB("Save", nil) +} + +// SaveAsCB performs a save as and does a callback at the very end (after all prompts have been resolved) +// The callback is only called if the save was successful +func (h *BufPane) SaveAsCB(action string, callback func()) bool { InfoBar.Prompt("Filename: ", "", "Save", nil, func(resp string, canceled bool) { if !canceled { // the filename might or might not be quoted, so unquote first then join the strings. - args, err := shellwords.Split(resp) - filename := strings.Join(args, " ") + args, err := shellquote.Split(resp) if err != nil { InfoBar.Error("Error parsing arguments: ", err) return } - h.saveBufToFile(filename) - + if len(args) == 0 { + InfoBar.Error("No filename given") + return + } + filename := strings.Join(args, " ") + noPrompt := h.saveBufToFile(filename, action, callback) + if noPrompt { + h.completeAction(action) + } } }) return false } +// SaveAs saves the buffer to disk with the given name +func (h *BufPane) SaveAs() bool { + return h.SaveAsCB("SaveAs", nil) +} + // This function saves the buffer to `filename` and changes the buffer's path and name // to `filename` if the save is successful -func (h *BufPane) saveBufToFile(filename string) { +// The callback is only called if the save was successful +func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool { err := h.Buf.SaveAs(filename) if err != nil { if strings.HasSuffix(err.Error(), "permission denied") { - InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) { - if yes && !canceled { - err = h.Buf.SaveAsWithSudo(filename) - if err != nil { - InfoBar.Error(err) - } else { - h.Buf.Path = filename - h.Buf.SetName(filename) - InfoBar.Message("Saved " + filename) + saveWithSudo := func() { + err = h.Buf.SaveAsWithSudo(filename) + if err != nil { + InfoBar.Error(err) + } else { + h.Buf.Path = filename + h.Buf.SetName(filename) + InfoBar.Message("Saved " + filename) + if callback != nil { + callback() } } - }) + } + if h.Buf.Settings["autosu"].(bool) { + saveWithSudo() + } else { + InfoBar.YNPrompt("Permission denied. Do you want to save this file using sudo? (y,n)", func(yes, canceled bool) { + if yes && !canceled { + saveWithSudo() + h.completeAction(action) + } + }) + return false + } } else { InfoBar.Error(err) } @@ -642,15 +799,56 @@ func (h *BufPane) saveBufToFile(filename string) { h.Buf.Path = filename h.Buf.SetName(filename) InfoBar.Message("Saved " + filename) + if callback != nil { + callback() + } } + return true } // Find opens a prompt and searches forward for the input func (h *BufPane) Find() bool { + return h.find(true) +} + +// FindLiteral is the same as Find() but does not support regular expressions +func (h *BufPane) FindLiteral() bool { + return h.find(false) +} + +// Search searches for a given string/regex in the buffer and selects the next +// match if a match is found +// This function affects lastSearch and lastSearchRegex (saved searches) for +// use with FindNext and FindPrevious +func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error { + match, found, err := h.Buf.FindNext(str, h.Buf.Start(), h.Buf.End(), h.Cursor.Loc, searchDown, useRegex) + if err != nil { + return err + } + if found { + h.Cursor.SetSelectionStart(match[0]) + h.Cursor.SetSelectionEnd(match[1]) + h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] + h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] + h.Cursor.GotoLoc(h.Cursor.CurSelection[1]) + h.lastSearch = str + h.lastSearchRegex = useRegex + h.Relocate() + } else { + h.Cursor.ResetSelection() + } + return nil +} + +func (h *BufPane) find(useRegex bool) bool { h.searchOrig = h.Cursor.Loc - InfoBar.Prompt("Find: ", "", "Find", func(resp string) { + prompt := "Find: " + if useRegex { + prompt = "Find (regex): " + } + InfoBar.Prompt(prompt, "", "Find", func(resp string) { // Event callback - match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true) + match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex) if found { h.Cursor.SetSelectionStart(match[0]) h.Cursor.SetSelectionEnd(match[1]) @@ -665,7 +863,7 @@ func (h *BufPane) Find() bool { }, func(resp string, canceled bool) { // Finished callback if !canceled { - match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true) + match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex) if err != nil { InfoBar.Error(err) } @@ -676,6 +874,7 @@ func (h *BufPane) Find() bool { h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] h.Cursor.GotoLoc(h.Cursor.CurSelection[1]) h.lastSearch = resp + h.lastSearchRegex = useRegex } else { h.Cursor.ResetSelection() InfoBar.Message("No matches found") @@ -686,7 +885,7 @@ func (h *BufPane) Find() bool { h.Relocate() }) - return false + return true } // FindNext searches forwards for the last used search term @@ -699,7 +898,7 @@ func (h *BufPane) FindNext() bool { if h.Cursor.HasSelection() { searchLoc = h.Cursor.CurSelection[1] } - match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, true) + match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.lastSearchRegex) if err != nil { InfoBar.Error(err) } @@ -712,6 +911,7 @@ func (h *BufPane) FindNext() bool { } else { h.Cursor.ResetSelection() } + h.Relocate() return true } @@ -725,7 +925,7 @@ func (h *BufPane) FindPrevious() bool { if h.Cursor.HasSelection() { searchLoc = h.Cursor.CurSelection[0] } - match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, true) + match, found, err := h.Buf.FindNext(h.lastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.lastSearchRegex) if err != nil { InfoBar.Error(err) } @@ -738,6 +938,7 @@ func (h *BufPane) FindPrevious() bool { } else { h.Cursor.ResetSelection() } + h.Relocate() return true } @@ -745,6 +946,7 @@ func (h *BufPane) FindPrevious() bool { func (h *BufPane) Undo() bool { h.Buf.Undo() InfoBar.Message("Undid action") + h.Relocate() return true } @@ -752,16 +954,33 @@ func (h *BufPane) Undo() bool { func (h *BufPane) Redo() bool { h.Buf.Redo() InfoBar.Message("Redid action") + h.Relocate() return true } // Copy the selection to the system clipboard func (h *BufPane) Copy() bool { if h.Cursor.HasSelection() { - h.Cursor.CopySelection("clipboard") + h.Cursor.CopySelection(clipboard.ClipboardReg) h.freshClip = true InfoBar.Message("Copied selection") } + h.Relocate() + return true +} + +// Copy the current line to the clipboard +func (h *BufPane) CopyLine() bool { + if h.Cursor.HasSelection() { + return false + } else { + h.Cursor.SelectLine() + h.Cursor.CopySelection(clipboard.ClipboardReg) + h.freshClip = true + InfoBar.Message("Copied line") + } + h.Cursor.Deselect(true) + h.Relocate() return true } @@ -773,10 +992,10 @@ func (h *BufPane) CutLine() bool { } if h.freshClip == true { if h.Cursor.HasSelection() { - if clip, err := clipboard.ReadAll("clipboard"); err != nil { - // messenger.Error(err) + if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil { + InfoBar.Error(err) } else { - clipboard.WriteAll(clip+string(h.Cursor.GetSelection()), "clipboard") + clipboard.Write(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg) } } } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false { @@ -787,18 +1006,20 @@ func (h *BufPane) CutLine() bool { h.Cursor.DeleteSelection() h.Cursor.ResetSelection() InfoBar.Message("Cut line") + h.Relocate() return true } // Cut the selection to the system clipboard func (h *BufPane) Cut() bool { if h.Cursor.HasSelection() { - h.Cursor.CopySelection("clipboard") + h.Cursor.CopySelection(clipboard.ClipboardReg) h.Cursor.DeleteSelection() h.Cursor.ResetSelection() h.freshClip = true InfoBar.Message("Cut selection") + h.Relocate() return true } else { return h.CutLine() @@ -816,6 +1037,7 @@ func (h *BufPane) DuplicateLine() bool { } InfoBar.Message("Duplicated line") + h.Relocate() return true } @@ -828,6 +1050,7 @@ func (h *BufPane) DeleteLine() bool { h.Cursor.DeleteSelection() h.Cursor.ResetSelection() InfoBar.Message("Deleted line") + h.Relocate() return true } @@ -835,24 +1058,35 @@ func (h *BufPane) DeleteLine() bool { func (h *BufPane) MoveLinesUp() bool { if h.Cursor.HasSelection() { if h.Cursor.CurSelection[0].Y == 0 { - InfoBar.Message("Can not move further up") - return true + InfoBar.Message("Cannot move further up") + return false } start := h.Cursor.CurSelection[0].Y end := h.Cursor.CurSelection[1].Y + sel := 1 if start > end { end, start = start, end + sel = 0 + } + + compensate := false + if h.Cursor.CurSelection[sel].X != 0 { + end++ + } else { + compensate = true } h.Buf.MoveLinesUp( start, end, ) - h.Cursor.CurSelection[1].Y -= 1 + if compensate { + h.Cursor.CurSelection[sel].Y -= 1 + } } else { if h.Cursor.Loc.Y == 0 { - InfoBar.Message("Can not move further up") - return true + InfoBar.Message("Cannot move further up") + return false } h.Buf.MoveLinesUp( h.Cursor.Loc.Y, @@ -860,6 +1094,7 @@ func (h *BufPane) MoveLinesUp() bool { ) } + h.Relocate() return true } @@ -867,13 +1102,19 @@ func (h *BufPane) MoveLinesUp() bool { func (h *BufPane) MoveLinesDown() bool { if h.Cursor.HasSelection() { if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() { - InfoBar.Message("Can not move further down") - return true + InfoBar.Message("Cannot move further down") + return false } start := h.Cursor.CurSelection[0].Y end := h.Cursor.CurSelection[1].Y + sel := 1 if start > end { end, start = start, end + sel = 0 + } + + if h.Cursor.CurSelection[sel].X != 0 { + end++ } h.Buf.MoveLinesDown( @@ -882,8 +1123,8 @@ func (h *BufPane) MoveLinesDown() bool { ) } else { if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 { - InfoBar.Message("Can not move further down") - return true + InfoBar.Message("Cannot move further down") + return false } h.Buf.MoveLinesDown( h.Cursor.Loc.Y, @@ -891,21 +1132,30 @@ func (h *BufPane) MoveLinesDown() bool { ) } + h.Relocate() return true } // Paste whatever is in the system clipboard into the buffer // Delete and paste if the user has a selection func (h *BufPane) Paste() bool { - clip, _ := clipboard.ReadAll("clipboard") + clip, err := clipboard.Read(clipboard.ClipboardReg) + if err != nil { + InfoBar.Error(err) + } h.paste(clip) + h.Relocate() return true } // PastePrimary pastes from the primary clipboard (only use on linux) func (h *BufPane) PastePrimary() bool { - clip, _ := clipboard.ReadAll("primary") + clip, err := clipboard.Read(clipboard.PrimaryReg) + if err != nil { + InfoBar.Error(err) + } h.paste(clip) + h.Relocate() return true } @@ -933,12 +1183,22 @@ func (h *BufPane) paste(clip string) { func (h *BufPane) JumpToMatchingBrace() bool { for _, bp := range buffer.BracePairs { r := h.Cursor.RuneUnder(h.Cursor.X) - if r == bp[0] || r == bp[1] { - matchingBrace := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc) - h.Cursor.GotoLoc(matchingBrace) + rl := h.Cursor.RuneUnder(h.Cursor.X - 1) + if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { + matchingBrace, left, found := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc) + if found { + if left { + h.Cursor.GotoLoc(matchingBrace) + } else { + h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf)) + } + } else { + return false + } } } + h.Relocate() return true } @@ -949,6 +1209,7 @@ func (h *BufPane) SelectAll() bool { // Put the cursor at the beginning h.Cursor.X = 0 h.Cursor.Y = 0 + h.Relocate() return true } @@ -959,7 +1220,17 @@ func (h *BufPane) OpenFile() bool { h.HandleCommand(resp) } }) - return false + return true +} + +// OpenFile opens a new file in the buffer +func (h *BufPane) JumpLine() bool { + InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) { + if !canceled { + h.HandleCommand(resp) + } + }) + return true } // Start moves the viewport to the start of the buffer @@ -967,7 +1238,7 @@ func (h *BufPane) Start() bool { v := h.GetView() v.StartLine = 0 h.SetView(v) - return false + return true } // End moves the viewport to the end of the buffer @@ -981,7 +1252,7 @@ func (h *BufPane) End() bool { v.StartLine = h.Buf.LinesNum() - v.Height h.SetView(v) } - return false + return true } // PageUp scrolls the view up a page @@ -993,7 +1264,7 @@ func (h *BufPane) PageUp() bool { v.StartLine = 0 } h.SetView(v) - return false + return true } // PageDown scrolls the view down a page @@ -1004,7 +1275,7 @@ func (h *BufPane) PageDown() bool { } else if h.Buf.LinesNum() >= v.Height { v.StartLine = h.Buf.LinesNum() - v.Height } - return false + return true } // SelectPageUp selects up one page @@ -1014,6 +1285,7 @@ func (h *BufPane) SelectPageUp() bool { } h.Cursor.UpN(h.GetView().Height) h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -1024,6 +1296,7 @@ func (h *BufPane) SelectPageDown() bool { } h.Cursor.DownN(h.GetView().Height) h.Cursor.SelectTo(h.Cursor.Loc) + h.Relocate() return true } @@ -1037,6 +1310,7 @@ func (h *BufPane) CursorPageUp() bool { h.Cursor.StoreVisualX() } h.Cursor.UpN(h.GetView().Height) + h.Relocate() return true } @@ -1050,6 +1324,7 @@ func (h *BufPane) CursorPageDown() bool { h.Cursor.StoreVisualX() } h.Cursor.DownN(h.GetView().Height) + h.Relocate() return true } @@ -1062,7 +1337,7 @@ func (h *BufPane) HalfPageUp() bool { v.StartLine = 0 } h.SetView(v) - return false + return true } // HalfPageDown scrolls the view down half a page @@ -1076,7 +1351,22 @@ func (h *BufPane) HalfPageDown() bool { } } h.SetView(v) - return false + return true +} + +// ToggleDiffGutter turns the diff gutter off and on +func (h *BufPane) ToggleDiffGutter() bool { + if !h.Buf.Settings["diffgutter"].(bool) { + h.Buf.Settings["diffgutter"] = true + h.Buf.UpdateDiff(func(synchronous bool) { + screen.Redraw() + }) + InfoBar.Message("Enabled diff gutter") + } else { + h.Buf.Settings["diffgutter"] = false + InfoBar.Message("Disabled diff gutter") + } + return true } // ToggleRuler turns line numbers off and on @@ -1088,18 +1378,13 @@ func (h *BufPane) ToggleRuler() bool { h.Buf.Settings["ruler"] = false InfoBar.Message("Disabled ruler") } - return false -} - -// JumpLine jumps to a line and moves the view accordingly. -func (h *BufPane) JumpLine() bool { - return false + return true } // ClearStatus clears the messenger bar func (h *BufPane) ClearStatus() bool { InfoBar.Message("") - return false + return true } // ToggleHelp toggles the help screen @@ -1109,14 +1394,14 @@ func (h *BufPane) ToggleHelp() bool { } else { h.openHelp("help") } - return false + return true } // ToggleKeyMenu toggles the keymenu option and resizes all tabs func (h *BufPane) ToggleKeyMenu() bool { config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool) Tabs.Resize() - return false + return true } // ShellMode opens a terminal to run a shell command @@ -1128,7 +1413,7 @@ func (h *BufPane) ShellMode() bool { } }) - return false + return true } // CommandMode lets the user enter a command @@ -1138,18 +1423,30 @@ func (h *BufPane) CommandMode() bool { h.HandleCommand(resp) } }) - return false + return true } // ToggleOverwriteMode lets the user toggle the text overwrite mode func (h *BufPane) ToggleOverwriteMode() bool { h.isOverwriteMode = !h.isOverwriteMode - return false + return true } // Escape leaves current mode func (h *BufPane) Escape() bool { - return false + return true +} + +// Deselect deselects on the current cursor +func (h *BufPane) Deselect() bool { + h.Cursor.Deselect(true) + return true +} + +// ClearInfo clears the infobar +func (h *BufPane) ClearInfo() bool { + InfoBar.Message("") + return true } // Quit this will close the current tab or view that is open @@ -1163,27 +1460,62 @@ func (h *BufPane) Quit() bool { } else { screen.Screen.Fini() InfoBar.Close() - os.Exit(0) + runtime.Goexit() } } if h.Buf.Modified() { - InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) { - if !canceled && !yes { - quit() - } else if !canceled && yes { - h.Save() + if config.GlobalSettings["autosave"].(float64) > 0 { + // autosave on means we automatically save when quitting + h.SaveCB("Quit", func() { quit() - } - }) + }) + } else { + InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) { + if !canceled && !yes { + quit() + } else if !canceled && yes { + h.SaveCB("Quit", func() { + quit() + }) + } + }) + } } else { quit() } - return false + return true } // QuitAll quits the whole editor; all splits and tabs func (h *BufPane) QuitAll() bool { - return false + anyModified := false + for _, b := range buffer.OpenBuffers { + if b.Modified() { + anyModified = true + break + } + } + + quit := func() { + for _, b := range buffer.OpenBuffers { + b.Close() + } + screen.Screen.Fini() + InfoBar.Close() + runtime.Goexit() + } + + if anyModified { + InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) { + if !canceled && yes { + quit() + } + }) + } else { + quit() + } + + return true } // AddTab adds a new tab with an empty buffer @@ -1195,86 +1527,112 @@ func (h *BufPane) AddTab() bool { Tabs.AddTab(tp) Tabs.SetActive(len(Tabs.List) - 1) - return false + return true } // PreviousTab switches to the previous tab in the tab list func (h *BufPane) PreviousTab() bool { - a := Tabs.Active() - Tabs.SetActive(util.Clamp(a-1, 0, len(Tabs.List)-1)) + tabsLen := len(Tabs.List) + a := Tabs.Active() + tabsLen + Tabs.SetActive((a - 1) % tabsLen) - return false + return true } // NextTab switches to the next tab in the tab list func (h *BufPane) NextTab() bool { a := Tabs.Active() - Tabs.SetActive(util.Clamp(a+1, 0, len(Tabs.List)-1)) - return false + Tabs.SetActive((a + 1) % len(Tabs.List)) + + return true } // VSplitAction opens an empty vertical split func (h *BufPane) VSplitAction() bool { h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault)) - return false + return true } // HSplitAction opens an empty horizontal split func (h *BufPane) HSplitAction() bool { h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault)) - return false + return true } // Unsplit closes all splits in the current tab except the active one func (h *BufPane) Unsplit() bool { - n := MainTab().GetNode(h.splitID) - n.Unsplit() + tab := h.tab + n := tab.GetNode(h.splitID) + ok := n.Unsplit() + if ok { + tab.RemovePane(tab.GetPane(h.splitID)) + tab.Resize() + tab.SetActive(len(tab.Panes) - 1) - MainTab().RemovePane(MainTab().GetPane(h.splitID)) - MainTab().Resize() - MainTab().SetActive(len(MainTab().Panes) - 1) + return true + } return false } // NextSplit changes the view to the next split func (h *BufPane) NextSplit() bool { - a := MainTab().active - if a < len(MainTab().Panes)-1 { + a := h.tab.active + if a < len(h.tab.Panes)-1 { a++ } else { a = 0 } - MainTab().SetActive(a) + h.tab.SetActive(a) - return false + return true } // PreviousSplit changes the view to the previous split func (h *BufPane) PreviousSplit() bool { - a := MainTab().active + a := h.tab.active if a > 0 { a-- } else { - a = len(MainTab().Panes) - 1 + a = len(h.tab.Panes) - 1 } - MainTab().SetActive(a) + h.tab.SetActive(a) - return false + return true } -var curMacro []interface{} -var recordingMacro bool +var curmacro []interface{} +var recording_macro bool // ToggleMacro toggles recording of a macro func (h *BufPane) ToggleMacro() bool { + recording_macro = !recording_macro + if recording_macro { + curmacro = []interface{}{} + InfoBar.Message("Recording") + } else { + InfoBar.Message("Stopped recording") + } + h.Relocate() return true } // PlayMacro plays back the most recently recorded macro func (h *BufPane) PlayMacro() bool { + if recording_macro { + return false + } + for _, action := range curmacro { + switch t := action.(type) { + case rune: + h.DoRuneInsert(t) + case func(*BufPane) bool: + t(h) + } + } + h.Relocate() return true } @@ -1284,6 +1642,7 @@ func (h *BufPane) SpawnMultiCursor() bool { if !spawner.HasSelection() { spawner.SelectWord() h.multiWord = true + h.Relocate() return true } @@ -1314,6 +1673,42 @@ func (h *BufPane) SpawnMultiCursor() bool { InfoBar.Message("No matches found") } + h.Relocate() + return true +} + +// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less. +func (h *BufPane) SpawnMultiCursorUp() bool { + if h.Cursor.Y == 0 { + return false + } else { + h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y - 1}) + h.Cursor.Relocate() + } + + c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y + 1}) + h.Buf.AddCursor(c) + h.Buf.SetCurCursor(h.Buf.NumCursors() - 1) + h.Buf.MergeCursors() + + h.Relocate() + return true +} + +// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more. +func (h *BufPane) SpawnMultiCursorDown() bool { + if h.Cursor.Y+1 == h.Buf.LinesNum() { + return false + } else { + h.Cursor.GotoLoc(buffer.Loc{h.Cursor.X, h.Cursor.Y + 1}) + h.Cursor.Relocate() + } + + c := buffer.NewCursor(h.Buf, buffer.Loc{h.Cursor.X, h.Cursor.Y - 1}) + h.Buf.AddCursor(c) + h.Buf.SetCurCursor(h.Buf.NumCursors() - 1) + h.Buf.MergeCursors() + h.Relocate() return true } @@ -1348,19 +1743,19 @@ func (h *BufPane) SpawnMultiCursorSelect() bool { return false } InfoBar.Message("Added cursors from selection") - return false + return true } // MouseMultiCursor is a mouse action which puts a new cursor at the mouse position func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool { b := h.Buf mx, my := e.Position() - mouseLoc := h.GetMouseLoc(buffer.Loc{X: mx, Y: my}) + mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my}) c := buffer.NewCursor(b, mouseLoc) b.AddCursor(c) b.MergeCursors() - return false + return true } // SkipMultiCursor moves the current multiple cursor to the next available position @@ -1391,6 +1786,7 @@ func (h *BufPane) SkipMultiCursor() bool { } else { InfoBar.Message("No matches found") } + h.Relocate() return true } @@ -1403,6 +1799,7 @@ func (h *BufPane) RemoveMultiCursor() bool { } else { h.multiWord = false } + h.Relocate() return true } @@ -1410,5 +1807,11 @@ func (h *BufPane) RemoveMultiCursor() bool { func (h *BufPane) RemoveAllMultiCursors() bool { h.Buf.ClearCursors() h.multiWord = false + h.Relocate() + return true +} + +// None is an action that does nothing +func (h *BufPane) None() bool { return true }