X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=internal%2Faction%2Factions.go;h=08bd4c7294d9911a8b30c939f60935c154610644;hb=3a97ce820c05af75b44850246e0609aa257b91c9;hp=3db9af31dec6130e7a7e1e4ba3e1dffbc9993ce4;hpb=31936358c16452b6cadb834fef1aed4a8241a453;p=micro.git diff --git a/internal/action/actions.go b/internal/action/actions.go index 3db9af31..08bd4c72 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -1,40 +1,48 @@ package action import ( + "errors" + "fmt" + "io/fs" "regexp" "runtime" "strings" "time" - "unicode/utf8" shellquote "github.com/kballard/go-shellquote" - "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/tcell" + "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/display" + "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/v2" ) // ScrollUp is not an action func (h *BufPane) ScrollUp(n int) { v := h.GetView() - if v.StartLine >= n { - v.StartLine -= n - h.SetView(v) - } else { - v.StartLine = 0 - } + v.StartLine = h.Scroll(v.StartLine, -n) + h.SetView(v) } // ScrollDown is not an action func (h *BufPane) ScrollDown(n int) { v := h.GetView() - if v.StartLine <= h.Buf.LinesNum()-1-n { - v.StartLine += n - h.SetView(v) + v.StartLine = h.Scroll(v.StartLine, n) + h.SetView(v) +} + +// ScrollAdjust can be used to shift the view so that the last line is at the +// bottom if the user has scrolled past the last line. +func (h *BufPane) ScrollAdjust() { + v := h.GetView() + end := h.SLocFromLoc(h.Buf.End()) + if h.Diff(v.StartLine, end) < h.BufView().Height-1 { + v.StartLine = h.Scroll(end, -h.BufView().Height+1) } + h.SetView(v) } // MousePress is the event that should happen when a normal click happens @@ -60,7 +68,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() @@ -69,7 +77,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 @@ -93,6 +101,7 @@ func (h *BufPane) MousePress(e *tcell.EventMouse) bool { h.Cursor.StoreVisualX() h.lastLoc = mouseLoc + h.Relocate() return true } @@ -111,22 +120,55 @@ func (h *BufPane) ScrollDownAction() bool { // Center centers the view on the cursor func (h *BufPane) Center() bool { v := h.GetView() - v.StartLine = h.Cursor.Y - v.Height/2 - if v.StartLine+v.Height > h.Buf.LinesNum() { - v.StartLine = h.Buf.LinesNum() - v.Height - } - if v.StartLine < 0 { - v.StartLine = 0 - } + v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2) h.SetView(v) - h.Relocate() + h.ScrollAdjust() return true } +// MoveCursorUp is not an action +func (h *BufPane) MoveCursorUp(n int) { + if !h.Buf.Settings["softwrap"].(bool) { + h.Cursor.UpN(n) + } else { + vloc := h.VLocFromLoc(h.Cursor.Loc) + sloc := h.Scroll(vloc.SLoc, -n) + if sloc == vloc.SLoc { + // we are at the beginning of buffer + h.Cursor.Loc = h.Buf.Start() + h.Cursor.LastVisualX = 0 + } else { + vloc.SLoc = sloc + vloc.VisualX = h.Cursor.LastVisualX + h.Cursor.Loc = h.LocFromVLoc(vloc) + } + } +} + +// MoveCursorDown is not an action +func (h *BufPane) MoveCursorDown(n int) { + if !h.Buf.Settings["softwrap"].(bool) { + h.Cursor.DownN(n) + } else { + vloc := h.VLocFromLoc(h.Cursor.Loc) + sloc := h.Scroll(vloc.SLoc, n) + if sloc == vloc.SLoc { + // we are at the end of buffer + h.Cursor.Loc = h.Buf.End() + vloc = h.VLocFromLoc(h.Cursor.Loc) + h.Cursor.LastVisualX = vloc.VisualX + } else { + vloc.SLoc = sloc + vloc.VisualX = h.Cursor.LastVisualX + h.Cursor.Loc = h.LocFromVLoc(vloc) + } + } +} + // CursorUp moves the cursor up func (h *BufPane) CursorUp() bool { h.Cursor.Deselect(true) - h.Cursor.Up() + h.MoveCursorUp(1) h.Relocate() return true } @@ -134,7 +176,7 @@ func (h *BufPane) CursorUp() bool { // CursorDown moves the cursor down func (h *BufPane) CursorDown() bool { h.Cursor.Deselect(true) - h.Cursor.Down() + h.MoveCursorDown(1) h.Relocate() return true } @@ -175,7 +217,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() } @@ -212,7 +254,7 @@ func (h *BufPane) SelectUp() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.Up() + h.MoveCursorUp(1) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -223,7 +265,7 @@ func (h *BufPane) SelectDown() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.Down() + h.MoveCursorDown(1) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -283,7 +325,7 @@ func (h *BufPane) SelectWordLeft() bool { return true } -// StartOfLine moves the cursor to the start of the text of the line +// StartOfText moves the cursor to the start of the text of the line func (h *BufPane) StartOfText() bool { h.Cursor.Deselect(true) h.Cursor.StartOfText() @@ -291,6 +333,19 @@ func (h *BufPane) StartOfText() bool { 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) @@ -325,6 +380,22 @@ func (h *BufPane) SelectToStartOfText() bool { 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 +} + // SelectToStartOfLine selects to the start of the current line func (h *BufPane) SelectToStartOfLine() bool { if !h.Cursor.HasSelection() { @@ -396,6 +467,7 @@ func (h *BufPane) CursorStart() bool { h.Cursor.Deselect(true) h.Cursor.X = 0 h.Cursor.Y = 0 + h.Cursor.StoreVisualX() h.Relocate() return true } @@ -456,7 +528,7 @@ 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() @@ -481,7 +553,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 { @@ -566,6 +638,20 @@ func (h *BufPane) IndentSelection() bool { 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() { @@ -620,6 +706,16 @@ func (h *BufPane) Autocomplete() bool { 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 @@ -659,23 +755,28 @@ func (h *BufPane) SaveAll() bool { 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 { - noPrompt := h.saveBufToFile(h.Buf.Path, "Save") + 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. @@ -689,35 +790,54 @@ func (h *BufPane) SaveAs() bool { return } filename := strings.Join(args, " ") - noPrompt := h.saveBufToFile(filename, "SaveAs") + noPrompt := h.saveBufToFile(filename, action, callback) if noPrompt { - h.completeAction("SaveAs") + 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, action string) bool { +// 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) + if errors.Is(err, fs.ErrPermission) { + 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() } - h.completeAction(action) } - }) - return false + } + if h.Buf.Settings["autosu"].(bool) { + saveWithSudo() + } else { + InfoBar.YNPrompt( + fmt.Sprintf("Permission denied. Do you want to save this file using %s? (y,n)", config.GlobalSettings["sucmd"].(string)), + func(yes, canceled bool) { + if yes && !canceled { + saveWithSudo() + h.completeAction(action) + } + }, + ) + return false + } } else { InfoBar.Error(err) } @@ -725,31 +845,74 @@ func (h *BufPane) saveBufToFile(filename string, action string) bool { 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) { - // Event callback - match, found, _ := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, true) - 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(match[1]) - } else { - h.Cursor.GotoLoc(h.searchOrig) - h.Cursor.ResetSelection() + prompt := "Find: " + if useRegex { + prompt = "Find (regex): " + } + var eventCallback func(resp string) + if h.Buf.Settings["incsearch"].(bool) { + eventCallback = func(resp string) { + 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]) + h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] + h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] + h.Cursor.GotoLoc(match[1]) + } else { + h.Cursor.GotoLoc(h.searchOrig) + h.Cursor.ResetSelection() + } + h.Relocate() } - h.Relocate() - }, func(resp string, canceled bool) { + } + findCallback := 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) } @@ -760,6 +923,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") @@ -768,8 +932,15 @@ func (h *BufPane) Find() bool { h.Cursor.ResetSelection() } h.Relocate() - }) - + } + pattern := string(h.Cursor.GetSelection()) + if eventCallback != nil && pattern != "" { + eventCallback(pattern) + } + InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback) + if pattern != "" { + InfoBar.SelectAll() + } return true } @@ -783,7 +954,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) } @@ -810,7 +981,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) } @@ -846,14 +1017,25 @@ func (h *BufPane) Redo() bool { // 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 - if clipboard.Unsupported { - InfoBar.Message("Copied selection (install xclip for external clipboard)") - } else { - InfoBar.Message("Copied selection") - } + InfoBar.Message("Copied selection") + } + h.Relocate() + return true +} + +// CopyLine copies the current line to the clipboard +func (h *BufPane) CopyLine() bool { + if h.Cursor.HasSelection() { + return false } + h.Cursor.SelectLine() + h.Cursor.CopySelection(clipboard.ClipboardReg) + h.freshClip = true + InfoBar.Message("Copied line") + + h.Cursor.Deselect(true) h.Relocate() return true } @@ -864,15 +1046,15 @@ func (h *BufPane) CutLine() bool { if !h.Cursor.HasSelection() { return false } - if h.freshClip == true { + if h.freshClip { 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.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors()) } } - } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || h.freshClip == false { + } else if time.Since(h.lastCutTime)/time.Second > 10*time.Second || !h.freshClip { h.Copy() } h.freshClip = true @@ -887,7 +1069,7 @@ func (h *BufPane) CutLine() bool { // 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 @@ -895,9 +1077,8 @@ func (h *BufPane) Cut() bool { h.Relocate() return true - } else { - return h.CutLine() } + return h.CutLine() } // DuplicateLine duplicates the current line or selection @@ -937,15 +1118,26 @@ func (h *BufPane) MoveLinesUp() bool { } 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("Cannot move further up") @@ -970,8 +1162,14 @@ func (h *BufPane) MoveLinesDown() bool { } 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( @@ -996,16 +1194,24 @@ func (h *BufPane) MoveLinesDown() bool { // 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") - h.paste(clip) + clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors()) + if err != nil { + InfoBar.Error(err) + } else { + 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") - h.paste(clip) + clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors()) + if err != nil { + InfoBar.Error(err) + } else { + h.paste(clip) + } h.Relocate() return true } @@ -1014,7 +1220,7 @@ func (h *BufPane) paste(clip string) { if h.Buf.Settings["smartpaste"].(bool) { if h.Cursor.X > 0 && len(util.GetLeadingWhitespace([]byte(strings.TrimLeft(clip, "\r\n")))) == 0 { leadingWS := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)) - clip = strings.Replace(clip, "\n", "\n"+string(leadingWS), -1) + clip = strings.ReplaceAll(clip, "\n", "\n"+string(leadingWS)) } } @@ -1026,11 +1232,7 @@ func (h *BufPane) paste(clip string) { h.Buf.Insert(h.Cursor.Loc, clip) // h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf) h.freshClip = false - if clipboard.Unsupported { - InfoBar.Message("Pasted clipboard (install xclip for external clipboard)") - } else { - InfoBar.Message("Pasted clipboard") - } + InfoBar.Message("Pasted clipboard") } // JumpToMatchingBrace moves the cursor to the matching brace if it is @@ -1040,11 +1242,16 @@ func (h *BufPane) JumpToMatchingBrace() bool { r := h.Cursor.RuneUnder(h.Cursor.X) rl := h.Cursor.RuneUnder(h.Cursor.X - 1) if r == bp[0] || r == bp[1] || rl == bp[0] || rl == bp[1] { - matchingBrace, left := h.Buf.FindMatchingBrace(bp, h.Cursor.Loc) - if left { - h.Cursor.GotoLoc(matchingBrace) + 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)) + } + break } else { - h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf)) + return false } } } @@ -1074,48 +1281,42 @@ func (h *BufPane) OpenFile() bool { 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 func (h *BufPane) Start() bool { v := h.GetView() - v.StartLine = 0 + v.StartLine = display.SLoc{0, 0} h.SetView(v) return true } // End moves the viewport to the end of the buffer func (h *BufPane) End() bool { - // TODO: softwrap problems? v := h.GetView() - if v.Height > h.Buf.LinesNum() { - v.StartLine = 0 - h.SetView(v) - } else { - v.StartLine = h.Buf.LinesNum() - v.Height - h.SetView(v) - } + v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1) + h.SetView(v) return true } // PageUp scrolls the view up a page func (h *BufPane) PageUp() bool { - v := h.GetView() - if v.StartLine > v.Height { - h.ScrollUp(v.Height) - } else { - v.StartLine = 0 - } - h.SetView(v) + h.ScrollUp(h.BufView().Height) return true } // PageDown scrolls the view down a page func (h *BufPane) PageDown() bool { - v := h.GetView() - if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height { - h.ScrollDown(v.Height) - } else if h.Buf.LinesNum() >= v.Height { - v.StartLine = h.Buf.LinesNum() - v.Height - } + h.ScrollDown(h.BufView().Height) + h.ScrollAdjust() return true } @@ -1124,7 +1325,7 @@ func (h *BufPane) SelectPageUp() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.UpN(h.GetView().Height) + h.MoveCursorUp(h.BufView().Height) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -1135,7 +1336,7 @@ func (h *BufPane) SelectPageDown() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.DownN(h.GetView().Height) + h.MoveCursorDown(h.BufView().Height) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -1150,7 +1351,7 @@ func (h *BufPane) CursorPageUp() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.UpN(h.GetView().Height) + h.MoveCursorUp(h.BufView().Height) h.Relocate() return true } @@ -1164,34 +1365,36 @@ func (h *BufPane) CursorPageDown() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.DownN(h.GetView().Height) + h.MoveCursorDown(h.BufView().Height) h.Relocate() return true } // HalfPageUp scrolls the view up half a page func (h *BufPane) HalfPageUp() bool { - v := h.GetView() - if v.StartLine > v.Height/2 { - h.ScrollUp(v.Height / 2) - } else { - v.StartLine = 0 - } - h.SetView(v) + h.ScrollUp(h.BufView().Height / 2) return true } // HalfPageDown scrolls the view down half a page func (h *BufPane) HalfPageDown() bool { - v := h.GetView() - if h.Buf.LinesNum()-(v.StartLine+v.Height) > v.Height/2 { - h.ScrollDown(v.Height / 2) + h.ScrollDown(h.BufView().Height / 2) + h.ScrollAdjust() + 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 { - if h.Buf.LinesNum() >= v.Height { - v.StartLine = h.Buf.LinesNum() - v.Height - } + h.Buf.Settings["diffgutter"] = false + InfoBar.Message("Disabled diff gutter") } - h.SetView(v) return true } @@ -1263,37 +1466,55 @@ func (h *BufPane) Escape() bool { 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 +} + +// ForceQuit closes the current tab or view even if there are unsaved changes +// (no prompt) +func (h *BufPane) ForceQuit() bool { + h.Buf.Close() + if len(MainTab().Panes) > 1 { + h.Unsplit() + } else if len(Tabs.List) > 1 { + Tabs.RemoveTab(h.splitID) + } else { + screen.Screen.Fini() + InfoBar.Close() + runtime.Goexit() + } + return true +} + // Quit this will close the current tab or view that is open func (h *BufPane) Quit() bool { - quit := func() { - h.Buf.Close() - if len(MainTab().Panes) > 1 { - h.Unsplit() - } else if len(Tabs.List) > 1 { - Tabs.RemoveTab(h.splitID) + if h.Buf.Modified() { + if config.GlobalSettings["autosave"].(float64) > 0 { + // autosave on means we automatically save when quitting + h.SaveCB("Quit", func() { + h.ForceQuit() + }) } else { - screen.Screen.Fini() - InfoBar.Close() - runtime.Goexit() + InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) { + if !canceled && !yes { + h.ForceQuit() + } else if !canceled && yes { + h.SaveCB("Quit", func() { + h.ForceQuit() + }) + } + }) } - } - if h.Buf.Modified() { - // if config.GlobalSettings["autosave"].(float64) > 0 { - // autosave on means we automatically save when quitting - // h.Save() - // 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.Save() - quit() - } - }) - // } } else { - quit() + h.ForceQuit() } return true } @@ -1344,8 +1565,9 @@ func (h *BufPane) AddTab() bool { // 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 true } @@ -1353,7 +1575,8 @@ func (h *BufPane) PreviousTab() bool { // 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)) + Tabs.SetActive((a + 1) % len(Tabs.List)) + return true } @@ -1373,49 +1596,53 @@ func (h *BufPane) HSplitAction() bool { // 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 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 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 true } var curmacro []interface{} -var recording_macro bool +var recordingMacro bool // ToggleMacro toggles recording of a macro func (h *BufPane) ToggleMacro() bool { - recording_macro = !recording_macro - if recording_macro { + recordingMacro = !recordingMacro + if recordingMacro { curmacro = []interface{}{} InfoBar.Message("Recording") } else { @@ -1427,7 +1654,7 @@ func (h *BufPane) ToggleMacro() bool { // PlayMacro plays back the most recently recorded macro func (h *BufPane) PlayMacro() bool { - if recording_macro { + if recordingMacro { return false } for _, action := range curmacro { @@ -1487,10 +1714,9 @@ func (h *BufPane) SpawnMultiCursor() bool { 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() } + 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) @@ -1501,14 +1727,13 @@ func (h *BufPane) SpawnMultiCursorUp() bool { return true } -// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y more. +// 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() } + 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)