mirror of
				https://github.com/jesseduffield/lazygit.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	Only render visible portion of the screen for commits view
This commit is contained in:
		| @@ -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 "" | ||||
| } | ||||
|   | ||||
| @@ -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() | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user