From fafa4280f58c4c6be3646870f9e7cbb4ada15f0b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 30 Mar 2023 21:51:09 +0200 Subject: [PATCH 1/5] Log memory usage every 10s --- pkg/gui/background.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/gui/background.go b/pkg/gui/background.go index db267c0dcae..061502a43fa 100644 --- a/pkg/gui/background.go +++ b/pkg/gui/background.go @@ -1,6 +1,8 @@ package gui import ( + "fmt" + "runtime" "strings" "time" @@ -46,6 +48,29 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() { refreshInterval) } } + + if self.gui.Config.GetDebug() { + self.goEvery(time.Second*time.Duration(10), self.gui.stopChan, func() error { + formatBytes := func(b uint64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := uint64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) + } + + m := runtime.MemStats{} + runtime.ReadMemStats(&m) + self.gui.c.Log.Infof("Heap memory in use: %s", formatBytes(m.HeapAlloc)) + return nil + }) + } } func (self *BackgroundRoutineMgr) startBackgroundFetch() { From 9eb9b369ffc7c99854e2565da9996c3dcc6116c8 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 23 Jun 2024 11:54:01 +0200 Subject: [PATCH 2/5] Bump gocui --- go.mod | 2 +- go.sum | 4 +- vendor/github.com/jesseduffield/gocui/view.go | 66 +++++++++++++------ vendor/modules.txt | 2 +- 4 files changed, 51 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index e994a67b1e0..4e189992ba6 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20240623092910-a42926c14fc9 + github.com/jesseduffield/gocui v0.3.1-0.20240623095254-05e1204c2454 github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index 4a915678432..19c69818978 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20240623092910-a42926c14fc9 h1:JJ0DrXgAUpGBGV5w8nzrQLMWTgcTvf745IKAk08qjcM= -github.com/jesseduffield/gocui v0.3.1-0.20240623092910-a42926c14fc9/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= +github.com/jesseduffield/gocui v0.3.1-0.20240623095254-05e1204c2454 h1:rTPA5WiPM1SPUA3r2kSb3RiILC93am6irMvOLjO7JNA= +github.com/jesseduffield/gocui v0.3.1-0.20240623095254-05e1204c2454/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 68b0f49c6eb..a32519b801f 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -771,13 +771,14 @@ func (v *View) writeRunes(p []rune) { } v.wx = 0 default: - moveCursor, cells := v.parseInput(r, v.wx, v.wy) + truncateLine, cells := v.parseInput(r, v.wx, v.wy) if cells == nil { continue } v.writeCells(v.wx, v.wy, cells) - if moveCursor { - v.wx += len(cells) + v.wx += len(cells) + if truncateLine { + v.lines[v.wy] = v.lines[v.wy][:v.wx] } } } @@ -800,7 +801,7 @@ func (v *View) writeString(s string) { // contains the processed data. func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { cells := []cell{} - moveCursor := true + truncateLine := false isEscape, err := v.ei.parseOne(ch) if err != nil { @@ -816,18 +817,13 @@ func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { } else { repeatCount := 1 if _, ok := v.ei.instruction.(eraseInLineFromCursor); ok { - // fill rest of line + // truncate line v.ei.instructionRead() - cx := 0 - for _, cell := range v.lines[v.wy] { - cx += runewidth.RuneWidth(cell.chr) - } - repeatCount = v.InnerWidth() - cx - ch = ' ' - moveCursor = false + repeatCount = 0 + truncateLine = true } else if isEscape { // do not output anything - return moveCursor, nil + return truncateLine, nil } else if ch == '\t' { // fill tab-sized space const tabStop = 4 @@ -844,7 +840,7 @@ func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { } } - return moveCursor, cells + return truncateLine, cells } // Read reads data into p from the current reading position set by SetReadPos. @@ -1590,19 +1586,51 @@ func (v *View) ClearTextArea() { _ = v.SetCursor(0, 0) } -// only call this function if you don't care where v.wx and v.wy end up -func (v *View) OverwriteLines(y int, content string) { - v.writeMutex.Lock() - defer v.writeMutex.Unlock() - +func (v *View) overwriteLines(y int, content string) { // break by newline, then for each line, write it, then add that erase command v.wx = 0 v.wy = y lines := strings.Replace(content, "\n", "\x1b[K\n", -1) + // If the last line doesn't end with a linefeed, add the erase command at + // the end too + if !strings.HasSuffix(lines, "\n") { + lines += "\x1b[K" + } v.writeString(lines) } +// only call this function if you don't care where v.wx and v.wy end up +func (v *View) OverwriteLines(y int, content string) { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.overwriteLines(y, content) +} + +// only call this function if you don't care where v.wx and v.wy end up +func (v *View) OverwriteLinesAndClearEverythingElse(y int, content string) { + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.overwriteLines(y, content) + + for i := 0; i < y; i += 1 { + v.lines[i] = nil + } + + for i := v.wy + 1; i < len(v.lines); i += 1 { + v.lines[i] = nil + } +} + +func (v *View) SetContentLineCount(lineCount int) { + if lineCount > 0 { + v.makeWriteable(0, lineCount-1) + } + v.lines = v.lines[:lineCount] +} + func (v *View) ScrollUp(amount int) { if amount > v.oy { amount = v.oy diff --git a/vendor/modules.txt b/vendor/modules.txt index 47793de1eb7..62d47eea4e7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20240623092910-a42926c14fc9 +# github.com/jesseduffield/gocui v0.3.1-0.20240623095254-05e1204c2454 ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 From dd2bffc278dc3879e112caa3effd9c9c426489af Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 6 Jun 2024 16:54:27 +0200 Subject: [PATCH 3/5] Simplify ListContextTrait.FocusLine When refreshViewportOnChange is true, we would refresh the viewport once at the end of FocusLine, and then we would check at the end of AfterLayout if the origin has changed, and refresh again if so. That's unnecessarily complicated, let's just unconditionally refresh at the end of AfterLayout only. --- pkg/gui/context/list_context_trait.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index c0e5ca04fa3..78d524bb22f 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -25,10 +25,9 @@ func (self *ListContextTrait) IsListContext() {} func (self *ListContextTrait) FocusLine() { // Doing this at the end of the layout function because we need the view to be // resized before we focus the line, otherwise if we're in accordion mode - // the view could be squashed and won't how to adjust the cursor/origin + // the view could be squashed and won't how to adjust the cursor/origin. + // Also, refreshing the viewport needs to happen after the view has been resized. self.c.AfterLayout(func() error { - oldOrigin, _ := self.GetViewTrait().ViewPortYBounds() - self.GetViewTrait().FocusPoint( self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx())) @@ -40,22 +39,13 @@ func (self *ListContextTrait) FocusLine() { self.GetViewTrait().CancelRangeSelect() } - // If FocusPoint() caused the view to scroll (because the selected line - // was out of view before), we need to rerender the view port again. - // This can happen when pressing , or . to scroll by pages, or < or > to - // jump to the top or bottom. - newOrigin, _ := self.GetViewTrait().ViewPortYBounds() - if self.refreshViewportOnChange && oldOrigin != newOrigin { + if self.refreshViewportOnChange { self.refreshViewport() } return nil }) self.setFooter() - - if self.refreshViewportOnChange { - self.refreshViewport() - } } func (self *ListContextTrait) refreshViewport() { From 44160ef8448239e397c7bddbbd78bc9cd38327a3 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 30 Mar 2023 18:17:50 +0200 Subject: [PATCH 4/5] Only render visible portion of the screen for commits view --- pkg/gui/context/base_context.go | 59 ++++++++++++---------- pkg/gui/context/list_context_trait.go | 27 +++++++++- pkg/gui/context/local_commits_context.go | 14 ++--- pkg/gui/context/remote_branches_context.go | 15 +++--- pkg/gui/context/sub_commits_context.go | 16 +++--- pkg/gui/context/view_trait.go | 9 ++++ pkg/gui/layout.go | 14 ++++- pkg/gui/types/context.go | 5 ++ pkg/integration/components/view_driver.go | 33 +++++++----- 9 files changed, 130 insertions(+), 62 deletions(-) diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go index acece19942a..beaa61446b3 100644 --- a/pkg/gui/context/base_context.go +++ b/pkg/gui/context/base_context.go @@ -20,11 +20,12 @@ type BaseContext struct { onFocusFn onFocusFn onFocusLostFn onFocusLostFn - focusable bool - transient bool - hasControlledBounds bool - needsRerenderOnWidthChange bool - highlightOnFocus bool + focusable bool + transient bool + hasControlledBounds bool + needsRerenderOnWidthChange bool + needsRerenderOnHeightChange bool + highlightOnFocus bool *ParentContextMgr } @@ -37,15 +38,16 @@ type ( var _ types.IBaseContext = &BaseContext{} type NewBaseContextOpts struct { - Kind types.ContextKind - Key types.ContextKey - View *gocui.View - WindowName string - Focusable bool - Transient bool - HasUncontrolledBounds bool // negating for the sake of making false the default - HighlightOnFocus bool - NeedsRerenderOnWidthChange bool + Kind types.ContextKind + Key types.ContextKey + View *gocui.View + WindowName string + Focusable bool + Transient bool + HasUncontrolledBounds bool // negating for the sake of making false the default + HighlightOnFocus bool + NeedsRerenderOnWidthChange bool + NeedsRerenderOnHeightChange bool OnGetOptionsMap func() map[string]string } @@ -56,18 +58,19 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext { hasControlledBounds := !opts.HasUncontrolledBounds return &BaseContext{ - kind: opts.Kind, - key: opts.Key, - view: opts.View, - windowName: opts.WindowName, - onGetOptionsMap: opts.OnGetOptionsMap, - focusable: opts.Focusable, - transient: opts.Transient, - hasControlledBounds: hasControlledBounds, - highlightOnFocus: opts.HighlightOnFocus, - needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange, - ParentContextMgr: &ParentContextMgr{}, - viewTrait: viewTrait, + kind: opts.Kind, + key: opts.Key, + view: opts.View, + windowName: opts.WindowName, + onGetOptionsMap: opts.OnGetOptionsMap, + focusable: opts.Focusable, + transient: opts.Transient, + hasControlledBounds: hasControlledBounds, + highlightOnFocus: opts.HighlightOnFocus, + needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange, + needsRerenderOnHeightChange: opts.NeedsRerenderOnHeightChange, + ParentContextMgr: &ParentContextMgr{}, + viewTrait: viewTrait, } } @@ -197,6 +200,10 @@ func (self *BaseContext) NeedsRerenderOnWidthChange() bool { return self.needsRerenderOnWidthChange } +func (self *BaseContext) NeedsRerenderOnHeightChange() bool { + return self.needsRerenderOnHeightChange +} + func (self *BaseContext) Title() string { return "" } diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index 78d524bb22f..aca33cbd061 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -18,6 +18,9 @@ type ListContextTrait struct { // we should find out exactly which lines are now part of the path and refresh those. // We should also keep track of the previous path and refresh those lines too. refreshViewportOnChange bool + // If this is true, we only render the visible lines of the list. Useful for lists that can + // get very long, because it can save a lot of memory + renderOnlyVisibleLines bool } func (self *ListContextTrait) IsListContext() {} @@ -28,6 +31,8 @@ func (self *ListContextTrait) FocusLine() { // the view could be squashed and won't how to adjust the cursor/origin. // Also, refreshing the viewport needs to happen after the view has been resized. self.c.AfterLayout(func() error { + oldOrigin, _ := self.GetViewTrait().ViewPortYBounds() + self.GetViewTrait().FocusPoint( self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx())) @@ -41,6 +46,11 @@ func (self *ListContextTrait) FocusLine() { if self.refreshViewportOnChange { self.refreshViewport() + } else if self.renderOnlyVisibleLines { + newOrigin, _ := self.GetViewTrait().ViewPortYBounds() + if oldOrigin != newOrigin { + return self.HandleRender() + } } return nil }) @@ -83,8 +93,21 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error // OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view func (self *ListContextTrait) HandleRender() error { self.list.ClampSelection() - content := self.renderLines(-1, -1) - self.GetViewTrait().SetContent(content) + if self.renderOnlyVisibleLines { + // Rendering only the visible area can save a lot of cell memory for + // those views that support it. + totalLength := self.list.Len() + if self.getNonModelItems != nil { + totalLength += len(self.getNonModelItems()) + } + self.GetViewTrait().SetContentLineCount(totalLength) + startIdx, length := self.GetViewTrait().ViewPortYBounds() + content := self.renderLines(startIdx, startIdx+length) + self.GetViewTrait().SetViewPortContentAndClearEverythingElse(content) + } else { + content := self.renderLines(-1, -1) + self.GetViewTrait().SetContent(content) + } self.c.Render() self.setFooter() diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index c02fa3fe684..ab42cfb70fa 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -72,12 +72,13 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { SearchTrait: NewSearchTrait(c), ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Commits, - WindowName: "commits", - Key: LOCAL_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - NeedsRerenderOnWidthChange: true, + View: c.Views().Commits, + WindowName: "commits", + Key: LOCAL_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + NeedsRerenderOnWidthChange: true, + NeedsRerenderOnHeightChange: true, })), ListRenderer: ListRenderer{ list: viewModel, @@ -85,6 +86,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext { }, c: c, refreshViewportOnChange: true, + renderOnlyVisibleLines: true, }, } diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go index 884d3debbf1..fff80e0768c 100644 --- a/pkg/gui/context/remote_branches_context.go +++ b/pkg/gui/context/remote_branches_context.go @@ -37,13 +37,14 @@ func NewRemoteBranchesContext( DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle), ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().RemoteBranches, - WindowName: "branches", - Key: REMOTE_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - Transient: true, - NeedsRerenderOnWidthChange: true, + View: c.Views().RemoteBranches, + WindowName: "branches", + Key: REMOTE_BRANCHES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + Transient: true, + NeedsRerenderOnWidthChange: true, + NeedsRerenderOnHeightChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go index 842b364b3dd..ab0d2784a50 100644 --- a/pkg/gui/context/sub_commits_context.go +++ b/pkg/gui/context/sub_commits_context.go @@ -115,13 +115,14 @@ func NewSubCommitsContext( DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle), ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().SubCommits, - WindowName: "branches", - Key: SUB_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, - Transient: true, - NeedsRerenderOnWidthChange: true, + View: c.Views().SubCommits, + WindowName: "branches", + Key: SUB_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + Transient: true, + NeedsRerenderOnWidthChange: true, + NeedsRerenderOnHeightChange: true, })), ListRenderer: ListRenderer{ list: viewModel, @@ -130,6 +131,7 @@ func NewSubCommitsContext( }, c: c, refreshViewportOnChange: true, + renderOnlyVisibleLines: true, }, } diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index 1179a8b1486..19141989740 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -34,6 +34,15 @@ func (self *ViewTrait) SetViewPortContent(content string) { self.view.OverwriteLines(y, content) } +func (self *ViewTrait) SetViewPortContentAndClearEverythingElse(content string) { + _, y := self.view.Origin() + self.view.OverwriteLinesAndClearEverythingElse(y, content) +} + +func (self *ViewTrait) SetContentLineCount(lineCount int) { + self.view.SetContentLineCount(lineCount) +} + func (self *ViewTrait) SetContent(content string) { self.view.SetContent(content) } diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 01c39762879..4e2b4947799 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -72,14 +72,26 @@ func (gui *Gui) layout(g *gocui.Gui) error { frameOffset = 0 } + mustRerender := false if context.NeedsRerenderOnWidthChange() { // view.Width() returns the width -1 for some reason oldWidth := view.Width() + 1 newWidth := dimensionsObj.X1 - dimensionsObj.X0 + 2*frameOffset if oldWidth != newWidth { - contextsToRerender = append(contextsToRerender, context) + mustRerender = true } } + if context.NeedsRerenderOnHeightChange() { + // view.Height() returns the height -1 for some reason + oldHeight := view.Height() + 1 + newHeight := dimensionsObj.Y1 - dimensionsObj.Y0 + 2*frameOffset + if oldHeight != newHeight { + mustRerender = true + } + } + if mustRerender { + contextsToRerender = append(contextsToRerender, context) + } _, err = g.SetView( viewName, diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index b0e312c975e..29b53b9ebc7 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -63,6 +63,9 @@ type IBaseContext interface { // true if the view needs to be rerendered when its width changes NeedsRerenderOnWidthChange() bool + // true if the view needs to be rerendered when its height changes + NeedsRerenderOnHeightChange() bool + // returns the desired title for the view upon activation. If there is no desired title (returns empty string), then // no title will be set Title() string @@ -172,6 +175,8 @@ type IViewTrait interface { SetRangeSelectStart(yIdx int) CancelRangeSelect() SetViewPortContent(content string) + SetViewPortContentAndClearEverythingElse(content string) + SetContentLineCount(lineCount int) SetContent(content string) SetFooter(value string) SetOriginX(value int) diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index b6f91760386..3abd63a9ebd 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -431,6 +431,11 @@ func (self *ViewDriver) SelectPreviousItem() *ViewDriver { return self.PressFast(self.t.keys.Universal.PrevItem) } +// i.e. pressing '<' +func (self *ViewDriver) GotoTop() *ViewDriver { + return self.PressFast(self.t.keys.Universal.GotoTop) +} + // i.e. pressing space func (self *ViewDriver) PressPrimaryAction() *ViewDriver { return self.Press(self.t.keys.Universal.Select) @@ -457,21 +462,15 @@ func (self *ViewDriver) PressEscape() *ViewDriver { // - the user is not in a list item // - no list item is found containing the given text // - multiple list items are found containing the given text in the initial page of items -// -// NOTE: this currently assumes that BufferLines returns all the lines that can be accessed. -// If this changes in future, we'll need to update this code to first attempt to find the item -// in the current page and failing that, jump to the top of the view and iterate through all of it, -// looking for the item. func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver { self.IsFocused() view := self.getView() lines := view.BufferLines() - var matchIndex int + matchIndex := -1 self.t.assertWithRetries(func() (bool, string) { - matchIndex = -1 var matches []string // first we look for a duplicate on the current screen. We won't bother looking beyond that though. for i, line := range lines { @@ -483,13 +482,19 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver { } if len(matches) > 1 { return false, fmt.Sprintf("Found %d matches for `%s`, expected only a single match. Matching lines:\n%s", len(matches), matcher.name(), strings.Join(matches, "\n")) - } else if len(matches) == 0 { - return false, fmt.Sprintf("Could not find item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n")) - } else { - return true, "" } + return true, "" }) + // If no match was found, it could be that this is a view that renders only + // the visible lines. In that case, we jump to the top and then press + // down-arrow until we found the match. We simply return the first match we + // find, so we have no way to assert that there are no duplicates. + if matchIndex == -1 { + self.GotoTop() + matchIndex = len(lines) + } + selectedLineIdx := self.getSelectedLineIdx() if selectedLineIdx == matchIndex { return self.SelectedLine(matcher) @@ -514,12 +519,14 @@ func (self *ViewDriver) NavigateToLine(matcher *TextMatcher) *ViewDriver { for i := 0; i < maxNumKeyPresses; i++ { keyPress() idx := self.getSelectedLineIdx() - if ok, _ := matcher.test(lines[idx]); ok { + // It is important to use view.BufferLines() here and not lines, because it + // could change with every keypress. + if ok, _ := matcher.test(view.BufferLines()[idx]); ok { return self } } - self.t.fail(fmt.Sprintf("Could not navigate to item matching: %s. Lines:\n%s", matcher.name(), strings.Join(lines, "\n"))) + self.t.fail(fmt.Sprintf("Could not navigate to item matching: %s. Lines:\n%s", matcher.name(), strings.Join(view.BufferLines(), "\n"))) return self } From deee5fa95781bb515db539999394d651c8ba31c6 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 5 Jun 2024 18:48:26 +0200 Subject: [PATCH 5/5] Render the view when scrolling with the wheel --- pkg/gui/context/list_context_trait.go | 4 ++++ pkg/gui/controllers/list_controller.go | 6 ++++++ pkg/gui/types/context.go | 1 + 3 files changed, 11 insertions(+) diff --git a/pkg/gui/context/list_context_trait.go b/pkg/gui/context/list_context_trait.go index aca33cbd061..773f946a94d 100644 --- a/pkg/gui/context/list_context_trait.go +++ b/pkg/gui/context/list_context_trait.go @@ -136,3 +136,7 @@ func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool { func (self *ListContextTrait) RangeSelectEnabled() bool { return true } + +func (self *ListContextTrait) RenderOnlyVisibleLines() bool { + return self.renderOnlyVisibleLines +} diff --git a/pkg/gui/controllers/list_controller.go b/pkg/gui/controllers/list_controller.go index dc876b3fca3..711d32f79f2 100644 --- a/pkg/gui/controllers/list_controller.go +++ b/pkg/gui/controllers/list_controller.go @@ -53,6 +53,9 @@ func (self *ListController) HandleScrollRight() error { func (self *ListController) HandleScrollUp() error { scrollHeight := self.c.UserConfig.Gui.ScrollHeight self.context.GetViewTrait().ScrollUp(scrollHeight) + if self.context.RenderOnlyVisibleLines() { + return self.context.HandleRender() + } return nil } @@ -60,6 +63,9 @@ func (self *ListController) HandleScrollUp() error { func (self *ListController) HandleScrollDown() error { scrollHeight := self.c.UserConfig.Gui.ScrollHeight self.context.GetViewTrait().ScrollDown(scrollHeight) + if self.context.RenderOnlyVisibleLines() { + return self.context.HandleRender() + } return nil } diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index 29b53b9ebc7..691d5694d4b 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -153,6 +153,7 @@ type IListContext interface { FocusLine() IsListContext() // used for type switch RangeSelectEnabled() bool + RenderOnlyVisibleLines() bool } type IPatchExplorerContext interface {