From 28e0e206511bb24c018bdd063130471340043f8d Mon Sep 17 00:00:00 2001 From: Dmitry Maluka Date: Sat, 3 Dec 2022 04:38:09 +0100 Subject: [PATCH] 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. --- internal/action/actions.go | 17 +++------ internal/action/bufpane.go | 75 +++++++++++++++++++++++++++++++++++--- internal/action/command.go | 9 ++--- 3 files changed, 79 insertions(+), 22 deletions(-) diff --git a/internal/action/actions.go b/internal/action/actions.go index 345f47c6..dd92a364 100644 --- a/internal/action/actions.go +++ b/internal/action/actions.go @@ -879,11 +879,10 @@ func (h *BufPane) Search(str string, useRegex bool, searchDown bool) error { h.Cursor.SetSelectionEnd(match[1]) h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] - h.Cursor.GotoLoc(h.Cursor.CurSelection[1]) + h.GotoLoc(h.Cursor.CurSelection[1]) h.Buf.LastSearch = str h.Buf.LastSearchRegex = useRegex h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool) - h.Relocate() } else { h.Cursor.ResetSelection() } @@ -905,12 +904,11 @@ func (h *BufPane) find(useRegex bool) bool { h.Cursor.SetSelectionEnd(match[1]) h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] - h.Cursor.GotoLoc(match[1]) + h.GotoLoc(match[1]) } else { - h.Cursor.GotoLoc(h.searchOrig) + h.GotoLoc(h.searchOrig) h.Cursor.ResetSelection() } - h.Relocate() } } findCallback := func(resp string, canceled bool) { @@ -925,7 +923,7 @@ func (h *BufPane) find(useRegex bool) bool { h.Cursor.SetSelectionEnd(match[1]) h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] - h.Cursor.GotoLoc(h.Cursor.CurSelection[1]) + h.GotoLoc(h.Cursor.CurSelection[1]) h.Buf.LastSearch = resp h.Buf.LastSearchRegex = useRegex h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool) @@ -936,7 +934,6 @@ func (h *BufPane) find(useRegex bool) bool { } else { h.Cursor.ResetSelection() } - h.Relocate() } pattern := string(h.Cursor.GetSelection()) if eventCallback != nil && pattern != "" { @@ -980,11 +977,10 @@ func (h *BufPane) FindNext() bool { h.Cursor.SetSelectionEnd(match[1]) h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] - h.Cursor.Loc = h.Cursor.CurSelection[1] + h.GotoLoc(h.Cursor.CurSelection[1]) } else { h.Cursor.ResetSelection() } - h.Relocate() return true } @@ -1007,11 +1003,10 @@ func (h *BufPane) FindPrevious() bool { h.Cursor.SetSelectionEnd(match[1]) h.Cursor.OrigSelection[0] = h.Cursor.CurSelection[0] h.Cursor.OrigSelection[1] = h.Cursor.CurSelection[1] - h.Cursor.Loc = h.Cursor.CurSelection[1] + h.GotoLoc(h.Cursor.CurSelection[1]) } else { h.Cursor.ResetSelection() } - h.Relocate() return true } diff --git a/internal/action/bufpane.go b/internal/action/bufpane.go index 583e46ce..847f5641 100644 --- a/internal/action/bufpane.go +++ b/internal/action/bufpane.go @@ -13,6 +13,7 @@ import ( "github.com/zyedidia/micro/v2/internal/display" ulua "github.com/zyedidia/micro/v2/internal/lua" "github.com/zyedidia/micro/v2/internal/screen" + "github.com/zyedidia/micro/v2/internal/util" "github.com/zyedidia/tcell/v2" ) @@ -235,10 +236,14 @@ type BufPane struct { // remember original location of a search in case the search is canceled searchOrig buffer.Loc + + // The pane may not yet be fully initialized after its creation + // since we may not know the window geometry yet. In such case we finish + // its initialization a bit later, after the initial resize. + initialized bool } -// NewBufPane creates a new buffer pane with the given window. -func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { +func newBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h := new(BufPane) h.Buf = buf h.BWindow = win @@ -247,8 +252,13 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { h.Cursor = h.Buf.GetActiveCursor() h.mouseReleased = true - config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h)) + return h +} +// NewBufPane creates a new buffer pane with the given window. +func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { + h := newBufPane(buf, win, tab) + h.finishInitialize() return h } @@ -256,7 +266,25 @@ func NewBufPane(buf *buffer.Buffer, win display.BWindow, tab *Tab) *BufPane { // creates a buf window. func NewBufPaneFromBuf(buf *buffer.Buffer, tab *Tab) *BufPane { w := display.NewBufWindow(0, 0, 0, 0, buf) - return NewBufPane(buf, w, tab) + h := newBufPane(buf, w, tab) + // Postpone finishing initializing the pane until we know the actual geometry + // of the buf window. + return h +} + +// TODO: make sure splitID and tab are set before finishInitialize is called +func (h *BufPane) finishInitialize() { + h.initialRelocate() + h.initialized = true + config.RunPluginFn("onBufPaneOpen", luar.New(ulua.L, h)) +} + +// Resize resizes the pane +func (h *BufPane) Resize(width, height int) { + h.BWindow.Resize(width, height) + if !h.initialized { + h.finishInitialize() + } } // SetTab sets this pane's tab. @@ -301,7 +329,7 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.BWindow.SetBuffer(b) h.Cursor = b.GetActiveCursor() h.Resize(h.GetView().Width, h.GetView().Height) - h.Relocate() + h.initialRelocate() // Set mouseReleased to true because we assume the mouse is not being // pressed when the editor is opened h.mouseReleased = true @@ -311,6 +339,43 @@ func (h *BufPane) OpenBuffer(b *buffer.Buffer) { h.lastClickTime = time.Time{} } +// GotoLoc moves the cursor to a new location and adjusts the view accordingly. +// Use GotoLoc when the new location may be far away from the current location. +func (h *BufPane) GotoLoc(loc buffer.Loc) { + sloc := h.SLocFromLoc(loc) + d := h.Diff(h.SLocFromLoc(h.Cursor.Loc), sloc) + + h.Cursor.GotoLoc(loc) + + // If the new location is far away from the previous one, + // ensure the cursor is at 25% of the window height + height := h.BufView().Height + if util.Abs(d) >= height { + v := h.GetView() + v.StartLine = h.Scroll(sloc, -height/4) + h.ScrollAdjust() + v.StartCol = 0 + } + h.Relocate() +} + +func (h *BufPane) initialRelocate() { + sloc := h.SLocFromLoc(h.Cursor.Loc) + height := h.BufView().Height + + // If the initial cursor location is far away from the beginning + // of the buffer, ensure the cursor is at 25% of the window height + v := h.GetView() + if h.Diff(display.SLoc{0, 0}, sloc) < height { + v.StartLine = display.SLoc{0, 0} + } else { + v.StartLine = h.Scroll(sloc, -height/4) + h.ScrollAdjust() + } + v.StartCol = 0 + h.Relocate() +} + // ID returns this pane's split id. func (h *BufPane) ID() uint64 { return h.splitID diff --git a/internal/action/command.go b/internal/action/command.go index a8a91886..16d2fff8 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -722,7 +722,7 @@ func (h *BufPane) GotoCmd(args []string) { } line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1) col = util.Clamp(col-1, 0, util.CharacterCount(h.Buf.LineBytes(line))) - h.Cursor.GotoLoc(buffer.Loc{col, line}) + h.GotoLoc(buffer.Loc{col, line}) } else { line, err := strconv.Atoi(args[0]) if err != nil { @@ -733,9 +733,8 @@ func (h *BufPane) GotoCmd(args []string) { line = h.Buf.LinesNum() + 1 + line } line = util.Clamp(line-1, 0, h.Buf.LinesNum()-1) - h.Cursor.GotoLoc(buffer.Loc{0, line}) + h.GotoLoc(buffer.Loc{0, line}) } - h.Relocate() } } @@ -834,13 +833,11 @@ func (h *BufPane) ReplaceCmd(args []string) { h.Cursor.SetSelectionStart(locs[0]) h.Cursor.SetSelectionEnd(locs[1]) - h.Cursor.GotoLoc(locs[0]) + h.GotoLoc(locs[0]) h.Buf.LastSearch = search h.Buf.LastSearchRegex = true h.Buf.HighlightSearch = h.Buf.Settings["hlsearch"].(bool) - h.Relocate() - InfoBar.YNPrompt("Perform replacement (y,n,esc)", func(yes, canceled bool) { if !canceled && yes { _, nrunes := h.Buf.ReplaceRegex(locs[0], locs[1], regex, replace)