Skip to content

feat: Add adaptive diff context size based on selection type #4762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/commands/git_commands/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,10 @@ func (self *CommitCommands) AmendHeadCmdObj() *oscommands.CmdObj {
}

func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) *oscommands.CmdObj {
contextSize := self.UserConfig().Git.DiffContextSize
return self.ShowCmdObjWithContextSize(hash, filterPath, self.UserConfig().Git.DiffContextSize)
}

func (self *CommitCommands) ShowCmdObjWithContextSize(hash string, filterPath string, contextSize uint64) *oscommands.CmdObj {
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
Expand Down
6 changes: 5 additions & 1 deletion pkg/commands/git_commands/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
// This is for generating diffs to be shown in the UI (e.g. rendering a range
// diff to the main view). It uses a custom pager if one is configured.
func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
return self.DiffCmdObjWithContextSize(diffArgs, self.UserConfig().Git.DiffContextSize)
}

func (self *DiffCommands) DiffCmdObjWithContextSize(diffArgs []string, contextSize uint64) *oscommands.CmdObj {
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != ""
ignoreWhitespace := self.UserConfig().Git.IgnoreWhitespaceInDiffView
Expand All @@ -31,7 +35,7 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath).
ToArgv(),
Expand Down
6 changes: 5 additions & 1 deletion pkg/commands/git_commands/stash.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,17 @@ func (self *StashCommands) Hash(index int) (string, error) {
}

func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj {
return self.ShowStashEntryCmdObjWithContextSize(index, self.UserConfig().Git.DiffContextSize)
}

func (self *StashCommands) ShowStashEntryCmdObjWithContextSize(index int, contextSize uint64) *oscommands.CmdObj {
// "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg("-u").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
Expand Down
6 changes: 4 additions & 2 deletions pkg/commands/git_commands/working_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,14 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}

func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj {
return self.WorktreeFileDiffCmdObjWithContextSize(node, plain, cached, self.UserConfig().Git.DiffContextSize)
}

func (self *WorkingTreeCommands) WorktreeFileDiffCmdObjWithContextSize(node models.IFile, plain bool, cached bool, contextSize uint64) *oscommands.CmdObj {
colorArg := self.UserConfig().Git.Paging.ColorArg
if plain {
colorArg = "never"
}

contextSize := self.UserConfig().Git.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
Expand Down
25 changes: 25 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ type SpinnerConfig struct {
Rate int `yaml:"rate" jsonschema:"minimum=1"`
}

type AdaptiveContextConfig struct {
// Whether to enable adaptive diff context sizes based on the current context
Enabled bool `yaml:"enabled"`
// Context size when viewing individual files
Files uint64 `yaml:"files"`
// Context size when viewing commit diffs
Commits uint64 `yaml:"commits"`
// Context size when viewing stash diffs
Stash uint64 `yaml:"stash"`
// Context size when in staging mode
Staging uint64 `yaml:"staging"`
// Context size when building custom patches
PatchBuilding uint64 `yaml:"patchBuilding"`
}

type GitConfig struct {
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
Paging PagingConfig `yaml:"paging"`
Expand Down Expand Up @@ -268,6 +283,8 @@ type GitConfig struct {
IgnoreWhitespaceInDiffView bool `yaml:"ignoreWhitespaceInDiffView"`
// The number of lines of context to show around each diff hunk. Can be changed from within Lazygit with the `{` and `}` keys.
DiffContextSize uint64 `yaml:"diffContextSize"`
// Adaptive diff context sizes based on the current context
AdaptiveContext AdaptiveContextConfig `yaml:"adaptiveContext"`
// The threshold for considering a file to be renamed, in percent. Can be changed from within Lazygit with the `(` and `)` keys.
RenameSimilarityThreshold int `yaml:"renameSimilarityThreshold" jsonschema:"minimum=0,maximum=100"`
// If true, do not spawn a separate process when using GPG
Expand Down Expand Up @@ -827,6 +844,14 @@ func GetDefaultConfig() *UserConfig {
AllBranchesLogCmds: []string{"git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium"},
IgnoreWhitespaceInDiffView: false,
DiffContextSize: 3,
AdaptiveContext: AdaptiveContextConfig{
Enabled: false,
Files: 1000,
Commits: 10,
Stash: 5,
Staging: 20,
PatchBuilding: 50,
},
RenameSimilarityThreshold: 50,
DisableForcePushing: false,
CommitPrefixes: map[string][]CommitPrefixConfig(nil),
Expand Down
38 changes: 38 additions & 0 deletions pkg/gui/controllers/common.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package controllers

import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)

type ControllerCommon struct {
Expand All @@ -22,3 +24,39 @@ func NewControllerCommon(
IGetHelpers: IGetHelpers,
}
}

// getContextSizeForCurrentContext returns the appropriate context size based on the current context
func (self *ControllerCommon) getContextSizeForCurrentContext() uint64 {
adaptiveConfig := self.UserConfig().Git.AdaptiveContext
if !adaptiveConfig.Enabled {
return self.UserConfig().Git.DiffContextSize
}

currentContext := self.currentSidePanel().GetKey()
switch currentContext {
case context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY:
return adaptiveConfig.Files
case context.LOCAL_COMMITS_CONTEXT_KEY, context.SUB_COMMITS_CONTEXT_KEY:
return adaptiveConfig.Commits
case context.STASH_CONTEXT_KEY:
return adaptiveConfig.Stash
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
return adaptiveConfig.Staging
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY, context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY:
return adaptiveConfig.PatchBuilding
default:
return self.UserConfig().Git.DiffContextSize
}
}

func (self *ControllerCommon) currentSidePanel() types.Context {
currentContext := self.Context().CurrentStatic()
if currentContext.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY ||
currentContext.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY {
if sidePanelContext := self.Context().NextInStack(currentContext); sidePanelContext != nil {
return sidePanelContext
}
}

return currentContext
}
99 changes: 85 additions & 14 deletions pkg/gui/controllers/context_lines_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ func (self *ContextLinesController) Increase() error {
return err
}

if self.c.UserConfig().Git.DiffContextSize < math.MaxUint64 {
self.c.UserConfig().Git.DiffContextSize++
}
self.incrementContextSize()
return self.applyChange()
}

Expand All @@ -86,17 +84,93 @@ func (self *ContextLinesController) Decrease() error {
return err
}

self.decrementContextSize()
return self.applyChange()
}

return nil
}

