2018-08-27 23:53:08 +00:00
|
|
|
package action
|
2018-08-27 19:53:10 +00:00
|
|
|
|
|
|
|
|
import (
|
2021-08-20 17:55:59 +00:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io/fs"
|
2023-01-30 03:23:21 +00:00
|
|
|
"os"
|
2019-01-16 22:52:30 +00:00
|
|
|
"regexp"
|
2019-12-22 18:43:29 +00:00
|
|
|
"runtime"
|
2019-01-02 21:27:27 +00:00
|
|
|
"strings"
|
2018-12-31 21:36:54 +00:00
|
|
|
"time"
|
2018-08-27 19:53:10 +00:00
|
|
|
|
2020-01-02 23:30:51 +00:00
|
|
|
shellquote "github.com/kballard/go-shellquote"
|
2025-03-24 22:01:48 +00:00
|
|
|
"github.com/micro-editor/tcell/v2"
|
2020-05-04 14:16:15 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/buffer"
|
2020-07-05 00:00:39 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/clipboard"
|
2020-05-04 14:16:15 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/config"
|
2021-04-07 20:18:51 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/display"
|
2020-05-04 14:16:15 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/screen"
|
|
|
|
|
"github.com/zyedidia/micro/v2/internal/shell"
|
|
|
|
|
"github.com/zyedidia/micro/v2/internal/util"
|
2018-08-27 19:53:10 +00:00
|
|
|
)
|
|
|
|
|
|
2019-01-01 03:07:01 +00:00
|
|
|
// ScrollUp is not an action
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ScrollUp(n int) {
|
2019-01-11 02:26:58 +00:00
|
|
|
v := h.GetView()
|
2021-04-07 20:18:51 +00:00
|
|
|
v.StartLine = h.Scroll(v.StartLine, -n)
|
|
|
|
|
h.SetView(v)
|
2019-01-01 03:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ScrollDown is not an action
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ScrollDown(n int) {
|
2019-01-11 02:26:58 +00:00
|
|
|
v := h.GetView()
|
2021-04-07 20:18:51 +00:00
|
|
|
v.StartLine = h.Scroll(v.StartLine, n)
|
|
|
|
|
h.SetView(v)
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-21 22:04:08 +00:00
|
|
|
// 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.
|
2021-04-07 20:18:51 +00:00
|
|
|
func (h *BufPane) ScrollAdjust() {
|
|
|
|
|
v := h.GetView()
|
|
|
|
|
end := h.SLocFromLoc(h.Buf.End())
|
2021-04-08 21:32:00 +00:00
|
|
|
if h.Diff(v.StartLine, end) < h.BufView().Height-1 {
|
|
|
|
|
v.StartLine = h.Scroll(end, -h.BufView().Height+1)
|
2019-01-01 03:07:01 +00:00
|
|
|
}
|
2021-04-07 20:18:51 +00:00
|
|
|
h.SetView(v)
|
2019-01-01 03:07:01 +00:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 20:07:30 +00:00
|
|
|
// ScrollReachedEnd returns true if the view is at the end of the buffer,
|
|
|
|
|
// i.e. the last line of the buffer is in the view.
|
|
|
|
|
func (h *BufPane) ScrollReachedEnd() bool {
|
|
|
|
|
v := h.GetView()
|
|
|
|
|
end := h.SLocFromLoc(h.Buf.End())
|
|
|
|
|
return h.Diff(v.StartLine, end) < h.BufView().Height
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// MousePress is the event that should happen when a normal click happens
|
|
|
|
|
// This is almost always bound to left click
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) MousePress(e *tcell.EventMouse) bool {
|
2019-01-02 22:39:50 +00:00
|
|
|
b := h.Buf
|
|
|
|
|
mx, my := e.Position()
|
2022-10-25 21:31:50 +00:00
|
|
|
// ignore click on the status line
|
|
|
|
|
if my >= h.BufView().Y+h.BufView().Height {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-12-24 21:01:08 +00:00
|
|
|
mouseLoc := h.LocFromVisual(buffer.Loc{mx, my})
|
2019-01-02 22:39:50 +00:00
|
|
|
h.Cursor.Loc = mouseLoc
|
|
|
|
|
|
2022-10-25 21:31:50 +00:00
|
|
|
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 {
|
|
|
|
|
// Triple click
|
|
|
|
|
h.lastClickTime = time.Now()
|
2019-01-02 22:39:50 +00:00
|
|
|
|
2022-10-25 21:31:50 +00:00
|
|
|
h.tripleClick = true
|
|
|
|
|
h.doubleClick = false
|
2019-01-02 22:39:50 +00:00
|
|
|
|
2022-10-25 21:31:50 +00:00
|
|
|
h.Cursor.SelectLine()
|
|
|
|
|
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
2019-01-02 22:39:50 +00:00
|
|
|
} else {
|
2022-10-25 21:31:50 +00:00
|
|
|
// Double click
|
2019-01-02 22:39:50 +00:00
|
|
|
h.lastClickTime = time.Now()
|
|
|
|
|
|
2022-10-25 21:31:50 +00:00
|
|
|
h.doubleClick = true
|
|
|
|
|
h.tripleClick = false
|
|
|
|
|
|
|
|
|
|
h.Cursor.SelectWord()
|
|
|
|
|
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
2019-01-02 22:39:50 +00:00
|
|
|
}
|
2022-10-25 21:31:50 +00:00
|
|
|
} else {
|
|
|
|
|
h.doubleClick = false
|
|
|
|
|
h.tripleClick = false
|
|
|
|
|
h.lastClickTime = time.Now()
|
|
|
|
|
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
h.Cursor.CurSelection[0] = h.Cursor.Loc
|
|
|
|
|
h.Cursor.CurSelection[1] = h.Cursor.Loc
|
2019-01-02 22:39:50 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 03:23:32 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2019-01-02 22:39:50 +00:00
|
|
|
h.lastLoc = mouseLoc
|
2020-08-12 05:18:15 +00:00
|
|
|
h.Relocate()
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2022-10-25 21:31:50 +00:00
|
|
|
func (h *BufPane) MouseDrag(e *tcell.EventMouse) bool {
|
|
|
|
|
mx, my := e.Position()
|
|
|
|
|
// ignore drag on the status line
|
|
|
|
|
if my >= h.BufView().Y+h.BufView().Height {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
|
|
|
|
|
|
|
|
|
if h.tripleClick {
|
|
|
|
|
h.Cursor.AddLineToSelection()
|
|
|
|
|
} else if h.doubleClick {
|
|
|
|
|
h.Cursor.AddWordToSelection()
|
|
|
|
|
} else {
|
2024-04-25 19:58:40 +00:00
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2022-10-25 21:31:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Cursor.StoreVisualX()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *BufPane) MouseRelease(e *tcell.EventMouse) bool {
|
|
|
|
|
// We could finish the selection based on the release location as in the
|
|
|
|
|
// commented out code below, to allow text selections even in a terminal
|
|
|
|
|
// that doesn't support mouse motion events. But when the mouse click is
|
|
|
|
|
// within the scroll margin, that would cause a scroll and selection
|
|
|
|
|
// even for a simple mouse click, which is not good.
|
|
|
|
|
// if !h.doubleClick && !h.tripleClick {
|
|
|
|
|
// mx, my := e.Position()
|
|
|
|
|
// h.Cursor.Loc = h.LocFromVisual(buffer.Loc{mx, my})
|
|
|
|
|
// h.Cursor.SetSelectionEnd(h.Cursor.Loc)
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.CopySelection(clipboard.PrimaryReg)
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// ScrollUpAction scrolls the view up
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ScrollUpAction() bool {
|
2019-01-01 04:47:24 +00:00
|
|
|
h.ScrollUp(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2025-04-12 10:27:00 +00:00
|
|
|
// ScrollDownAction scrolls the view down
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ScrollDownAction() bool {
|
2019-01-01 04:47:24 +00:00
|
|
|
h.ScrollDown(util.IntOpt(h.Buf.Settings["scrollspeed"]))
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Center centers the view on the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Center() bool {
|
2019-01-11 02:26:58 +00:00
|
|
|
v := h.GetView()
|
2021-04-08 21:32:00 +00:00
|
|
|
v.StartLine = h.Scroll(h.SLocFromLoc(h.Cursor.Loc), -h.BufView().Height/2)
|
2019-01-11 02:26:58 +00:00
|
|
|
h.SetView(v)
|
2021-04-07 20:18:51 +00:00
|
|
|
h.ScrollAdjust()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-23 05:25:33 +00:00
|
|
|
// CursorToViewTop moves the cursor to the top of the view,
|
|
|
|
|
// offset by scrollmargin unless at the beginning or end of the file
|
|
|
|
|
func (h *BufPane) CursorToViewTop() bool {
|
|
|
|
|
v := h.GetView()
|
|
|
|
|
h.Buf.ClearCursors()
|
|
|
|
|
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
|
|
|
|
bStart := display.SLoc{0, 0}
|
|
|
|
|
if v.StartLine == bStart {
|
|
|
|
|
scrollmargin = 0
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
|
|
|
|
SLoc: h.Scroll(v.StartLine, scrollmargin),
|
|
|
|
|
VisualX: 0,
|
|
|
|
|
}))
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorToViewCenter moves the cursor to the center of the view
|
|
|
|
|
func (h *BufPane) CursorToViewCenter() bool {
|
|
|
|
|
v := h.GetView()
|
|
|
|
|
h.Buf.ClearCursors()
|
|
|
|
|
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
|
|
|
|
SLoc: h.Scroll(v.StartLine, h.BufView().Height/2),
|
|
|
|
|
VisualX: 0,
|
|
|
|
|
}))
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorToViewBottom moves the cursor to the bottom of the view,
|
|
|
|
|
// offset by scrollmargin unless at the beginning or end of the file
|
|
|
|
|
func (h *BufPane) CursorToViewBottom() bool {
|
|
|
|
|
v := h.GetView()
|
|
|
|
|
h.Buf.ClearCursors()
|
|
|
|
|
scrollmargin := int(h.Buf.Settings["scrollmargin"].(float64))
|
|
|
|
|
bEnd := h.SLocFromLoc(h.Buf.End())
|
|
|
|
|
lastLine := h.Scroll(v.StartLine, h.BufView().Height-1)
|
|
|
|
|
if lastLine == bEnd {
|
|
|
|
|
scrollmargin = 0
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.GotoLoc(h.LocFromVLoc(display.VLoc{
|
|
|
|
|
SLoc: h.Scroll(lastLine, -scrollmargin),
|
|
|
|
|
VisualX: 0,
|
|
|
|
|
}))
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-06 22:43:36 +00:00
|
|
|
// 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()
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2021-03-06 22:43:36 +00:00
|
|
|
} else {
|
|
|
|
|
vloc.SLoc = sloc
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
2021-03-06 22:43:36 +00:00
|
|
|
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()
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2021-03-06 22:43:36 +00:00
|
|
|
} else {
|
|
|
|
|
vloc.SLoc = sloc
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
vloc.VisualX = h.Cursor.LastWrappedVisualX
|
2021-03-06 22:43:36 +00:00
|
|
|
h.Cursor.Loc = h.LocFromVLoc(vloc)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// CursorUp moves the cursor up
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorUp() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
2021-03-06 22:43:36 +00:00
|
|
|
h.MoveCursorUp(1)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorDown moves the cursor down
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorDown() bool {
|
2024-11-20 13:29:02 +00:00
|
|
|
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
2024-04-25 00:27:41 +00:00
|
|
|
h.Cursor.Deselect(false)
|
2024-11-20 13:29:02 +00:00
|
|
|
if selectionEndNewline {
|
|
|
|
|
h.Cursor.Start()
|
2024-11-30 15:51:13 +00:00
|
|
|
} else {
|
|
|
|
|
h.MoveCursorDown(1)
|
2024-11-20 13:29:02 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorLeft moves the cursor left
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorLeft() bool {
|
2019-06-15 20:03:02 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
} else {
|
|
|
|
|
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
|
|
|
|
tabmovement := h.Buf.Settings["tabmovement"].(bool)
|
|
|
|
|
if tabstospaces && tabmovement {
|
|
|
|
|
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
|
|
|
|
line := h.Buf.LineBytes(h.Cursor.Y)
|
|
|
|
|
if h.Cursor.X-tabsize >= 0 && util.IsSpaces(line[h.Cursor.X-tabsize:h.Cursor.X]) && util.IsBytesWhitespace(line[0:h.Cursor.X-tabsize]) {
|
|
|
|
|
for i := 0; i < tabsize; i++ {
|
|
|
|
|
h.Cursor.Left()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.Left()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.Left()
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorRight moves the cursor right
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorRight() bool {
|
2019-06-15 20:03:02 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.Deselect(false)
|
|
|
|
|
} else {
|
|
|
|
|
tabstospaces := h.Buf.Settings["tabstospaces"].(bool)
|
|
|
|
|
tabmovement := h.Buf.Settings["tabmovement"].(bool)
|
|
|
|
|
if tabstospaces && tabmovement {
|
|
|
|
|
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
|
|
|
|
line := h.Buf.LineBytes(h.Cursor.Y)
|
2020-05-20 20:47:08 +00:00
|
|
|
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]) {
|
2019-06-15 20:03:02 +00:00
|
|
|
for i := 0; i < tabsize; i++ {
|
|
|
|
|
h.Cursor.Right()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.Right()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.Right()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WordRight moves the cursor one word to the right
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) WordRight() bool {
|
2019-01-05 02:48:19 +00:00
|
|
|
h.Cursor.Deselect(false)
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.WordRight()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WordLeft moves the cursor one word to the left
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) WordLeft() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.WordLeft()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 06:59:08 +00:00
|
|
|
// SubWordRight moves the cursor one sub-word to the right
|
|
|
|
|
func (h *BufPane) SubWordRight() bool {
|
|
|
|
|
h.Cursor.Deselect(false)
|
|
|
|
|
h.Cursor.SubWordRight()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SubWordLeft moves the cursor one sub-word to the left
|
|
|
|
|
func (h *BufPane) SubWordLeft() bool {
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.SubWordLeft()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// SelectUp selects up one line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectUp() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
2021-03-06 22:43:36 +00:00
|
|
|
h.MoveCursorUp(1)
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectDown selects down one line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectDown() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
2021-03-06 22:43:36 +00:00
|
|
|
h.MoveCursorDown(1)
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectLeft selects the character to the left of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectLeft() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
loc := h.Cursor.Loc
|
|
|
|
|
count := h.Buf.End()
|
|
|
|
|
if loc.GreaterThan(count) {
|
|
|
|
|
loc = count
|
|
|
|
|
}
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.Left()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectRight selects the character to the right of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectRight() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
loc := h.Cursor.Loc
|
|
|
|
|
count := h.Buf.End()
|
|
|
|
|
if loc.GreaterThan(count) {
|
|
|
|
|
loc = count
|
|
|
|
|
}
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.Right()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectWordRight selects the word to the right of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectWordRight() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.WordRight()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectWordLeft selects the word to the left of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectWordLeft() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.WordLeft()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 06:59:08 +00:00
|
|
|
// SelectSubWordRight selects the sub-word to the right of the cursor
|
|
|
|
|
func (h *BufPane) SelectSubWordRight() bool {
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.SubWordRight()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectSubWordLeft selects the sub-word to the left of the cursor
|
|
|
|
|
func (h *BufPane) SelectSubWordLeft() bool {
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.SubWordLeft()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-13 16:04:10 +00:00
|
|
|
// StartOfText moves the cursor to the start of the text of the line
|
2020-01-25 18:02:13 +00:00
|
|
|
func (h *BufPane) StartOfText() bool {
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.StartOfText()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 21:21:02 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// StartOfLine moves the cursor to the start of the line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) StartOfLine() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
2020-01-25 18:02:13 +00:00
|
|
|
h.Cursor.Start()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EndOfLine moves the cursor to the end of the line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) EndOfLine() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.End()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectLine selects the entire current line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectLine() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.SelectLine()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-25 18:02:13 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 21:21:02 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// SelectToStartOfLine selects to the start of the current line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectToStartOfLine() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.Start()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectToEndOfLine selects to the end of the current line
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectToEndOfLine() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.End()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 10:21:57 +00:00
|
|
|
func (h *BufPane) paragraphPrevious() {
|
2018-08-28 22:44:52 +00:00
|
|
|
var line int
|
2024-07-20 10:21:57 +00:00
|
|
|
// Skip to the first non-empty line
|
2018-08-28 22:44:52 +00:00
|
|
|
for line = h.Cursor.Y; line > 0; line-- {
|
2024-07-20 10:21:57 +00:00
|
|
|
if len(h.Buf.LineBytes(line)) != 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Find the first empty line
|
|
|
|
|
for ; line > 0; line-- {
|
|
|
|
|
if len(h.Buf.LineBytes(line)) == 0 {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.X = 0
|
|
|
|
|
h.Cursor.Y = line
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-20 10:21:57 +00:00
|
|
|
// If no empty line was found, move the cursor to the start of the buffer
|
2018-08-28 22:44:52 +00:00
|
|
|
if line == 0 {
|
|
|
|
|
h.Cursor.Loc = h.Buf.Start()
|
|
|
|
|
}
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-07-20 10:21:57 +00:00
|
|
|
func (h *BufPane) paragraphNext() {
|
2018-08-28 22:44:52 +00:00
|
|
|
var line int
|
2024-07-20 10:21:57 +00:00
|
|
|
// Skip to the first non-empty line
|
2018-08-28 22:44:52 +00:00
|
|
|
for line = h.Cursor.Y; line < h.Buf.LinesNum(); line++ {
|
2024-07-20 10:21:57 +00:00
|
|
|
if len(h.Buf.LineBytes(line)) != 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Find the first empty line
|
|
|
|
|
for ; line < h.Buf.LinesNum(); line++ {
|
|
|
|
|
if len(h.Buf.LineBytes(line)) == 0 {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.X = 0
|
|
|
|
|
h.Cursor.Y = line
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-20 10:21:57 +00:00
|
|
|
// If no empty line was found, move the cursor to the end of the buffer
|
2018-08-28 22:44:52 +00:00
|
|
|
if line == h.Buf.LinesNum() {
|
|
|
|
|
h.Cursor.Loc = h.Buf.End()
|
|
|
|
|
}
|
2024-07-20 10:21:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParagraphPrevious moves the cursor to the first empty line that comes before
|
|
|
|
|
// the paragraph closest to the cursor, or beginning of the buffer if there
|
|
|
|
|
// isn't a paragraph
|
|
|
|
|
func (h *BufPane) ParagraphPrevious() bool {
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.paragraphPrevious()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParagraphNext moves the cursor to the first empty line that comes after the
|
|
|
|
|
// paragraph closest to the cursor, or end of the buffer if there isn't a
|
|
|
|
|
// paragraph
|
|
|
|
|
func (h *BufPane) ParagraphNext() bool {
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.paragraphNext()
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectToParagraphPrevious selects to the first empty line that comes before
|
|
|
|
|
// the paragraph closest to the cursor, or beginning of the buffer if there
|
|
|
|
|
// isn't a paragraph
|
|
|
|
|
func (h *BufPane) SelectToParagraphPrevious() bool {
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.paragraphPrevious()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectToParagraphNext selects to the first empty line that comes after the
|
|
|
|
|
// paragraph closest to the cursor, or end of the buffer if there isn't a
|
|
|
|
|
// paragraph
|
|
|
|
|
func (h *BufPane) SelectToParagraphNext() bool {
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.paragraphNext()
|
|
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Retab changes all tabs to spaces or all spaces to tabs depending
|
|
|
|
|
// on the user's settings
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Retab() bool {
|
2019-01-15 03:44:06 +00:00
|
|
|
h.Buf.Retab()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorStart moves the cursor to the start of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorStart() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.X = 0
|
|
|
|
|
h.Cursor.Y = 0
|
2020-02-15 20:38:20 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CursorEnd moves the cursor to the end of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorEnd() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.Loc = h.Buf.End()
|
|
|
|
|
h.Cursor.StoreVisualX()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectToStart selects the text from the cursor to the start of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectToStart() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.CursorStart()
|
|
|
|
|
h.Cursor.SelectTo(h.Buf.Start())
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectToEnd selects the text from the cursor to the end of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectToEnd() bool {
|
2018-08-28 22:44:52 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
|
|
|
|
h.CursorEnd()
|
|
|
|
|
h.Cursor.SelectTo(h.Buf.End())
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// InsertNewline inserts a newline plus possible some whitespace if autoindent is on
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) InsertNewline() bool {
|
2019-01-01 04:47:24 +00:00
|
|
|
// Insert a newline
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ws := util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))
|
|
|
|
|
cx := h.Cursor.X
|
2019-01-17 03:32:33 +00:00
|
|
|
h.Buf.Insert(h.Cursor.Loc, "\n")
|
2019-01-01 04:47:24 +00:00
|
|
|
// h.Cursor.Right()
|
|
|
|
|
|
|
|
|
|
if h.Buf.Settings["autoindent"].(bool) {
|
|
|
|
|
if cx < len(ws) {
|
|
|
|
|
ws = ws[0:cx]
|
|
|
|
|
}
|
2019-01-17 03:32:33 +00:00
|
|
|
h.Buf.Insert(h.Cursor.Loc, string(ws))
|
2019-01-01 04:47:24 +00:00
|
|
|
// for i := 0; i < len(ws); i++ {
|
|
|
|
|
// h.Cursor.Right()
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// 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)
|
2020-05-20 20:47:08 +00:00
|
|
|
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y - 1}, buffer.Loc{X: util.CharacterCount(line), Y: h.Cursor.Y - 1})
|
2019-01-01 04:47:24 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-13 15:46:34 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backspace deletes the previous character
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Backspace() bool {
|
2018-08-29 03:30:39 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
} else if h.Cursor.Loc.GreaterThan(h.Buf.Start()) {
|
|
|
|
|
// We have to do something a bit hacky here because we want to
|
|
|
|
|
// delete the line by first moving left and then deleting backwards
|
|
|
|
|
// but the undo redo would place the cursor in the wrong place
|
|
|
|
|
// So instead we move left, save the position, move back, delete
|
|
|
|
|
// and restore the position
|
|
|
|
|
|
|
|
|
|
// If the user is using spaces instead of tabs and they are deleting
|
|
|
|
|
// whitespace at the start of the line, we should delete as if it's a
|
|
|
|
|
// tab (tabSize number of spaces)
|
|
|
|
|
lineStart := util.SliceStart(h.Buf.LineBytes(h.Cursor.Y), h.Cursor.X)
|
|
|
|
|
tabSize := int(h.Buf.Settings["tabsize"].(float64))
|
2020-05-20 20:47:08 +00:00
|
|
|
if h.Buf.Settings["tabstospaces"].(bool) && util.IsSpaces(lineStart) && len(lineStart) != 0 && util.CharacterCount(lineStart)%tabSize == 0 {
|
2018-08-29 03:30:39 +00:00
|
|
|
loc := h.Cursor.Loc
|
|
|
|
|
h.Buf.Remove(loc.Move(-tabSize, h.Buf), loc)
|
|
|
|
|
} else {
|
|
|
|
|
loc := h.Cursor.Loc
|
|
|
|
|
h.Buf.Remove(loc.Move(-1, h.Buf), loc)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-13 15:46:34 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteWordRight deletes the word to the right of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) DeleteWordRight() bool {
|
2018-09-03 20:54:56 +00:00
|
|
|
h.SelectWordRight()
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteWordLeft deletes the word to the left of the cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) DeleteWordLeft() bool {
|
2018-09-03 20:54:56 +00:00
|
|
|
h.SelectWordLeft()
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-14 06:59:08 +00:00
|
|
|
// DeleteSubWordRight deletes the sub-word to the right of the cursor
|
|
|
|
|
func (h *BufPane) DeleteSubWordRight() bool {
|
|
|
|
|
h.SelectSubWordRight()
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteSubWordLeft deletes the sub-word to the left of the cursor
|
|
|
|
|
func (h *BufPane) DeleteSubWordLeft() bool {
|
|
|
|
|
h.SelectSubWordLeft()
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Delete deletes the next character
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Delete() bool {
|
2018-09-03 20:54:56 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
} else {
|
|
|
|
|
loc := h.Cursor.Loc
|
|
|
|
|
if loc.LessThan(h.Buf.End()) {
|
|
|
|
|
h.Buf.Remove(loc, loc.Move(1, h.Buf))
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IndentSelection indents the current selection
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) IndentSelection() bool {
|
2018-09-03 20:54:56 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
start := h.Cursor.CurSelection[0]
|
|
|
|
|
end := h.Cursor.CurSelection[1]
|
|
|
|
|
if end.Y < start.Y {
|
|
|
|
|
start, end = end, start
|
|
|
|
|
h.Cursor.SetSelectionStart(start)
|
|
|
|
|
h.Cursor.SetSelectionEnd(end)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startY := start.Y
|
|
|
|
|
endY := end.Move(-1, h.Buf).Y
|
|
|
|
|
endX := end.Move(-1, h.Buf).X
|
|
|
|
|
tabsize := int(h.Buf.Settings["tabsize"].(float64))
|
|
|
|
|
indentsize := len(h.Buf.IndentString(tabsize))
|
|
|
|
|
for y := startY; y <= endY; y++ {
|
2020-01-26 05:40:40 +00:00
|
|
|
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})
|
|
|
|
|
}
|
2018-09-03 20:54:56 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-01-24 23:25:59 +00:00
|
|
|
h.Buf.RelocateCursors()
|
2018-09-03 20:54:56 +00:00
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-09-03 20:54:56 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-12 18:30:24 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// OutdentLine moves the current line back one indentation
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) OutdentLine() bool {
|
2018-12-31 21:36:54 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-01 04:47:24 +00:00
|
|
|
for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
|
2018-12-31 21:36:54 +00:00
|
|
|
if len(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y))) == 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
h.Buf.Remove(buffer.Loc{X: 0, Y: h.Cursor.Y}, buffer.Loc{X: 1, Y: h.Cursor.Y})
|
|
|
|
|
}
|
2019-01-24 23:25:59 +00:00
|
|
|
h.Buf.RelocateCursors()
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OutdentSelection takes the current selection and moves it back one indent level
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) OutdentSelection() bool {
|
2018-12-31 21:36:54 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
start := h.Cursor.CurSelection[0]
|
|
|
|
|
end := h.Cursor.CurSelection[1]
|
|
|
|
|
if end.Y < start.Y {
|
|
|
|
|
start, end = end, start
|
|
|
|
|
h.Cursor.SetSelectionStart(start)
|
|
|
|
|
h.Cursor.SetSelectionEnd(end)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startY := start.Y
|
|
|
|
|
endY := end.Move(-1, h.Buf).Y
|
|
|
|
|
for y := startY; y <= endY; y++ {
|
2019-01-01 04:47:24 +00:00
|
|
|
for x := 0; x < len(h.Buf.IndentString(util.IntOpt(h.Buf.Settings["tabsize"]))); x++ {
|
2018-12-31 21:36:54 +00:00
|
|
|
if len(util.GetLeadingWhitespace(h.Buf.LineBytes(y))) == 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
h.Buf.Remove(buffer.Loc{X: 0, Y: y}, buffer.Loc{X: 1, Y: y})
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-24 23:25:59 +00:00
|
|
|
h.Buf.RelocateCursors()
|
2018-12-31 21:36:54 +00:00
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-12-31 21:36:54 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
// Autocomplete cycles the suggestions and performs autocompletion if there are suggestions
|
|
|
|
|
func (h *BufPane) Autocomplete() bool {
|
2019-06-16 19:56:39 +00:00
|
|
|
b := h.Buf
|
2019-12-25 17:54:51 +00:00
|
|
|
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-15 19:02:41 +00:00
|
|
|
if b.HasSuggestions {
|
|
|
|
|
b.CycleAutocomplete(true)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-08 17:54:31 +00:00
|
|
|
if h.Cursor.X == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
r := h.Cursor.RuneUnder(h.Cursor.X)
|
|
|
|
|
prev := h.Cursor.RuneUnder(h.Cursor.X - 1)
|
2024-05-14 06:51:13 +00:00
|
|
|
if !util.IsAutocomplete(prev) || util.IsWordChar(r) {
|
|
|
|
|
// don't autocomplete if cursor is within a word
|
2020-02-09 21:46:53 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return b.Autocomplete(buffer.BufferComplete)
|
|
|
|
|
}
|
2019-06-16 19:56:39 +00:00
|
|
|
|
2020-01-04 20:51:15 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
// 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)
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
bytesUntilIndent := tabBytes - (h.Cursor.GetVisualX(false) % tabBytes)
|
2019-12-25 17:54:51 +00:00
|
|
|
b.Insert(h.Cursor.Loc, indent[:bytesUntilIndent])
|
|
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SaveAll saves all open buffers
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SaveAll() bool {
|
2019-01-14 21:09:46 +00:00
|
|
|
for _, b := range buffer.OpenBuffers {
|
|
|
|
|
b.Save()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-09 21:36:15 +00:00
|
|
|
// 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 {
|
2019-01-17 03:15:11 +00:00
|
|
|
// If this is an empty buffer, ask for a filename
|
|
|
|
|
if h.Buf.Path == "" {
|
2020-02-09 21:36:15 +00:00
|
|
|
h.SaveAsCB(action, callback)
|
2019-01-17 03:15:11 +00:00
|
|
|
} else {
|
2020-02-09 21:36:15 +00:00
|
|
|
noPrompt := h.saveBufToFile(h.Buf.Path, action, callback)
|
2020-01-03 22:38:50 +00:00
|
|
|
if noPrompt {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2019-01-17 03:15:11 +00:00
|
|
|
}
|
2019-12-29 23:23:17 +00:00
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-09 21:36:15 +00:00
|
|
|
// 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)
|
2020-05-29 19:02:38 +00:00
|
|
|
// The callback is only called if the save was successful
|
2020-02-09 21:36:15 +00:00
|
|
|
func (h *BufPane) SaveAsCB(action string, callback func()) bool {
|
2019-01-17 03:15:11 +00:00
|
|
|
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.
|
2020-01-02 23:30:51 +00:00
|
|
|
args, err := shellquote.Split(resp)
|
2019-01-17 03:15:11 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error("Error parsing arguments: ", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2020-01-06 16:38:21 +00:00
|
|
|
if len(args) == 0 {
|
|
|
|
|
InfoBar.Error("No filename given")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
filename := strings.Join(args, " ")
|
2023-01-30 02:11:34 +00:00
|
|
|
fileinfo, err := os.Stat(filename)
|
|
|
|
|
if err != nil {
|
2023-12-10 21:52:22 +00:00
|
|
|
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {
|
2023-01-30 02:11:34 +00:00
|
|
|
noPrompt := h.saveBufToFile(filename, action, callback)
|
|
|
|
|
if noPrompt {
|
|
|
|
|
h.completeAction(action)
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-04-29 19:11:16 +00:00
|
|
|
} else {
|
|
|
|
|
InfoBar.Error(err)
|
|
|
|
|
return
|
2023-01-30 02:11:34 +00:00
|
|
|
}
|
2023-12-10 21:52:22 +00:00
|
|
|
} else {
|
|
|
|
|
InfoBar.YNPrompt(
|
|
|
|
|
fmt.Sprintf("The file %s already exists in the directory, would you like to overwrite? Y/n", fileinfo.Name()),
|
|
|
|
|
func(yes, canceled bool) {
|
|
|
|
|
if yes && !canceled {
|
|
|
|
|
noPrompt := h.saveBufToFile(filename, action, callback)
|
|
|
|
|
if noPrompt {
|
|
|
|
|
h.completeAction(action)
|
|
|
|
|
}
|
2023-01-30 02:11:34 +00:00
|
|
|
}
|
2023-12-10 21:52:22 +00:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
2019-01-17 03:15:11 +00:00
|
|
|
}
|
|
|
|
|
})
|
2019-12-29 23:23:17 +00:00
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-09 21:36:15 +00:00
|
|
|
// SaveAs saves the buffer to disk with the given name
|
|
|
|
|
func (h *BufPane) SaveAs() bool {
|
|
|
|
|
return h.SaveAsCB("SaveAs", nil)
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-17 03:15:11 +00:00
|
|
|
// This function saves the buffer to `filename` and changes the buffer's path and name
|
|
|
|
|
// to `filename` if the save is successful
|
2020-05-29 19:02:38 +00:00
|
|
|
// The callback is only called if the save was successful
|
2020-02-09 21:36:15 +00:00
|
|
|
func (h *BufPane) saveBufToFile(filename string, action string, callback func()) bool {
|
2019-01-17 03:15:11 +00:00
|
|
|
err := h.Buf.SaveAs(filename)
|
|
|
|
|
if err != nil {
|
2021-08-20 17:55:59 +00:00
|
|
|
if errors.Is(err, fs.ErrPermission) {
|
2020-02-12 19:15:30 +00:00
|
|
|
saveWithSudo := func() {
|
|
|
|
|
err = h.Buf.SaveAsWithSudo(filename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Saved " + filename)
|
2020-05-29 19:02:38 +00:00
|
|
|
if callback != nil {
|
|
|
|
|
callback()
|
|
|
|
|
}
|
2020-02-09 21:36:15 +00:00
|
|
|
}
|
2020-02-12 19:15:30 +00:00
|
|
|
}
|
|
|
|
|
if h.Buf.Settings["autosu"].(bool) {
|
|
|
|
|
saveWithSudo()
|
|
|
|
|
} else {
|
2021-08-20 17:55:59 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
2020-02-12 19:15:30 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2019-01-17 03:15:11 +00:00
|
|
|
} else {
|
|
|
|
|
InfoBar.Error(err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Saved " + filename)
|
2020-05-29 19:02:38 +00:00
|
|
|
if callback != nil {
|
|
|
|
|
callback()
|
|
|
|
|
}
|
2020-02-09 21:36:15 +00:00
|
|
|
}
|
2020-01-03 22:38:50 +00:00
|
|
|
return true
|
2019-01-17 03:15:11 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Find opens a prompt and searches forward for the input
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Find() bool {
|
2020-05-17 16:22:33 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-23 22:47:42 +00:00
|
|
|
// Search searches for a given string/regex in the buffer and selects the next
|
|
|
|
|
// match if a match is found
|
2021-09-28 20:39:03 +00:00
|
|
|
// This function behaves the same way as Find and FindLiteral actions:
|
|
|
|
|
// it affects the buffer's LastSearch and LastSearchRegex (saved searches)
|
|
|
|
|
// for use with FindNext and FindPrevious, and turns HighlightSearch on or off
|
|
|
|
|
// according to hlsearch setting
|
2020-06-23 22:47:42 +00:00
|
|
|
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]
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(h.Cursor.CurSelection[1])
|
2021-09-28 20:39:03 +00:00
|
|
|
h.Buf.LastSearch = str
|
|
|
|
|
h.Buf.LastSearchRegex = useRegex
|
|
|
|
|
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
|
2020-06-23 22:47:42 +00:00
|
|
|
} else {
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-17 16:22:33 +00:00
|
|
|
func (h *BufPane) find(useRegex bool) bool {
|
2019-06-15 18:44:03 +00:00
|
|
|
h.searchOrig = h.Cursor.Loc
|
2020-05-17 16:22:33 +00:00
|
|
|
prompt := "Find: "
|
|
|
|
|
if useRegex {
|
|
|
|
|
prompt = "Find (regex): "
|
|
|
|
|
}
|
2021-01-04 06:59:45 +00:00
|
|
|
var eventCallback func(resp string)
|
2021-01-27 18:49:38 +00:00
|
|
|
if h.Buf.Settings["incsearch"].(bool) {
|
2021-01-04 06:59:45 +00:00
|
|
|
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]
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(match[1])
|
2021-01-04 06:59:45 +00:00
|
|
|
} else {
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(h.searchOrig)
|
2021-01-04 06:59:45 +00:00
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2019-01-03 20:27:43 +00:00
|
|
|
}
|
2021-01-04 06:59:45 +00:00
|
|
|
}
|
2021-06-02 20:04:31 +00:00
|
|
|
findCallback := func(resp string, canceled bool) {
|
2019-01-03 20:35:24 +00:00
|
|
|
// Finished callback
|
2019-01-03 20:27:43 +00:00
|
|
|
if !canceled {
|
2020-05-17 16:22:33 +00:00
|
|
|
match, found, err := h.Buf.FindNext(resp, h.Buf.Start(), h.Buf.End(), h.searchOrig, true, useRegex)
|
2019-01-03 20:27:43 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(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]
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(h.Cursor.CurSelection[1])
|
2021-09-28 20:39:03 +00:00
|
|
|
h.Buf.LastSearch = resp
|
|
|
|
|
h.Buf.LastSearchRegex = useRegex
|
|
|
|
|
h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool)
|
2019-01-03 20:27:43 +00:00
|
|
|
} else {
|
|
|
|
|
h.Cursor.ResetSelection()
|
2019-01-03 20:35:24 +00:00
|
|
|
InfoBar.Message("No matches found")
|
2019-01-03 20:27:43 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2021-06-02 20:04:31 +00:00
|
|
|
}
|
|
|
|
|
pattern := string(h.Cursor.GetSelection())
|
2024-03-15 11:25:39 +00:00
|
|
|
if useRegex && pattern != "" {
|
|
|
|
|
pattern = regexp.QuoteMeta(pattern)
|
|
|
|
|
}
|
2021-06-02 20:04:31 +00:00
|
|
|
if eventCallback != nil && pattern != "" {
|
|
|
|
|
eventCallback(pattern)
|
|
|
|
|
}
|
|
|
|
|
InfoBar.Prompt(prompt, pattern, "Find", eventCallback, findCallback)
|
2021-06-09 21:04:11 +00:00
|
|
|
if pattern != "" {
|
|
|
|
|
InfoBar.SelectAll()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2021-09-28 20:39:03 +00:00
|
|
|
// ToggleHighlightSearch toggles highlighting all instances of the last used search term
|
|
|
|
|
func (h *BufPane) ToggleHighlightSearch() bool {
|
|
|
|
|
h.Buf.HighlightSearch = !h.Buf.HighlightSearch
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UnhighlightSearch unhighlights all instances of the last used search term
|
|
|
|
|
func (h *BufPane) UnhighlightSearch() bool {
|
2024-06-15 18:30:05 +00:00
|
|
|
if !h.Buf.HighlightSearch {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2021-09-28 20:39:03 +00:00
|
|
|
h.Buf.HighlightSearch = false
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-08 09:08:38 +00:00
|
|
|
// ResetSearch resets the last used search term
|
|
|
|
|
func (h *BufPane) ResetSearch() bool {
|
|
|
|
|
if h.Buf.LastSearch != "" {
|
|
|
|
|
h.Buf.LastSearch = ""
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// FindNext searches forwards for the last used search term
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) FindNext() bool {
|
2024-06-08 09:06:54 +00:00
|
|
|
if h.Buf.LastSearch == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-01-03 20:35:24 +00:00
|
|
|
// If the cursor is at the start of a selection and we search we want
|
|
|
|
|
// to search from the end of the selection in the case that
|
|
|
|
|
// the selection is a search result in which case we wouldn't move at
|
|
|
|
|
// at all which would be bad
|
|
|
|
|
searchLoc := h.Cursor.Loc
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
searchLoc = h.Cursor.CurSelection[1]
|
|
|
|
|
}
|
2021-09-28 20:39:03 +00:00
|
|
|
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
2019-01-03 20:35:24 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
2024-12-17 17:52:44 +00:00
|
|
|
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
|
|
|
|
// skip empty match at present cursor location
|
|
|
|
|
if searchLoc == h.Buf.End() {
|
|
|
|
|
searchLoc = h.Buf.Start()
|
|
|
|
|
} else {
|
|
|
|
|
searchLoc = searchLoc.Move(1, h.Buf)
|
|
|
|
|
}
|
|
|
|
|
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, true, h.Buf.LastSearchRegex)
|
2019-01-03 20:35:24 +00:00
|
|
|
}
|
|
|
|
|
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]
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(h.Cursor.CurSelection[1])
|
2019-01-03 20:35:24 +00:00
|
|
|
} else {
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FindPrevious searches backwards for the last used search term
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) FindPrevious() bool {
|
2024-06-08 09:06:54 +00:00
|
|
|
if h.Buf.LastSearch == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-01-03 20:35:24 +00:00
|
|
|
// If the cursor is at the end of a selection and we search we want
|
|
|
|
|
// to search from the beginning of the selection in the case that
|
|
|
|
|
// the selection is a search result in which case we wouldn't move at
|
|
|
|
|
// at all which would be bad
|
|
|
|
|
searchLoc := h.Cursor.Loc
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
searchLoc = h.Cursor.CurSelection[0]
|
|
|
|
|
}
|
2021-09-28 20:39:03 +00:00
|
|
|
match, found, err := h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
2019-01-03 20:35:24 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
2024-12-17 17:52:44 +00:00
|
|
|
} else if found && searchLoc == match[0] && match[0] == match[1] {
|
|
|
|
|
// skip empty match at present cursor location
|
|
|
|
|
if searchLoc == h.Buf.Start() {
|
|
|
|
|
searchLoc = h.Buf.End()
|
|
|
|
|
} else {
|
|
|
|
|
searchLoc = searchLoc.Move(-1, h.Buf)
|
|
|
|
|
}
|
|
|
|
|
match, found, _ = h.Buf.FindNext(h.Buf.LastSearch, h.Buf.Start(), h.Buf.End(), searchLoc, false, h.Buf.LastSearchRegex)
|
2019-01-03 20:35:24 +00:00
|
|
|
}
|
|
|
|
|
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]
|
Improve buffer view relocation after jumping to a far-away location (#2628)
* Improve buffer view relocation after jumping to a far-away location
When the cursor is moved to a location which is far away from the
current location (e.g. after a search or a goto line), the buffer view
is always relocated in such a way that the cursor is at the bottom or
at the top (minus scrollmargin), i.e. as if we just scrolled to this
location. It's not like in other editors, and IMHO it's annoying. When
we jump to a new location far away, we usually want to see more of its
context, so the cursor should be placed closer to the center of the
view, not near its edges.
This change implements the behavior similar to other editors:
- If the distance between the new and the old location is less than one
frame (i.e. the view either doesn't change or just slightly "shifts")
then the current behavior remains unchanged.
- Otherwise the current line is placed at 25% of the window height.
* Postpone calling onBufPaneOpen until the initial resize
It is currently not possible to find out the geometry of a newly created
bufpane in onBufPaneOpen lua callback: bp:GetView() returns {0,0,0,0}
instead of the actual window. The reason is that the bufpane view is not
properly initialized yet when the bufpane is created and the callback is
triggered. It is initialized a bit later, at the initial resize.
So postpone calling onBufPaneOpen until after the initial resize.
* Improve buffer view relocation when opening a file at a far-away location
When a file is opened with the initial cursor location at a given line
which is far away from the beginning of the file, the buffer view is
relocated so that the cursor is at the bottom (minus scrollmargin)
as if we just scrolled to this line, which is annoying since we'd rather
like to see more of the context of this initial location.
So implement the behavior similar to the earlier commit (which addresses
a similar issue about jumping far away after a search or goto):
- If the initial cursor location is less than one frame away from the
beginning of the buffer, keep the existing behavior i.e. just display
the beginning of the buffer.
- Otherwise place the cursor location at 25% of the window height.
2022-12-03 03:38:09 +00:00
|
|
|
h.GotoLoc(h.Cursor.CurSelection[1])
|
2019-01-03 20:35:24 +00:00
|
|
|
} else {
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-20 22:23:35 +00:00
|
|
|
// DiffNext searches forward until the beginning of the next block of diffs
|
|
|
|
|
func (h *BufPane) DiffNext() bool {
|
|
|
|
|
cur := h.Cursor.Loc.Y
|
|
|
|
|
dl, err := h.Buf.FindNextDiffLine(cur, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
h.GotoLoc(buffer.Loc{0, dl})
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DiffPrevious searches forward until the end of the previous block of diffs
|
|
|
|
|
func (h *BufPane) DiffPrevious() bool {
|
|
|
|
|
cur := h.Cursor.Loc.Y
|
|
|
|
|
dl, err := h.Buf.FindNextDiffLine(cur, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
h.GotoLoc(buffer.Loc{0, dl})
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Undo undoes the last action
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Undo() bool {
|
2024-06-14 23:00:36 +00:00
|
|
|
if !h.Buf.Undo() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-01-03 20:59:26 +00:00
|
|
|
InfoBar.Message("Undid action")
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Redo redoes the last action
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Redo() bool {
|
2024-06-14 23:00:36 +00:00
|
|
|
if !h.Buf.Redo() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-01-03 20:59:26 +00:00
|
|
|
InfoBar.Message("Redid action")
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
func (h *BufPane) selectLines() int {
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
start := h.Cursor.CurSelection[0]
|
|
|
|
|
end := h.Cursor.CurSelection[1]
|
|
|
|
|
if start.GreaterThan(end) {
|
|
|
|
|
start, end = end, start
|
|
|
|
|
}
|
|
|
|
|
if end.X == 0 {
|
|
|
|
|
end = end.Move(-1, h.Buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.SetSelectionStart(buffer.Loc{0, start.Y})
|
|
|
|
|
h.Cursor.SetSelectionEnd(buffer.Loc{0, end.Y + 1})
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.SelectLine()
|
|
|
|
|
}
|
2024-10-24 16:01:45 +00:00
|
|
|
|
|
|
|
|
nlines := h.Cursor.CurSelection[1].Y - h.Cursor.CurSelection[0].Y
|
|
|
|
|
if nlines == 0 && h.Cursor.HasSelection() {
|
|
|
|
|
// selected last line and it is not empty
|
|
|
|
|
nlines++
|
|
|
|
|
}
|
|
|
|
|
return nlines
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Copy the selection to the system clipboard
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Copy() bool {
|
2024-06-08 23:53:45 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
2024-06-08 23:53:45 +00:00
|
|
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
2024-06-09 12:58:05 +00:00
|
|
|
h.freshClip = false
|
2024-06-08 23:53:45 +00:00
|
|
|
InfoBar.Message("Copied selection")
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
// CopyLine copies the current line to the clipboard. If there is a selection,
|
|
|
|
|
// CopyLine copies all the lines that are (fully or partially) in the selection.
|
2020-04-30 04:06:54 +00:00
|
|
|
func (h *BufPane) CopyLine() bool {
|
2022-07-24 21:09:14 +00:00
|
|
|
origLoc := h.Cursor.Loc
|
2024-06-08 22:24:54 +00:00
|
|
|
origLastVisualX := h.Cursor.LastVisualX
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
origSelection := h.Cursor.CurSelection
|
|
|
|
|
|
|
|
|
|
nlines := h.selectLines()
|
|
|
|
|
if nlines == 0 {
|
2020-04-30 04:54:02 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2021-08-21 22:04:08 +00:00
|
|
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
2024-06-09 12:58:05 +00:00
|
|
|
h.freshClip = false
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
if nlines > 1 {
|
|
|
|
|
InfoBar.Message(fmt.Sprintf("Copied %d lines", nlines))
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Copied line")
|
|
|
|
|
}
|
2021-08-21 22:04:08 +00:00
|
|
|
|
2022-07-24 21:09:14 +00:00
|
|
|
h.Cursor.Loc = origLoc
|
2024-06-08 22:24:54 +00:00
|
|
|
h.Cursor.LastVisualX = origLastVisualX
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
h.Cursor.CurSelection = origSelection
|
2020-04-30 04:54:02 +00:00
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
2020-04-30 04:06:54 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-09 10:28:32 +00:00
|
|
|
// Cut the selection to the system clipboard
|
|
|
|
|
func (h *BufPane) Cut() bool {
|
2018-12-31 21:36:54 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2024-06-09 10:28:32 +00:00
|
|
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
2024-06-09 12:58:05 +00:00
|
|
|
h.freshClip = false
|
2024-06-09 10:28:32 +00:00
|
|
|
InfoBar.Message("Cut selection")
|
|
|
|
|
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
// CutLine cuts the current line to the clipboard. If there is a selection,
|
|
|
|
|
// CutLine cuts all the lines that are (fully or partially) in the selection.
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CutLine() bool {
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
nlines := h.selectLines()
|
|
|
|
|
if nlines == 0 {
|
2018-12-31 21:36:54 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2024-06-09 11:14:02 +00:00
|
|
|
totalLines := nlines
|
2020-09-16 04:08:01 +00:00
|
|
|
if h.freshClip {
|
2024-06-09 10:16:25 +00:00
|
|
|
if clip, err := clipboard.Read(clipboard.ClipboardReg); err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
2024-06-09 13:04:43 +00:00
|
|
|
return false
|
2024-06-09 10:16:25 +00:00
|
|
|
} else {
|
|
|
|
|
clipboard.WriteMulti(clip+string(h.Cursor.GetSelection()), clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
2024-06-09 11:14:02 +00:00
|
|
|
totalLines = strings.Count(clip, "\n") + nlines
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
2024-06-09 10:07:07 +00:00
|
|
|
} else {
|
2024-06-09 12:58:05 +00:00
|
|
|
h.Cursor.CopySelection(clipboard.ClipboardReg)
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
|
|
|
|
h.freshClip = true
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
2024-06-08 17:14:30 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
2024-06-09 11:14:02 +00:00
|
|
|
if totalLines > 1 {
|
|
|
|
|
InfoBar.Message(fmt.Sprintf("Cut %d lines", totalLines))
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Cut line")
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-09 13:21:32 +00:00
|
|
|
// Duplicate the selection
|
|
|
|
|
func (h *BufPane) Duplicate() bool {
|
|
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
2024-06-09 13:21:32 +00:00
|
|
|
h.Buf.Insert(h.Cursor.CurSelection[1], string(h.Cursor.GetSelection()))
|
|
|
|
|
InfoBar.Message("Duplicated selection")
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-08 21:57:57 +00:00
|
|
|
// DuplicateLine duplicates the current line. If there is a selection, DuplicateLine
|
|
|
|
|
// duplicates all the lines that are (fully or partially) in the selection.
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) DuplicateLine() bool {
|
2018-12-31 21:36:54 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
2024-06-08 21:57:57 +00:00
|
|
|
origLoc := h.Cursor.Loc
|
|
|
|
|
origLastVisualX := h.Cursor.LastVisualX
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
origLastWrappedVisualX := h.Cursor.LastWrappedVisualX
|
2024-06-08 21:57:57 +00:00
|
|
|
origSelection := h.Cursor.CurSelection
|
|
|
|
|
|
|
|
|
|
start := h.Cursor.CurSelection[0]
|
|
|
|
|
end := h.Cursor.CurSelection[1]
|
|
|
|
|
if start.GreaterThan(end) {
|
|
|
|
|
start, end = end, start
|
|
|
|
|
}
|
|
|
|
|
if end.X == 0 {
|
|
|
|
|
end = end.Move(-1, h.Buf)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
h.Cursor.Loc = end
|
|
|
|
|
h.Cursor.End()
|
|
|
|
|
for y := start.Y; y <= end.Y; y++ {
|
|
|
|
|
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(y)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Cursor.Loc = origLoc
|
|
|
|
|
h.Cursor.LastVisualX = origLastVisualX
|
Overhaul LastVisualX and GetVisualX() usage
Restore the original meaning of LastVisualX before commit 6d13710d934d
("Implement moving cursor up/down within a wrapped line"): last visual x
location of the cursor in a logical line in the buffer, not in a visual
line on the screen (in other words, taking tabs and wide characters into
account, but not taking softwrap into account). And add a separate
LastWrappedVisualX field, similar to LastVisualX but taking softwrap
into account as well.
This allows tracking last x position at the same time for both cases
when we care about softwrap and when we don't care about it. This can be
useful, for example, for implementing cursor up/down movement actions
that always move by logical lines, not by visual lines, even if softwrap
is enabled (in addition to our default CursorUp and CursorDown actions
that move by visual lines).
Also this fixes a minor bug: in InsertTab(), when `tabstospaces` is
enabled and we insert a tab, the amount of inserted spaces depends on
the visual line wrapping (i.e. on the window width), which is probably
not a good idea.
2024-10-13 21:12:04 +00:00
|
|
|
h.Cursor.LastWrappedVisualX = origLastWrappedVisualX
|
2024-06-08 21:57:57 +00:00
|
|
|
h.Cursor.CurSelection = origSelection
|
|
|
|
|
|
|
|
|
|
if start.Y < end.Y {
|
|
|
|
|
InfoBar.Message(fmt.Sprintf("Duplicated %d lines", end.Y-start.Y+1))
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Duplicated line")
|
|
|
|
|
}
|
2018-12-31 21:36:54 +00:00
|
|
|
} else {
|
|
|
|
|
h.Cursor.End()
|
2019-01-17 03:32:33 +00:00
|
|
|
h.Buf.Insert(h.Cursor.Loc, "\n"+string(h.Buf.LineBytes(h.Cursor.Y)))
|
2024-06-08 21:57:57 +00:00
|
|
|
InfoBar.Message("Duplicated line")
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
// DeleteLine deletes the current line. If there is a selection, DeleteLine
|
|
|
|
|
// deletes all the lines that are (fully or partially) in the selection.
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) DeleteLine() bool {
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
nlines := h.selectLines()
|
|
|
|
|
if nlines == 0 {
|
2018-12-31 21:36:54 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
2024-06-08 17:14:30 +00:00
|
|
|
h.Cursor.StoreVisualX()
|
CopyLine, CutLine, DeleteLine: respect selection
When there is a selection containing multiple lines, CutLine, DeleteLine
and CopyLine actions currently cut/delete/copy just the "current" line,
as usual. This behavior is at least confusing, since when there is a
selection, the cursor is not displayed, so the user doesn't know which
line is the current one.
So change the behavior. When there is a multi-line selection,
cut/delete/copy all lines covered by the selection, not just the current
line. Note that it will cut/delete/copy whole lines, not just the
selection itself, i.e. if the first and/or the last line of the
selection is only partially within the selection, we will
cut/delete/copy the entire first and last lines nonetheless.
2024-06-09 10:59:19 +00:00
|
|
|
if nlines > 1 {
|
|
|
|
|
InfoBar.Message(fmt.Sprintf("Deleted %d lines", nlines))
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Deleted line")
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MoveLinesUp moves up the current line or selected lines if any
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) MoveLinesUp() bool {
|
2019-01-15 03:38:59 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
if h.Cursor.CurSelection[0].Y == 0 {
|
2019-12-25 17:54:51 +00:00
|
|
|
InfoBar.Message("Cannot move further up")
|
|
|
|
|
return false
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
start := h.Cursor.CurSelection[0].Y
|
|
|
|
|
end := h.Cursor.CurSelection[1].Y
|
2020-05-07 23:39:17 +00:00
|
|
|
sel := 1
|
2019-01-15 03:38:59 +00:00
|
|
|
if start > end {
|
|
|
|
|
end, start = start, end
|
2020-05-07 23:39:17 +00:00
|
|
|
sel = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compensate := false
|
|
|
|
|
if h.Cursor.CurSelection[sel].X != 0 {
|
|
|
|
|
end++
|
|
|
|
|
} else {
|
|
|
|
|
compensate = true
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Buf.MoveLinesUp(
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
)
|
2020-05-07 23:39:17 +00:00
|
|
|
if compensate {
|
|
|
|
|
h.Cursor.CurSelection[sel].Y -= 1
|
|
|
|
|
}
|
2019-01-15 03:38:59 +00:00
|
|
|
} else {
|
|
|
|
|
if h.Cursor.Loc.Y == 0 {
|
2019-12-25 17:54:51 +00:00
|
|
|
InfoBar.Message("Cannot move further up")
|
|
|
|
|
return false
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
h.Buf.MoveLinesUp(
|
|
|
|
|
h.Cursor.Loc.Y,
|
|
|
|
|
h.Cursor.Loc.Y+1,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MoveLinesDown moves down the current line or selected lines if any
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) MoveLinesDown() bool {
|
2019-01-15 03:38:59 +00:00
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
if h.Cursor.CurSelection[1].Y >= h.Buf.LinesNum() {
|
2019-12-25 17:54:51 +00:00
|
|
|
InfoBar.Message("Cannot move further down")
|
|
|
|
|
return false
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
start := h.Cursor.CurSelection[0].Y
|
|
|
|
|
end := h.Cursor.CurSelection[1].Y
|
2020-05-07 23:39:17 +00:00
|
|
|
sel := 1
|
2019-01-15 03:38:59 +00:00
|
|
|
if start > end {
|
|
|
|
|
end, start = start, end
|
2020-05-07 23:39:17 +00:00
|
|
|
sel = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h.Cursor.CurSelection[sel].X != 0 {
|
|
|
|
|
end++
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.Buf.MoveLinesDown(
|
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
if h.Cursor.Loc.Y >= h.Buf.LinesNum()-1 {
|
2019-12-25 17:54:51 +00:00
|
|
|
InfoBar.Message("Cannot move further down")
|
|
|
|
|
return false
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
h.Buf.MoveLinesDown(
|
|
|
|
|
h.Cursor.Loc.Y,
|
|
|
|
|
h.Cursor.Loc.Y+1,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Paste whatever is in the system clipboard into the buffer
|
|
|
|
|
// Delete and paste if the user has a selection
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Paste() bool {
|
2020-07-05 05:12:35 +00:00
|
|
|
clip, err := clipboard.ReadMulti(clipboard.ClipboardReg, h.Cursor.Num, h.Buf.NumCursors())
|
2020-07-05 00:00:39 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
2020-07-05 00:54:27 +00:00
|
|
|
} else {
|
|
|
|
|
h.paste(clip)
|
2020-07-05 00:00:39 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PastePrimary pastes from the primary clipboard (only use on linux)
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PastePrimary() bool {
|
2020-07-05 05:12:35 +00:00
|
|
|
clip, err := clipboard.ReadMulti(clipboard.PrimaryReg, h.Cursor.Num, h.Buf.NumCursors())
|
2020-07-05 00:00:39 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
2020-07-05 00:54:27 +00:00
|
|
|
} else {
|
|
|
|
|
h.paste(clip)
|
2020-07-05 00:00:39 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) paste(clip string) {
|
2019-01-02 21:27:27 +00:00
|
|
|
if h.Buf.Settings["smartpaste"].(bool) {
|
2024-03-13 20:16:10 +00:00
|
|
|
if h.Cursor.X > 0 {
|
|
|
|
|
leadingPasteWS := string(util.GetLeadingWhitespace([]byte(clip)))
|
|
|
|
|
if leadingPasteWS != " " && strings.Contains(clip, "\n"+leadingPasteWS) {
|
|
|
|
|
leadingWS := string(util.GetLeadingWhitespace(h.Buf.LineBytes(h.Cursor.Y)))
|
|
|
|
|
clip = strings.TrimPrefix(clip, leadingPasteWS)
|
|
|
|
|
clip = strings.ReplaceAll(clip, "\n"+leadingPasteWS, "\n"+leadingWS)
|
|
|
|
|
}
|
2019-01-02 21:27:27 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.DeleteSelection()
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-17 03:32:33 +00:00
|
|
|
h.Buf.Insert(h.Cursor.Loc, clip)
|
2019-01-02 21:27:27 +00:00
|
|
|
// h.Cursor.Loc = h.Cursor.Loc.Move(Count(clip), h.Buf)
|
|
|
|
|
h.freshClip = false
|
2020-07-05 00:00:39 +00:00
|
|
|
InfoBar.Message("Pasted clipboard")
|
2019-01-02 21:27:27 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// JumpToMatchingBrace moves the cursor to the matching brace if it is
|
|
|
|
|
// currently on a brace
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) JumpToMatchingBrace() bool {
|
2024-06-04 22:56:19 +00:00
|
|
|
matchingBrace, left, found := h.Buf.FindMatchingBrace(h.Cursor.Loc)
|
|
|
|
|
if found {
|
Add `matchbraceleft` option (#3432)
Add `matchbraceleft` option to allow disabling the default behavior
matching not just the brace under cursor but also the brace to the left
of it (which is arguably convenient, but also ambiguous and
non-intuitive). With `matchbraceleft` disabled, micro will only match
the brace character that is precisely under the cursor, and also when
jumping to the matching brace, will always move cursor precisely to the
matching brace character, not to the character next to it.
Nota bene: historical journey:
- There was already a `matchbraceleft` option introduced in commit
ea6a87d41a9f, when this feature (matching brace to the left) was
introduced first time. That time it was matching _only_ the brace
to the left, _instead_ of the brace under the cursor, and was
disabled by default.
- Later this feature was removed during the big refactoring of micro.
- Then this feature was reintroduced again in commit d1e713ce08ba, in
its present form (i.e. combined brace matching both under the cursor
and to the left, simulating I-beam cursor behavior), and it was
introduced unconditionally, without an option to disable it.
- Since then, multiple users complained about this feature and asked
for an option to disable it, so now we are reintroducing it as an
option again (this time enabled by default though).
2024-08-18 19:08:05 +00:00
|
|
|
if h.Buf.Settings["matchbraceleft"].(bool) {
|
|
|
|
|
if left {
|
|
|
|
|
h.Cursor.GotoLoc(matchingBrace)
|
|
|
|
|
} else {
|
|
|
|
|
h.Cursor.GotoLoc(matchingBrace.Move(1, h.Buf))
|
|
|
|
|
}
|
2024-06-04 22:56:19 +00:00
|
|
|
} else {
|
Add `matchbraceleft` option (#3432)
Add `matchbraceleft` option to allow disabling the default behavior
matching not just the brace under cursor but also the brace to the left
of it (which is arguably convenient, but also ambiguous and
non-intuitive). With `matchbraceleft` disabled, micro will only match
the brace character that is precisely under the cursor, and also when
jumping to the matching brace, will always move cursor precisely to the
matching brace character, not to the character next to it.
Nota bene: historical journey:
- There was already a `matchbraceleft` option introduced in commit
ea6a87d41a9f, when this feature (matching brace to the left) was
introduced first time. That time it was matching _only_ the brace
to the left, _instead_ of the brace under the cursor, and was
disabled by default.
- Later this feature was removed during the big refactoring of micro.
- Then this feature was reintroduced again in commit d1e713ce08ba, in
its present form (i.e. combined brace matching both under the cursor
and to the left, simulating I-beam cursor behavior), and it was
introduced unconditionally, without an option to disable it.
- Since then, multiple users complained about this feature and asked
for an option to disable it, so now we are reintroducing it as an
option again (this time enabled by default though).
2024-08-18 19:08:05 +00:00
|
|
|
h.Cursor.GotoLoc(matchingBrace)
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
2024-06-04 22:56:19 +00:00
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
2019-01-15 03:38:59 +00:00
|
|
|
}
|
2022-07-24 21:06:59 +00:00
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectAll selects the entire buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectAll() bool {
|
2018-12-31 21:36:54 +00:00
|
|
|
h.Cursor.SetSelectionStart(h.Buf.Start())
|
|
|
|
|
h.Cursor.SetSelectionEnd(h.Buf.End())
|
|
|
|
|
// Put the cursor at the beginning
|
|
|
|
|
h.Cursor.X = 0
|
|
|
|
|
h.Cursor.Y = 0
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OpenFile opens a new file in the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) OpenFile() bool {
|
2019-01-03 22:07:28 +00:00
|
|
|
InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) {
|
2019-01-01 04:47:24 +00:00
|
|
|
if !canceled {
|
2019-01-11 19:49:22 +00:00
|
|
|
h.HandleCommand(resp)
|
2019-01-01 04:47:24 +00:00
|
|
|
}
|
2019-01-02 21:27:27 +00:00
|
|
|
})
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-20 22:23:35 +00:00
|
|
|
// JumpLine asks the user to enter a line number to jump to
|
2020-03-03 01:09:19 +00:00
|
|
|
func (h *BufPane) JumpLine() bool {
|
|
|
|
|
InfoBar.Prompt("> ", "goto ", "Command", nil, func(resp string, canceled bool) {
|
|
|
|
|
if !canceled {
|
|
|
|
|
h.HandleCommand(resp)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Start moves the viewport to the start of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Start() bool {
|
2019-01-11 02:26:58 +00:00
|
|
|
v := h.GetView()
|
2021-04-07 20:18:51 +00:00
|
|
|
v.StartLine = display.SLoc{0, 0}
|
2019-01-11 02:26:58 +00:00
|
|
|
h.SetView(v)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// End moves the viewport to the end of the buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) End() bool {
|
2019-01-11 02:26:58 +00:00
|
|
|
v := h.GetView()
|
2021-04-08 21:32:00 +00:00
|
|
|
v.StartLine = h.Scroll(h.SLocFromLoc(h.Buf.End()), -h.BufView().Height+1)
|
2021-04-07 20:18:51 +00:00
|
|
|
h.SetView(v)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PageUp scrolls the view up a page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PageUp() bool {
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
h.ScrollUp(h.BufView().Height - pageOverlap)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PageDown scrolls the view down a page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PageDown() bool {
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
h.ScrollDown(h.BufView().Height - pageOverlap)
|
2021-04-07 20:18:51 +00:00
|
|
|
h.ScrollAdjust()
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectPageUp selects up one page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectPageUp() bool {
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
scrollAmount := h.BufView().Height - pageOverlap
|
2019-01-02 21:27:27 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
2024-10-23 21:24:08 +00:00
|
|
|
h.MoveCursorUp(scrollAmount)
|
2019-01-02 21:27:27 +00:00
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2024-10-23 21:24:08 +00:00
|
|
|
if h.Cursor.Num == 0 {
|
|
|
|
|
h.ScrollUp(scrollAmount)
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SelectPageDown selects down one page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SelectPageDown() bool {
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
scrollAmount := h.BufView().Height - pageOverlap
|
2019-01-02 21:27:27 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.OrigSelection[0] = h.Cursor.Loc
|
|
|
|
|
}
|
2024-10-23 21:24:08 +00:00
|
|
|
h.MoveCursorDown(scrollAmount)
|
2019-01-02 21:27:27 +00:00
|
|
|
h.Cursor.SelectTo(h.Cursor.Loc)
|
2024-12-03 20:07:30 +00:00
|
|
|
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
2024-10-23 21:24:08 +00:00
|
|
|
h.ScrollDown(scrollAmount)
|
|
|
|
|
h.ScrollAdjust()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-23 21:24:08 +00:00
|
|
|
// CursorPageUp places the cursor a page up,
|
|
|
|
|
// moving the view to keep cursor at the same relative position in the view
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorPageUp() bool {
|
2019-01-02 21:27:27 +00:00
|
|
|
h.Cursor.Deselect(true)
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
scrollAmount := h.BufView().Height - pageOverlap
|
|
|
|
|
h.MoveCursorUp(scrollAmount)
|
|
|
|
|
if h.Cursor.Num == 0 {
|
|
|
|
|
h.ScrollUp(scrollAmount)
|
2019-01-02 21:27:27 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-23 21:24:08 +00:00
|
|
|
// CursorPageDown places the cursor a page down,
|
|
|
|
|
// moving the view to keep cursor at the same relative position in the view
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CursorPageDown() bool {
|
2024-11-20 13:29:02 +00:00
|
|
|
selectionEndNewline := h.Cursor.HasSelection() && h.Cursor.CurSelection[1].X == 0
|
2019-01-02 21:27:27 +00:00
|
|
|
h.Cursor.Deselect(false)
|
2024-10-23 21:24:08 +00:00
|
|
|
pageOverlap := int(h.Buf.Settings["pageoverlap"].(float64))
|
|
|
|
|
scrollAmount := h.BufView().Height - pageOverlap
|
2024-11-30 15:51:13 +00:00
|
|
|
if selectionEndNewline {
|
|
|
|
|
scrollAmount--
|
|
|
|
|
}
|
2024-10-23 21:24:08 +00:00
|
|
|
h.MoveCursorDown(scrollAmount)
|
2024-12-03 20:07:30 +00:00
|
|
|
if h.Cursor.Num == 0 && !h.ScrollReachedEnd() {
|
2024-10-23 21:24:08 +00:00
|
|
|
h.ScrollDown(scrollAmount)
|
|
|
|
|
h.ScrollAdjust()
|
2019-01-02 21:27:27 +00:00
|
|
|
}
|
2024-11-20 13:29:02 +00:00
|
|
|
if selectionEndNewline {
|
|
|
|
|
h.Cursor.Start()
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HalfPageUp scrolls the view up half a page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) HalfPageUp() bool {
|
2021-04-08 21:32:00 +00:00
|
|
|
h.ScrollUp(h.BufView().Height / 2)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HalfPageDown scrolls the view down half a page
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) HalfPageDown() bool {
|
2021-04-08 21:32:00 +00:00
|
|
|
h.ScrollDown(h.BufView().Height / 2)
|
2021-04-07 20:18:51 +00:00
|
|
|
h.ScrollAdjust()
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-02-08 07:56:24 +00:00
|
|
|
// ToggleDiffGutter turns the diff gutter off and on
|
|
|
|
|
func (h *BufPane) ToggleDiffGutter() bool {
|
2025-02-16 17:01:18 +00:00
|
|
|
diffgutter := !h.Buf.Settings["diffgutter"].(bool)
|
|
|
|
|
h.Buf.SetOptionNative("diffgutter", diffgutter)
|
|
|
|
|
if diffgutter {
|
2024-05-12 18:02:51 +00:00
|
|
|
h.Buf.UpdateDiff()
|
2020-02-08 07:56:24 +00:00
|
|
|
InfoBar.Message("Enabled diff gutter")
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Disabled diff gutter")
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// ToggleRuler turns line numbers off and on
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ToggleRuler() bool {
|
2025-02-16 17:01:18 +00:00
|
|
|
ruler := !h.Buf.Settings["ruler"].(bool)
|
|
|
|
|
h.Buf.SetOptionNative("ruler", ruler)
|
|
|
|
|
if ruler {
|
2019-01-02 03:36:12 +00:00
|
|
|
InfoBar.Message("Enabled ruler")
|
2018-12-31 21:36:54 +00:00
|
|
|
} else {
|
2019-01-02 03:36:12 +00:00
|
|
|
InfoBar.Message("Disabled ruler")
|
2018-12-31 21:36:54 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-06-16 15:53:08 +00:00
|
|
|
// ClearStatus clears the infobar. It is an alias for ClearInfo.
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ClearStatus() bool {
|
2024-06-16 15:53:08 +00:00
|
|
|
return h.ClearInfo()
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToggleHelp toggles the help screen
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ToggleHelp() bool {
|
2019-01-15 22:10:13 +00:00
|
|
|
if h.Buf.Type == buffer.BTHelp {
|
|
|
|
|
h.Quit()
|
|
|
|
|
} else {
|
2024-10-15 04:19:07 +00:00
|
|
|
hsplit := config.GlobalSettings["helpsplit"] == "hsplit"
|
|
|
|
|
h.openHelp("help", hsplit, false)
|
2019-01-15 22:10:13 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToggleKeyMenu toggles the keymenu option and resizes all tabs
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ToggleKeyMenu() bool {
|
2019-01-16 23:37:45 +00:00
|
|
|
config.GlobalSettings["keymenu"] = !config.GetGlobalOption("keymenu").(bool)
|
|
|
|
|
Tabs.Resize()
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ShellMode opens a terminal to run a shell command
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ShellMode() bool {
|
2019-01-10 21:37:05 +00:00
|
|
|
InfoBar.Prompt("$ ", "", "Shell", nil, func(resp string, canceled bool) {
|
|
|
|
|
if !canceled {
|
|
|
|
|
// The true here is for openTerm to make the command interactive
|
|
|
|
|
shell.RunInteractiveShell(resp, true, false)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CommandMode lets the user enter a command
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) CommandMode() bool {
|
2019-01-03 22:07:28 +00:00
|
|
|
InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) {
|
2019-01-02 04:29:25 +00:00
|
|
|
if !canceled {
|
2019-01-11 19:49:22 +00:00
|
|
|
h.HandleCommand(resp)
|
2019-01-02 04:29:25 +00:00
|
|
|
}
|
|
|
|
|
})
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ToggleOverwriteMode lets the user toggle the text overwrite mode
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ToggleOverwriteMode() bool {
|
2025-01-20 20:49:50 +00:00
|
|
|
h.Buf.OverwriteMode = !h.Buf.OverwriteMode
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Escape leaves current mode
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Escape() bool {
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2020-07-04 01:02:16 +00:00
|
|
|
// Deselect deselects on the current cursor
|
|
|
|
|
func (h *BufPane) Deselect() bool {
|
2024-06-15 18:30:05 +00:00
|
|
|
if !h.Cursor.HasSelection() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2020-07-04 01:02:16 +00:00
|
|
|
h.Cursor.Deselect(true)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ClearInfo clears the infobar
|
|
|
|
|
func (h *BufPane) ClearInfo() bool {
|
2024-06-15 18:30:05 +00:00
|
|
|
if InfoBar.Msg == "" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2020-07-04 01:02:16 +00:00
|
|
|
InfoBar.Message("")
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-02 02:55:49 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-18 11:21:27 +00:00
|
|
|
// closePrompt displays a prompt to save the buffer before closing it to proceed
|
|
|
|
|
// with a different action or command
|
|
|
|
|
func (h *BufPane) closePrompt(action string, callback func()) {
|
|
|
|
|
InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) {
|
|
|
|
|
if !canceled && !yes {
|
|
|
|
|
callback()
|
|
|
|
|
} else if !canceled && yes {
|
|
|
|
|
h.SaveCB(action, callback)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// Quit this will close the current tab or view that is open
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Quit() bool {
|
2025-04-18 11:19:19 +00:00
|
|
|
if h.Buf.Modified() && !h.Buf.Shared() {
|
2025-01-25 19:24:31 +00:00
|
|
|
if config.GlobalSettings["autosave"].(float64) > 0 && h.Buf.Path != "" {
|
2020-02-08 21:53:08 +00:00
|
|
|
// autosave on means we automatically save when quitting
|
2020-02-09 21:36:15 +00:00
|
|
|
h.SaveCB("Quit", func() {
|
2021-03-02 02:55:49 +00:00
|
|
|
h.ForceQuit()
|
2020-02-09 21:36:15 +00:00
|
|
|
})
|
2020-02-08 21:53:08 +00:00
|
|
|
} else {
|
2025-04-18 11:21:27 +00:00
|
|
|
h.closePrompt("Quit", func() {
|
|
|
|
|
h.ForceQuit()
|
2020-02-08 21:53:08 +00:00
|
|
|
})
|
|
|
|
|
}
|
2019-01-05 02:48:19 +00:00
|
|
|
} else {
|
2021-03-02 02:55:49 +00:00
|
|
|
h.ForceQuit()
|
2019-01-05 02:48:19 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// QuitAll quits the whole editor; all splits and tabs
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) QuitAll() bool {
|
2019-08-05 03:23:32 +00:00
|
|
|
anyModified := false
|
|
|
|
|
for _, b := range buffer.OpenBuffers {
|
|
|
|
|
if b.Modified() {
|
|
|
|
|
anyModified = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
quit := func() {
|
2023-08-31 11:53:33 +00:00
|
|
|
buffer.CloseOpenBuffers()
|
2019-08-05 03:23:32 +00:00
|
|
|
screen.Screen.Fini()
|
|
|
|
|
InfoBar.Close()
|
2019-12-22 18:43:29 +00:00
|
|
|
runtime.Goexit()
|
2019-08-05 03:23:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if anyModified {
|
|
|
|
|
InfoBar.YNPrompt("Quit micro? (all open buffers will be closed without saving)", func(yes, canceled bool) {
|
|
|
|
|
if !canceled && yes {
|
|
|
|
|
quit()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
quit()
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AddTab adds a new tab with an empty buffer
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) AddTab() bool {
|
2019-01-10 04:44:53 +00:00
|
|
|
width, height := screen.Screen.Size()
|
2019-01-15 03:16:44 +00:00
|
|
|
iOffset := config.GetInfoBarOffset()
|
2019-01-10 04:44:53 +00:00
|
|
|
b := buffer.NewBufferFromString("", "", buffer.BTDefault)
|
2019-01-15 03:16:44 +00:00
|
|
|
tp := NewTabFromBuffer(0, 0, width, height-iOffset, b)
|
2019-01-10 04:44:53 +00:00
|
|
|
Tabs.AddTab(tp)
|
|
|
|
|
Tabs.SetActive(len(Tabs.List) - 1)
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PreviousTab switches to the previous tab in the tab list
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PreviousTab() bool {
|
2024-07-24 09:16:34 +00:00
|
|
|
if Tabs.Active() == 0 {
|
2024-06-15 18:30:05 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2024-07-24 09:16:34 +00:00
|
|
|
Tabs.SetActive(Tabs.Active() - 1)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NextTab switches to the next tab in the tab list
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) NextTab() bool {
|
2024-07-24 09:16:34 +00:00
|
|
|
if Tabs.Active() == len(Tabs.List)-1 {
|
2024-06-15 18:30:05 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2024-07-24 09:16:34 +00:00
|
|
|
Tabs.SetActive(Tabs.Active() + 1)
|
|
|
|
|
return true
|
|
|
|
|
}
|
2024-06-15 18:30:05 +00:00
|
|
|
|
2024-07-24 09:16:34 +00:00
|
|
|
// FirstTab switches to the first tab in the tab list
|
|
|
|
|
func (h *BufPane) FirstTab() bool {
|
|
|
|
|
if Tabs.Active() == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
Tabs.SetActive(0)
|
|
|
|
|
return true
|
|
|
|
|
}
|
2020-04-17 17:42:48 +00:00
|
|
|
|
2024-07-24 09:16:34 +00:00
|
|
|
// LastTab switches to the last tab in the tab list
|
|
|
|
|
func (h *BufPane) LastTab() bool {
|
|
|
|
|
lastTabIndex := len(Tabs.List) - 1
|
|
|
|
|
if Tabs.Active() == lastTabIndex {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
Tabs.SetActive(lastTabIndex)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-13 23:18:23 +00:00
|
|
|
// VSplitAction opens an empty vertical split
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) VSplitAction() bool {
|
2019-01-13 23:18:23 +00:00
|
|
|
h.VSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
|
2019-01-10 04:44:53 +00:00
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-13 23:18:23 +00:00
|
|
|
// HSplitAction opens an empty horizontal split
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) HSplitAction() bool {
|
2019-01-13 23:18:23 +00:00
|
|
|
h.HSplitBuf(buffer.NewBufferFromString("", "", buffer.BTDefault))
|
2019-01-10 04:44:53 +00:00
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unsplit closes all splits in the current tab except the active one
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) Unsplit() bool {
|
2020-02-09 02:06:13 +00:00
|
|
|
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)
|
2019-01-09 21:55:00 +00:00
|
|
|
|
2020-02-09 02:06:13 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NextSplit changes the view to the next split
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) NextSplit() bool {
|
2024-07-24 09:16:34 +00:00
|
|
|
if h.tab.active == len(h.tab.Panes)-1 {
|
2024-06-15 18:30:05 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2024-07-24 09:16:34 +00:00
|
|
|
h.tab.SetActive(h.tab.active + 1)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PreviousSplit changes the view to the previous split
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PreviousSplit() bool {
|
2024-07-24 09:16:34 +00:00
|
|
|
if h.tab.active == 0 {
|
2024-06-15 18:30:05 +00:00
|
|
|
return false
|
|
|
|
|
}
|
2024-07-24 09:16:34 +00:00
|
|
|
h.tab.SetActive(h.tab.active - 1)
|
|
|
|
|
return true
|
|
|
|
|
}
|
2024-06-15 18:30:05 +00:00
|
|
|
|
2024-07-24 09:16:34 +00:00
|
|
|
// FirstSplit changes the view to the first split
|
|
|
|
|
func (h *BufPane) FirstSplit() bool {
|
|
|
|
|
if h.tab.active == 0 {
|
|
|
|
|
return false
|
2019-01-14 05:18:49 +00:00
|
|
|
}
|
2024-07-24 09:16:34 +00:00
|
|
|
h.tab.SetActive(0)
|
|
|
|
|
return true
|
|
|
|
|
}
|
2019-01-10 04:44:53 +00:00
|
|
|
|
2024-07-24 09:16:34 +00:00
|
|
|
// LastSplit changes the view to the last split
|
|
|
|
|
func (h *BufPane) LastSplit() bool {
|
|
|
|
|
lastPaneIdx := len(h.tab.Panes) - 1
|
|
|
|
|
if h.tab.active == lastPaneIdx {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
h.tab.SetActive(lastPaneIdx)
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2019-08-05 03:23:32 +00:00
|
|
|
var curmacro []interface{}
|
2021-08-21 22:04:08 +00:00
|
|
|
var recordingMacro bool
|
2018-08-27 19:53:10 +00:00
|
|
|
|
|
|
|
|
// ToggleMacro toggles recording of a macro
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) ToggleMacro() bool {
|
2021-08-21 22:04:08 +00:00
|
|
|
recordingMacro = !recordingMacro
|
|
|
|
|
if recordingMacro {
|
2019-08-05 03:23:32 +00:00
|
|
|
curmacro = []interface{}{}
|
|
|
|
|
InfoBar.Message("Recording")
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("Stopped recording")
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PlayMacro plays back the most recently recorded macro
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) PlayMacro() bool {
|
2021-08-21 22:04:08 +00:00
|
|
|
if recordingMacro {
|
2019-08-05 03:23:32 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, action := range curmacro {
|
|
|
|
|
switch t := action.(type) {
|
|
|
|
|
case rune:
|
|
|
|
|
h.DoRuneInsert(t)
|
2024-03-14 02:52:52 +00:00
|
|
|
case BufKeyAction:
|
2019-12-25 18:11:38 +00:00
|
|
|
t(h)
|
2019-08-05 03:23:32 +00:00
|
|
|
}
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2018-08-27 19:53:10 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SpawnMultiCursor creates a new multiple cursor at the next occurrence of the current selection or current word
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SpawnMultiCursor() bool {
|
2019-01-03 20:59:26 +00:00
|
|
|
spawner := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
|
|
|
|
if !spawner.HasSelection() {
|
|
|
|
|
spawner.SelectWord()
|
2019-01-05 02:48:19 +00:00
|
|
|
h.multiWord = true
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2019-01-05 02:48:19 +00:00
|
|
|
return true
|
|
|
|
|
}
|
2019-01-03 20:59:26 +00:00
|
|
|
|
2019-01-05 02:48:19 +00:00
|
|
|
sel := spawner.GetSelection()
|
|
|
|
|
searchStart := spawner.CurSelection[1]
|
2019-01-03 20:59:26 +00:00
|
|
|
|
2019-01-05 02:48:19 +00:00
|
|
|
search := string(sel)
|
2019-01-16 22:52:30 +00:00
|
|
|
search = regexp.QuoteMeta(search)
|
2019-01-05 02:48:19 +00:00
|
|
|
if h.multiWord {
|
|
|
|
|
search = "\\b" + search + "\\b"
|
|
|
|
|
}
|
2019-01-16 22:52:30 +00:00
|
|
|
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, true, true)
|
2019-01-05 02:48:19 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
|
|
|
|
}
|
|
|
|
|
if found {
|
|
|
|
|
c := buffer.NewCursor(h.Buf, buffer.Loc{})
|
|
|
|
|
c.SetSelectionStart(match[0])
|
|
|
|
|
c.SetSelectionEnd(match[1])
|
|
|
|
|
c.OrigSelection[0] = c.CurSelection[0]
|
|
|
|
|
c.OrigSelection[1] = c.CurSelection[1]
|
|
|
|
|
c.Loc = c.CurSelection[1]
|
|
|
|
|
|
|
|
|
|
h.Buf.AddCursor(c)
|
2019-01-16 22:52:30 +00:00
|
|
|
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
2019-01-05 02:48:19 +00:00
|
|
|
h.Buf.MergeCursors()
|
|
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("No matches found")
|
2019-01-03 20:59:26 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2019-01-05 02:48:19 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-31 10:42:55 +00:00
|
|
|
// SpawnCursorAtLoc spawns a new cursor at a location and merges the cursors
|
|
|
|
|
func (h *BufPane) SpawnCursorAtLoc(loc buffer.Loc) *buffer.Cursor {
|
|
|
|
|
c := buffer.NewCursor(h.Buf, loc)
|
|
|
|
|
h.Buf.AddCursor(c)
|
|
|
|
|
h.Buf.MergeCursors()
|
|
|
|
|
return c
|
|
|
|
|
}
|
|
|
|
|
|
Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors
SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.
This trick causes at least the following issues:
- When the line above or below, where we spawn a new cursor, is shorter
than the current cursor position in the current line, the new cursor
is placed at the end of this short line (which is expected), but also
the current cursor unexpectedly changes its x position and moves
below/above the new cursor.
- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
non-intuitively removes the cursor which, from the user point of view,
is not the last but the last-but-one cursor.
Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.
Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.
* SpawnMultiCursorUp/Down: move common code to a helper fn
* SpawnMultiCursorUp/Down: honor visual width and LastVisualX
Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:
- SpawnMultiCursorUp/Down doesn't take into account the visual width of
the text before the cursor, which may be different from its character
width (e.g. if it contains tabs). So e.g. if the number of tabs before
the cursor in the current line is not the same as in the new line, the
new cursor is placed at an unexpected location.
- SpawnMultiCursorUp/Down doesn't take into account the cursor's
remembered x position (LastVisualX) when e.g. spawning a new cursor
in the below line which is short than the current cursor position, and
then spawning yet another cursor in the next below line which is
longer than this short line.
* SpawnMultiCursorUp/Down: honor softwrap
When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.
* SpawnMultiCursorUp/Down: deselect when spawning cursors
To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 21:23:50 +00:00
|
|
|
// SpawnMultiCursorUpN is not an action
|
|
|
|
|
func (h *BufPane) SpawnMultiCursorUpN(n int) bool {
|
|
|
|
|
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
2024-10-13 21:20:57 +00:00
|
|
|
if n > 0 && lastC.Y == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if n < 0 && lastC.Y+1 == h.Buf.LinesNum() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors
SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.
This trick causes at least the following issues:
- When the line above or below, where we spawn a new cursor, is shorter
than the current cursor position in the current line, the new cursor
is placed at the end of this short line (which is expected), but also
the current cursor unexpectedly changes its x position and moves
below/above the new cursor.
- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
non-intuitively removes the cursor which, from the user point of view,
is not the last but the last-but-one cursor.
Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.
Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.
* SpawnMultiCursorUp/Down: move common code to a helper fn
* SpawnMultiCursorUp/Down: honor visual width and LastVisualX
Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:
- SpawnMultiCursorUp/Down doesn't take into account the visual width of
the text before the cursor, which may be different from its character
width (e.g. if it contains tabs). So e.g. if the number of tabs before
the cursor in the current line is not the same as in the new line, the
new cursor is placed at an unexpected location.
- SpawnMultiCursorUp/Down doesn't take into account the cursor's
remembered x position (LastVisualX) when e.g. spawning a new cursor
in the below line which is short than the current cursor position, and
then spawning yet another cursor in the next below line which is
longer than this short line.
* SpawnMultiCursorUp/Down: honor softwrap
When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.
* SpawnMultiCursorUp/Down: deselect when spawning cursors
To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 21:23:50 +00:00
|
|
|
|
2024-10-13 21:20:57 +00:00
|
|
|
h.Buf.DeselectCursors()
|
Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors
SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.
This trick causes at least the following issues:
- When the line above or below, where we spawn a new cursor, is shorter
than the current cursor position in the current line, the new cursor
is placed at the end of this short line (which is expected), but also
the current cursor unexpectedly changes its x position and moves
below/above the new cursor.
- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
non-intuitively removes the cursor which, from the user point of view,
is not the last but the last-but-one cursor.
Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.
Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.
* SpawnMultiCursorUp/Down: move common code to a helper fn
* SpawnMultiCursorUp/Down: honor visual width and LastVisualX
Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:
- SpawnMultiCursorUp/Down doesn't take into account the visual width of
the text before the cursor, which may be different from its character
width (e.g. if it contains tabs). So e.g. if the number of tabs before
the cursor in the current line is not the same as in the new line, the
new cursor is placed at an unexpected location.
- SpawnMultiCursorUp/Down doesn't take into account the cursor's
remembered x position (LastVisualX) when e.g. spawning a new cursor
in the below line which is short than the current cursor position, and
then spawning yet another cursor in the next below line which is
longer than this short line.
* SpawnMultiCursorUp/Down: honor softwrap
When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.
* SpawnMultiCursorUp/Down: deselect when spawning cursors
To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 21:23:50 +00:00
|
|
|
|
2024-10-13 21:20:57 +00:00
|
|
|
c := buffer.NewCursor(h.Buf, buffer.Loc{lastC.X, lastC.Y - n})
|
|
|
|
|
c.LastVisualX = lastC.LastVisualX
|
|
|
|
|
c.LastWrappedVisualX = lastC.LastWrappedVisualX
|
|
|
|
|
c.X = c.GetCharPosInLine(h.Buf.LineBytes(c.Y), c.LastVisualX)
|
|
|
|
|
c.Relocate()
|
2020-02-08 00:37:56 +00:00
|
|
|
|
|
|
|
|
h.Buf.AddCursor(c)
|
|
|
|
|
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
|
|
|
|
h.Buf.MergeCursors()
|
|
|
|
|
|
|
|
|
|
h.Relocate()
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors
SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.
This trick causes at least the following issues:
- When the line above or below, where we spawn a new cursor, is shorter
than the current cursor position in the current line, the new cursor
is placed at the end of this short line (which is expected), but also
the current cursor unexpectedly changes its x position and moves
below/above the new cursor.
- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
non-intuitively removes the cursor which, from the user point of view,
is not the last but the last-but-one cursor.
Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.
Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.
* SpawnMultiCursorUp/Down: move common code to a helper fn
* SpawnMultiCursorUp/Down: honor visual width and LastVisualX
Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:
- SpawnMultiCursorUp/Down doesn't take into account the visual width of
the text before the cursor, which may be different from its character
width (e.g. if it contains tabs). So e.g. if the number of tabs before
the cursor in the current line is not the same as in the new line, the
new cursor is placed at an unexpected location.
- SpawnMultiCursorUp/Down doesn't take into account the cursor's
remembered x position (LastVisualX) when e.g. spawning a new cursor
in the below line which is short than the current cursor position, and
then spawning yet another cursor in the next below line which is
longer than this short line.
* SpawnMultiCursorUp/Down: honor softwrap
When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.
* SpawnMultiCursorUp/Down: deselect when spawning cursors
To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 21:23:50 +00:00
|
|
|
// SpawnMultiCursorUp creates additional cursor, at the same X (if possible), one Y less.
|
|
|
|
|
func (h *BufPane) SpawnMultiCursorUp() bool {
|
|
|
|
|
return h.SpawnMultiCursorUpN(1)
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-13 16:04:10 +00:00
|
|
|
// SpawnMultiCursorDown creates additional cursor, at the same X (if possible), one Y more.
|
2020-02-08 00:37:56 +00:00
|
|
|
func (h *BufPane) SpawnMultiCursorDown() bool {
|
Fix various issues with `SpawnMultiCursor{Up,Down}` (#3145)
* SpawnMultiCursorUp/Down: change order of adding cursors
SpawnMultiCursor{Up,Down} currently works in a tricky way: instead of
creating a new cursor above or below, it moves the current "primary"
cursor above or below, and then creates a new cursor below or above the
new position of the current cursor (i.e. at its previous position),
creating an illusion for the user that the current (top-most or
bottom-most) cursor is a newly spawned cursor.
This trick causes at least the following issues:
- When the line above or below, where we spawn a new cursor, is shorter
than the current cursor position in the current line, the new cursor
is placed at the end of this short line (which is expected), but also
the current cursor unexpectedly changes its x position and moves
below/above the new cursor.
- When removing a cursor in RemoveMultiCursor (default Alt-p key), it
non-intuitively removes the cursor which, from the user point of view,
is not the last but the last-but-one cursor.
Fix these issues by replacing the trick with a straightforward logic:
just create the new cursor above or below the last one.
Note that this fix has a user-visible side effect: the last cursor is
no longer the "primary" one (since it is now the last in the list, not
the first), so e.g. when the user clears multicursors via Esc key, the
remaining cursor is the first one, not the last one. I assume it's ok.
* SpawnMultiCursorUp/Down: move common code to a helper fn
* SpawnMultiCursorUp/Down: honor visual width and LastVisualX
Make spawning multicursors up/down behave more similarly to cursor
movements up/down. This change fixes 2 issues at once:
- SpawnMultiCursorUp/Down doesn't take into account the visual width of
the text before the cursor, which may be different from its character
width (e.g. if it contains tabs). So e.g. if the number of tabs before
the cursor in the current line is not the same as in the new line, the
new cursor is placed at an unexpected location.
- SpawnMultiCursorUp/Down doesn't take into account the cursor's
remembered x position (LastVisualX) when e.g. spawning a new cursor
in the below line which is short than the current cursor position, and
then spawning yet another cursor in the next below line which is
longer than this short line.
* SpawnMultiCursorUp/Down: honor softwrap
When softwrap is enabled and the current line is wrapped, make
SpawnMultiCursor{Up,Down} spawn cursor in the next visual line within
this wrapped line, similarly to how we handle cursor movements up/down
within wrapped lines.
* SpawnMultiCursorUp/Down: deselect when spawning cursors
To avoid weird user experience (spawned cursors messing with selections
of existing cursors).
2024-03-04 21:23:50 +00:00
|
|
|
return h.SpawnMultiCursorUpN(-1)
|
2020-02-08 00:37:56 +00:00
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// SpawnMultiCursorSelect adds a cursor at the beginning of each line of a selection
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) SpawnMultiCursorSelect() bool {
|
2019-01-03 20:59:26 +00:00
|
|
|
// Avoid cases where multiple cursors already exist, that would create problems
|
|
|
|
|
if h.Buf.NumCursors() > 1 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var startLine int
|
|
|
|
|
var endLine int
|
|
|
|
|
|
|
|
|
|
a, b := h.Cursor.CurSelection[0].Y, h.Cursor.CurSelection[1].Y
|
|
|
|
|
if a > b {
|
|
|
|
|
startLine, endLine = b, a
|
|
|
|
|
} else {
|
|
|
|
|
startLine, endLine = a, b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if h.Cursor.HasSelection() {
|
|
|
|
|
h.Cursor.ResetSelection()
|
|
|
|
|
h.Cursor.GotoLoc(buffer.Loc{0, startLine})
|
|
|
|
|
|
|
|
|
|
for i := startLine; i <= endLine; i++ {
|
|
|
|
|
c := buffer.NewCursor(h.Buf, buffer.Loc{0, i})
|
|
|
|
|
c.StoreVisualX()
|
|
|
|
|
h.Buf.AddCursor(c)
|
|
|
|
|
}
|
|
|
|
|
h.Buf.MergeCursors()
|
|
|
|
|
} else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
InfoBar.Message("Added cursors from selection")
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-04 21:21:50 +00:00
|
|
|
// MouseMultiCursor is a mouse action which puts a new cursor at the mouse position,
|
|
|
|
|
// or removes a cursor if it is already there
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) MouseMultiCursor(e *tcell.EventMouse) bool {
|
2019-01-03 04:26:40 +00:00
|
|
|
b := h.Buf
|
|
|
|
|
mx, my := e.Position()
|
2022-10-25 21:31:50 +00:00
|
|
|
// ignore click on the status line
|
|
|
|
|
if my >= h.BufView().Y+h.BufView().Height {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-12-24 21:01:08 +00:00
|
|
|
mouseLoc := h.LocFromVisual(buffer.Loc{X: mx, Y: my})
|
2024-03-04 21:21:50 +00:00
|
|
|
|
|
|
|
|
if h.Buf.NumCursors() > 1 {
|
|
|
|
|
cursors := h.Buf.GetCursors()
|
|
|
|
|
for _, c := range cursors {
|
|
|
|
|
if c.Loc == mouseLoc {
|
|
|
|
|
h.Buf.RemoveCursor(c.Num)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-03 04:26:40 +00:00
|
|
|
c := buffer.NewCursor(b, mouseLoc)
|
|
|
|
|
b.AddCursor(c)
|
|
|
|
|
b.MergeCursors()
|
|
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-16 20:20:12 +00:00
|
|
|
func (h *BufPane) skipMultiCursor(forward bool) bool {
|
2019-01-03 20:59:26 +00:00
|
|
|
lastC := h.Buf.GetCursor(h.Buf.NumCursors() - 1)
|
2024-06-15 18:32:16 +00:00
|
|
|
if !lastC.HasSelection() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-01-03 20:59:26 +00:00
|
|
|
sel := lastC.GetSelection()
|
|
|
|
|
searchStart := lastC.CurSelection[1]
|
2024-09-16 20:20:12 +00:00
|
|
|
if !forward {
|
|
|
|
|
searchStart = lastC.CurSelection[0]
|
|
|
|
|
}
|
2019-01-03 20:59:26 +00:00
|
|
|
|
2019-01-16 22:52:30 +00:00
|
|
|
search := string(sel)
|
|
|
|
|
search = regexp.QuoteMeta(search)
|
|
|
|
|
if h.multiWord {
|
|
|
|
|
search = "\\b" + search + "\\b"
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 20:20:12 +00:00
|
|
|
match, found, err := h.Buf.FindNext(search, h.Buf.Start(), h.Buf.End(), searchStart, forward, true)
|
2019-01-03 20:59:26 +00:00
|
|
|
if err != nil {
|
|
|
|
|
InfoBar.Error(err)
|
|
|
|
|
}
|
|
|
|
|
if found {
|
|
|
|
|
lastC.SetSelectionStart(match[0])
|
|
|
|
|
lastC.SetSelectionEnd(match[1])
|
|
|
|
|
lastC.OrigSelection[0] = lastC.CurSelection[0]
|
|
|
|
|
lastC.OrigSelection[1] = lastC.CurSelection[1]
|
|
|
|
|
lastC.Loc = lastC.CurSelection[1]
|
|
|
|
|
|
|
|
|
|
h.Buf.MergeCursors()
|
2019-01-16 22:52:30 +00:00
|
|
|
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
2019-01-03 20:59:26 +00:00
|
|
|
} else {
|
|
|
|
|
InfoBar.Message("No matches found")
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2019-01-16 22:52:30 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-16 20:20:12 +00:00
|
|
|
// SkipMultiCursor moves the current multiple cursor to the next available position
|
|
|
|
|
func (h *BufPane) SkipMultiCursor() bool {
|
|
|
|
|
return h.skipMultiCursor(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SkipMultiCursorBack moves the current multiple cursor to the previous available position
|
|
|
|
|
func (h *BufPane) SkipMultiCursorBack() bool {
|
|
|
|
|
return h.skipMultiCursor(false)
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-27 19:53:10 +00:00
|
|
|
// RemoveMultiCursor removes the latest multiple cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) RemoveMultiCursor() bool {
|
2019-01-03 04:26:40 +00:00
|
|
|
if h.Buf.NumCursors() > 1 {
|
|
|
|
|
h.Buf.RemoveCursor(h.Buf.NumCursors() - 1)
|
2019-01-16 22:52:30 +00:00
|
|
|
h.Buf.SetCurCursor(h.Buf.NumCursors() - 1)
|
2019-01-03 04:26:40 +00:00
|
|
|
h.Buf.UpdateCursors()
|
2024-06-15 15:28:29 +00:00
|
|
|
} else if h.multiWord {
|
2019-01-05 02:48:19 +00:00
|
|
|
h.multiWord = false
|
2024-06-15 18:35:55 +00:00
|
|
|
h.Cursor.Deselect(true)
|
2024-06-15 15:28:29 +00:00
|
|
|
} else {
|
|
|
|
|
return false
|
2019-01-03 04:26:40 +00:00
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2019-01-16 22:52:30 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RemoveAllMultiCursors removes all cursors except the base cursor
|
2019-01-19 20:37:59 +00:00
|
|
|
func (h *BufPane) RemoveAllMultiCursors() bool {
|
2024-06-15 15:28:29 +00:00
|
|
|
if h.Buf.NumCursors() > 1 || h.multiWord {
|
|
|
|
|
h.Buf.ClearCursors()
|
|
|
|
|
h.multiWord = false
|
|
|
|
|
} else {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2019-12-25 17:54:51 +00:00
|
|
|
h.Relocate()
|
2019-01-03 04:26:40 +00:00
|
|
|
return true
|
2018-08-27 19:53:10 +00:00
|
|
|
}
|
2019-09-02 18:40:50 +00:00
|
|
|
|
2019-12-25 17:54:51 +00:00
|
|
|
// None is an action that does nothing
|
2019-09-02 18:40:50 +00:00
|
|
|
func (h *BufPane) None() bool {
|
2019-12-25 17:54:51 +00:00
|
|
|
return true
|
2019-09-02 18:40:50 +00:00
|
|
|
}
|