package action
import (
+ "errors"
+ "fmt"
+ "io/fs"
"regexp"
"runtime"
"strings"
"time"
shellquote "github.com/kballard/go-shellquote"
- "github.com/zyedidia/clipboard"
"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"
+ "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
h.doubleClick = false
h.Cursor.SelectLine()
- h.Cursor.CopySelection("primary")
+ h.Cursor.CopySelection(clipboard.PrimaryReg)
} else {
// Double click
h.lastClickTime = time.Now()
h.tripleClick = false
h.Cursor.SelectWord()
- h.Cursor.CopySelection("primary")
+ h.Cursor.CopySelection(clipboard.PrimaryReg)
}
} else {
h.doubleClick = false
h.Cursor.StoreVisualX()
h.lastLoc = mouseLoc
+ h.Relocate()
return true
}
// 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
}
// CursorDown moves the cursor down
func (h *BufPane) CursorDown() bool {
h.Cursor.Deselect(true)
- h.Cursor.Down()
+ h.MoveCursorDown(1)
h.Relocate()
return true
}
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
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
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") {
+ if errors.Is(err, fs.ErrPermission) {
saveWithSudo := func() {
err = h.Buf.SaveAsWithSudo(filename)
if err != nil {
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)
- }
- })
+ 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 {
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, 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()
+ 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, useRegex)
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
}
// 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
}
-// Copy the current line to the clipboard
+// CopyLine copies 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")
- h.freshClip = true
- if clipboard.Unsupported {
- InfoBar.Message("Copied line (install xclip for external clipboard)")
- } else {
- InfoBar.Message("Copied line")
- }
}
+ h.Cursor.SelectLine()
+ h.Cursor.CopySelection(clipboard.ClipboardReg)
+ h.freshClip = true
+ InfoBar.Message("Copied line")
+
h.Cursor.Deselect(true)
h.Relocate()
return true
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
// 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
h.Relocate()
return true
- } else {
- return h.CutLine()
}
+ return h.CutLine()
}
// DuplicateLine duplicates the current line or selection
// 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
}
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))
}
}
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
} else {
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
}
+ break
} else {
return false
}
// 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
}
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
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
h.Cursor.ResetSelection()
h.Cursor.StoreVisualX()
}
- h.Cursor.UpN(h.GetView().Height)
+ h.MoveCursorUp(h.BufView().Height)
h.Relocate()
return true
}
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)
- } else {
- if h.Buf.LinesNum() >= v.Height {
- v.StartLine = h.Buf.LinesNum() - v.Height
- }
- }
- h.SetView(v)
+ h.ScrollDown(h.BufView().Height / 2)
+ h.ScrollAdjust()
return true
}
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)
- } else {
- screen.Screen.Fini()
- InfoBar.Close()
- runtime.Goexit()
- }
- }
if h.Buf.Modified() {
if config.GlobalSettings["autosave"].(float64) > 0 {
// autosave on means we automatically save when quitting
h.SaveCB("Quit", func() {
- quit()
+ h.ForceQuit()
})
} else {
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
if !canceled && !yes {
- quit()
+ h.ForceQuit()
} else if !canceled && yes {
h.SaveCB("Quit", func() {
- quit()
+ h.ForceQuit()
})
}
})
}
} else {
- quit()
+ h.ForceQuit()
}
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 {
// PlayMacro plays back the most recently recorded macro
func (h *BufPane) PlayMacro() bool {
- if recording_macro {
+ if recordingMacro {
return false
}
for _, action := range curmacro {
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)
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)