From 6d13710d934dc1bfd804246c255bdce3e42fdc95 Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sat, 6 Mar 2021 23:43:36 +0100 Subject: [PATCH] Implement moving cursor up/down within a wrapped line Modified behavior of CursorUp, CursorDown, CursorPageUp etc: if softwrap is enabled, cursor moves by visual lines, not logical lines. TODO: implement it also for Home and End keys: move cursor to the visual start or end of a line. I haven't implemented it for now, because I'm not sure what should be the behavior of StartOfTextToggle then (considering that Home key is bound to StartOfTextToggle by default). Fixes #1598 --- internal/action/actions.go | 55 ++++++++++++++++++++++++++++++----- internal/buffer/buffer.go | 6 ++++ internal/buffer/cursor.go | 4 +++ internal/display/bufwindow.go | 15 ++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index c0334a6a..f25ad04e 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -123,10 +123,49 @@ func (h *BufPane) Center() bool { return true } +// MoveCursorUp is not an action +func (h *BufPane) MoveCursorUp(n int) { + if !h.Buf.Settings["softwrap"].(bool) { + h.Cursor.UpN(n) + } else { + vloc := h.VLocFromLoc(h.Cursor.Loc) + sloc := h.Scroll(vloc.SLoc, -n) + if sloc == vloc.SLoc { + // we are at the beginning of buffer + h.Cursor.Loc = h.Buf.Start() + h.Cursor.LastVisualX = 0 + } else { + vloc.SLoc = sloc + vloc.VisualX = h.Cursor.LastVisualX + h.Cursor.Loc = h.LocFromVLoc(vloc) + } + } +} + +// MoveCursorDown is not an action +func (h *BufPane) MoveCursorDown(n int) { + if !h.Buf.Settings["softwrap"].(bool) { + h.Cursor.DownN(n) + } else { + vloc := h.VLocFromLoc(h.Cursor.Loc) + sloc := h.Scroll(vloc.SLoc, n) + if sloc == vloc.SLoc { + // we are at the end of buffer + h.Cursor.Loc = h.Buf.End() + vloc = h.VLocFromLoc(h.Cursor.Loc) + h.Cursor.LastVisualX = vloc.VisualX + } else { + vloc.SLoc = sloc + vloc.VisualX = h.Cursor.LastVisualX + h.Cursor.Loc = h.LocFromVLoc(vloc) + } + } +} + // CursorUp moves the cursor up func (h *BufPane) CursorUp() bool { h.Cursor.Deselect(true) - h.Cursor.Up() + h.MoveCursorUp(1) h.Relocate() return true } @@ -134,7 +173,7 @@ func (h *BufPane) CursorUp() bool { // CursorDown moves the cursor down func (h *BufPane) CursorDown() bool { h.Cursor.Deselect(true) - h.Cursor.Down() + h.MoveCursorDown(1) h.Relocate() return true } @@ -212,7 +251,7 @@ func (h *BufPane) SelectUp() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.Up() + h.MoveCursorUp(1) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -223,7 +262,7 @@ func (h *BufPane) SelectDown() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.Down() + h.MoveCursorDown(1) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -1274,7 +1313,7 @@ func (h *BufPane) SelectPageUp() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.UpN(h.BufHeight()) + h.MoveCursorUp(h.BufHeight()) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -1285,7 +1324,7 @@ func (h *BufPane) SelectPageDown() bool { if !h.Cursor.HasSelection() { h.Cursor.OrigSelection[0] = h.Cursor.Loc } - h.Cursor.DownN(h.BufHeight()) + h.MoveCursorDown(h.BufHeight()) h.Cursor.SelectTo(h.Cursor.Loc) h.Relocate() return true @@ -1300,7 +1339,7 @@ func (h *BufPane) CursorPageUp() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.UpN(h.BufHeight()) + h.MoveCursorUp(h.BufHeight()) h.Relocate() return true } @@ -1314,7 +1353,7 @@ func (h *BufPane) CursorPageDown() bool { h.Cursor.ResetSelection() h.Cursor.StoreVisualX() } - h.Cursor.DownN(h.BufHeight()) + h.MoveCursorDown(h.BufHeight()) h.Relocate() return true } diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 718510dd..034e28e5 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -196,6 +196,12 @@ type Buffer struct { // the buffer module cannot directly call the display's API (it would mean // a circular dependency between packages). OptionCallback func(option string, nativeValue interface{}) + + // The display module registers its own GetVisualX function for getting + // the correct visual x location of a cursor when softwrap is used. + // This is hacky. Maybe it would be better to move all the visual x logic + // from buffer to display, but it would require rewriting a lot of code. + GetVisualX func(loc Loc) int } // NewBufferFromFileAtLoc opens a new buffer with a given cursor location diff --git a/internal/buffer/cursor.go b/internal/buffer/cursor.go index aa3daf02..12fc5db2 100644 --- a/internal/buffer/cursor.go +++ b/internal/buffer/cursor.go @@ -67,6 +67,10 @@ func (c *Cursor) GotoLoc(l Loc) { // GetVisualX returns the x value of the cursor in visual spaces func (c *Cursor) GetVisualX() int { + if c.buf.GetVisualX != nil { + return c.buf.GetVisualX(c.Loc) + } + if c.X <= 0 { c.X = 0 return 0 diff --git a/internal/display/bufwindow.go b/internal/display/bufwindow.go index e493547b..930d9c36 100644 --- a/internal/display/bufwindow.go +++ b/internal/display/bufwindow.go @@ -54,8 +54,15 @@ func (w *BufWindow) SetBuffer(b *buffer.Buffer) { w.StartLine.Row = 0 } w.Relocate() + + for _, c := range w.Buf.GetCursors() { + c.LastVisualX = c.GetVisualX() + } } } + b.GetVisualX = func(loc buffer.Loc) int { + return w.VLocFromLoc(loc).VisualX + } } func (w *BufWindow) GetView() *View { @@ -68,7 +75,15 @@ func (w *BufWindow) SetView(view *View) { func (w *BufWindow) Resize(width, height int) { w.Width, w.Height = width, height + w.updateDisplayInfo() + w.Relocate() + + if w.Buf.Settings["softwrap"].(bool) { + for _, c := range w.Buf.GetCursors() { + c.LastVisualX = c.GetVisualX() + } + } } func (w *BufWindow) SetActive(b bool) {