// incrementContextSize increases the context size for the current context
func (self *ContextLinesController) incrementContextSize() {
adaptiveConfig := &self.c.UserConfig().Git.AdaptiveContext
if !adaptiveConfig.Enabled {
if self.c.UserConfig().Git.DiffContextSize < math.MaxUint64 {
self.c.UserConfig().Git.DiffContextSize++
}
return
}

currentContext := self.currentSidePanel().GetKey()
switch currentContext {
case context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY:
if adaptiveConfig.Files < math.MaxUint64 {
adaptiveConfig.Files++
}
case context.LOCAL_COMMITS_CONTEXT_KEY, context.SUB_COMMITS_CONTEXT_KEY:
if adaptiveConfig.Commits < math.MaxUint64 {
adaptiveConfig.Commits++
}
case context.STASH_CONTEXT_KEY:
if adaptiveConfig.Stash < math.MaxUint64 {
adaptiveConfig.Stash++
}
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
if adaptiveConfig.Staging < math.MaxUint64 {
adaptiveConfig.Staging++
}
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY, context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY:
if adaptiveConfig.PatchBuilding < math.MaxUint64 {
adaptiveConfig.PatchBuilding++
}
default:
if self.c.UserConfig().Git.DiffContextSize < math.MaxUint64 {
self.c.UserConfig().Git.DiffContextSize++
}
}
}

// decrementContextSize decreases the context size for the current context
func (self *ContextLinesController) decrementContextSize() {
adaptiveConfig := &self.c.UserConfig().Git.AdaptiveContext
if !adaptiveConfig.Enabled {
if self.c.UserConfig().Git.DiffContextSize > 0 {
self.c.UserConfig().Git.DiffContextSize--
}
return self.applyChange()
return
}

return nil
currentContext := self.currentSidePanel().GetKey()
switch currentContext {
case context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY:
if adaptiveConfig.Files > 0 {
adaptiveConfig.Files--
}
case context.LOCAL_COMMITS_CONTEXT_KEY, context.SUB_COMMITS_CONTEXT_KEY:
if adaptiveConfig.Commits > 0 {
adaptiveConfig.Commits--
}
case context.STASH_CONTEXT_KEY:
if adaptiveConfig.Stash > 0 {
adaptiveConfig.Stash--
}
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
if adaptiveConfig.Staging > 0 {
adaptiveConfig.Staging--
}
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY, context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY:
if adaptiveConfig.PatchBuilding > 0 {
adaptiveConfig.PatchBuilding--
}
default:
if self.c.UserConfig().Git.DiffContextSize > 0 {
self.c.UserConfig().Git.DiffContextSize--
}
}
}

func (self *ContextLinesController) applyChange() error {
self.c.Toast(fmt.Sprintf(self.c.Tr.DiffContextSizeChanged, self.c.UserConfig().Git.DiffContextSize))
self.c.Toast(fmt.Sprintf(self.c.Tr.DiffContextSizeChanged, self.getContextSizeForCurrentContext()))

currentContext := self.currentSidePanel()
switch currentContext.GetKey() {
Expand Down Expand Up @@ -127,13 +201,10 @@ func (self *ContextLinesController) isShowingDiff() bool {
}

func (self *ContextLinesController) currentSidePanel() types.Context {
currentContext := self.c.Context().CurrentStatic()
if currentContext.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY ||
currentContext.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY {
if sidePanelContext := self.c.Context().NextInStack(currentContext); sidePanelContext != nil {
return sidePanelContext
}
}
return self.c.currentSidePanel()
}

return currentContext
// getContextSizeForCurrentContext returns the appropriate context size based on the current context
func (self *ContextLinesController) getContextSizeForCurrentContext() uint64 {
return self.c.getContextSizeForCurrentContext()
}
6 changes: 4 additions & 2 deletions pkg/gui/controllers/files_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ func (self *FilesController) GetOnRenderToMain() func() {
split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges()

cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged)
contextSize := self.c.getContextSizeForCurrentContext()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObjWithContextSize(node, false, mainShowsStaged, contextSize)
title := self.c.Tr.UnstagedChanges
if mainShowsStaged {
title = self.c.Tr.StagedChanges
Expand All @@ -297,7 +298,8 @@ func (self *FilesController) GetOnRenderToMain() func() {
}

if split {
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true)
contextSize := self.c.getContextSizeForCurrentContext()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObjWithContextSize(node, false, true, contextSize)

title := self.c.Tr.StagedChanges
if mainShowsStaged {
Expand Down