diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e5c1c7ede..f362cdad2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,6 +97,10 @@ To run gofumpt from your terminal go: go install mvdan.cc/gofumpt@latest && gofumpt -l -w . ``` +## Programming Font + +Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these. + ## Internationalisation Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!). diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go index 421476368..acece1994 100644 --- a/pkg/gui/context/base_context.go +++ b/pkg/gui/context/base_context.go @@ -20,10 +20,11 @@ type BaseContext struct { onFocusFn onFocusFn onFocusLostFn onFocusLostFn - focusable bool - transient bool - hasControlledBounds bool - highlightOnFocus bool + focusable bool + transient bool + hasControlledBounds bool + needsRerenderOnWidthChange bool + highlightOnFocus bool *ParentContextMgr } @@ -36,14 +37,15 @@ 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 + 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 OnGetOptionsMap func() map[string]string } @@ -54,17 +56,18 @@ 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, - 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, + ParentContextMgr: &ParentContextMgr{}, + viewTrait: viewTrait, } } @@ -190,6 +193,10 @@ func (self *BaseContext) HasControlledBounds() bool { return self.hasControlledBounds } +func (self *BaseContext) NeedsRerenderOnWidthChange() bool { + return self.needsRerenderOnWidthChange +} + func (self *BaseContext) Title() string { return "" } diff --git a/pkg/gui/context/branches_context.go b/pkg/gui/context/branches_context.go index 68324d020..5905168ea 100644 --- a/pkg/gui/context/branches_context.go +++ b/pkg/gui/context/branches_context.go @@ -30,6 +30,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext { c.State().GetItemOperation, c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL, c.Modes().Diffing.Ref, + c.Views().Branches.Width(), c.Tr, c.UserConfig, c.Model().Worktrees, @@ -40,11 +41,12 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext { FilteredListViewModel: viewModel, ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().Branches, - WindowName: "branches", - Key: LOCAL_BRANCHES_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, + View: c.Views().Branches, + WindowName: "branches", + Key: LOCAL_BRANCHES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + NeedsRerenderOnWidthChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/context/local_commits_context.go b/pkg/gui/context/local_commits_context.go index fa91e6a79..e0172638d 100644 --- a/pkg/gui/context/local_commits_context.go +++ b/pkg/gui/context/local_commits_context.go @@ -68,11 +68,12 @@ 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, + View: c.Views().Commits, + WindowName: "commits", + Key: LOCAL_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + NeedsRerenderOnWidthChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/context/reflog_commits_context.go b/pkg/gui/context/reflog_commits_context.go index a90507e86..8dc52cde7 100644 --- a/pkg/gui/context/reflog_commits_context.go +++ b/pkg/gui/context/reflog_commits_context.go @@ -43,11 +43,12 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext { FilteredListViewModel: viewModel, ListContextTrait: &ListContextTrait{ Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{ - View: c.Views().ReflogCommits, - WindowName: "commits", - Key: REFLOG_COMMITS_CONTEXT_KEY, - Kind: types.SIDE_CONTEXT, - Focusable: true, + View: c.Views().ReflogCommits, + WindowName: "commits", + Key: REFLOG_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + NeedsRerenderOnWidthChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/context/remote_branches_context.go b/pkg/gui/context/remote_branches_context.go index 144a8c369..82d37b613 100644 --- a/pkg/gui/context/remote_branches_context.go +++ b/pkg/gui/context/remote_branches_context.go @@ -36,12 +36,13 @@ 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, + View: c.Views().RemoteBranches, + WindowName: "branches", + Key: REMOTE_BRANCHES_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + Transient: true, + NeedsRerenderOnWidthChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/context/sub_commits_context.go b/pkg/gui/context/sub_commits_context.go index 7bf34d190..79b0d9781 100644 --- a/pkg/gui/context/sub_commits_context.go +++ b/pkg/gui/context/sub_commits_context.go @@ -115,12 +115,13 @@ 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, + View: c.Views().SubCommits, + WindowName: "branches", + Key: SUB_COMMITS_CONTEXT_KEY, + Kind: types.SIDE_CONTEXT, + Focusable: true, + Transient: true, + NeedsRerenderOnWidthChange: true, })), ListRenderer: ListRenderer{ list: viewModel, diff --git a/pkg/gui/controllers/screen_mode_actions.go b/pkg/gui/controllers/screen_mode_actions.go index d31154636..1db27f2e2 100644 --- a/pkg/gui/controllers/screen_mode_actions.go +++ b/pkg/gui/controllers/screen_mode_actions.go @@ -1,7 +1,6 @@ package controllers import ( - "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -17,7 +16,7 @@ func (self *ScreenModeActions) Next() error { ), ) - return self.rerenderViewsWithScreenModeDependentContent() + return nil } func (self *ScreenModeActions) Prev() error { @@ -28,32 +27,9 @@ func (self *ScreenModeActions) Prev() error { ), ) - return self.rerenderViewsWithScreenModeDependentContent() -} - -// these views need to be re-rendered when the screen mode changes. The commits view, -// for example, will show authorship information in half and full screen mode. -func (self *ScreenModeActions) rerenderViewsWithScreenModeDependentContent() error { - // for now we re-render all list views. - for _, context := range self.c.Context().AllList() { - if err := self.rerenderView(context.GetView()); err != nil { - return err - } - } - return nil } -func (self *ScreenModeActions) rerenderView(view *gocui.View) error { - context, ok := self.c.Helpers().View.ContextForView(view.Name()) - if !ok { - self.c.Log.Errorf("no context found for view %s", view.Name()) - return nil - } - - return context.HandleRender() -} - func nextIntInCycle(sl []types.WindowMaximisation, current types.WindowMaximisation) types.WindowMaximisation { for i, val := range sl { if val == current { diff --git a/pkg/gui/controllers/status_controller.go b/pkg/gui/controllers/status_controller.go index 2a186670b..5d35d9f47 100644 --- a/pkg/gui/controllers/status_controller.go +++ b/pkg/gui/controllers/status_controller.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/constants" @@ -106,7 +107,7 @@ func (self *StatusController) onClick() error { } cx, _ := self.c.Views().Status.Cursor() - upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr) + upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now()) repoName := self.c.Git().RepoPaths.RepoName() workingTreeState := self.c.Git().Status.WorkingTreeState() switch workingTreeState { diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go index 2f6e21b73..7b43f8aaa 100644 --- a/pkg/gui/layout.go +++ b/pkg/gui/layout.go @@ -44,8 +44,13 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } + contextsToRerender := []types.Context{} + // we assume that the view has already been created. - setViewFromDimensions := func(viewName string, windowName string) (*gocui.View, error) { + setViewFromDimensions := func(context types.Context) (*gocui.View, error) { + viewName := context.GetViewName() + windowName := context.GetWindowName() + dimensionsObj, ok := viewDimensions[windowName] view, err := g.View(viewName) @@ -67,6 +72,16 @@ func (gui *Gui) layout(g *gocui.Gui) error { if view.Frame { frameOffset = 0 } + + 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) + } + } + _, err = g.SetView( viewName, dimensionsObj.X0-frameOffset, @@ -85,7 +100,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { continue } - _, err := setViewFromDimensions(context.GetViewName(), context.GetWindowName()) + _, err := setViewFromDimensions(context) if err != nil && !gocui.IsUnknownView(err) { return err } @@ -146,6 +161,12 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } + for _, context := range contextsToRerender { + if err := context.HandleRender(); err != nil { + return err + } + } + // here is a good place log some stuff // if you run `lazygit --logs` // this will let you see these branches as prettified json diff --git a/pkg/gui/presentation/branches.go b/pkg/gui/presentation/branches.go index bd027ca62..a2e9f139f 100644 --- a/pkg/gui/presentation/branches.go +++ b/pkg/gui/presentation/branches.go @@ -3,6 +3,7 @@ package presentation import ( "fmt" "strings" + "time" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -13,6 +14,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/theme" "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/mattn/go-runewidth" "github.com/samber/lo" ) @@ -23,13 +25,14 @@ func GetBranchListDisplayStrings( getItemOperation func(item types.HasUrn) types.ItemOperation, fullDescription bool, diffName string, + viewWidth int, tr *i18n.TranslationSet, userConfig *config.UserConfig, worktrees []*models.Worktree, ) [][]string { return lo.Map(branches, func(branch *models.Branch, _ int) []string { diffed := branch.Name == diffName - return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees) + return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now()) }) } @@ -39,10 +42,32 @@ func getBranchDisplayStrings( itemOperation types.ItemOperation, fullDescription bool, diffed bool, + viewWidth int, tr *i18n.TranslationSet, userConfig *config.UserConfig, worktrees []*models.Worktree, + now time.Time, ) []string { + checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees) + showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash + branchStatus := BranchStatus(b, itemOperation, tr, now) + worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree)) + + // Recency is always three characters, plus one for the space + availableWidth := viewWidth - 4 + if len(branchStatus) > 0 { + availableWidth -= runewidth.StringWidth(branchStatus) + 1 + } + if icons.IsIconEnabled() { + availableWidth -= 2 // one for the icon, one for the space + } + if showCommitHash { + availableWidth -= utils.COMMIT_HASH_SHORT_SIZE + 1 + } + if checkedOutByWorkTree { + availableWidth -= runewidth.StringWidth(worktreeIcon) + 1 + } + displayName := b.Name if b.DisplayName != "" { displayName = b.DisplayName @@ -53,13 +78,19 @@ func getBranchDisplayStrings( nameTextStyle = theme.DiffTerminalColor } + if len(displayName) > availableWidth { + // Never shorten the branch name to less then 3 characters + len := utils.Max(availableWidth, 4) + displayName = displayName[:len-1] + "…" + } coloredName := nameTextStyle.Sprint(displayName) - branchStatus := utils.WithPadding(ColoredBranchStatus(b, itemOperation, tr), 2, utils.AlignLeft) - if git_commands.CheckedOutByOtherWorktree(b, worktrees) { - worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree)) + if checkedOutByWorkTree { coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon)) } - coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus) + if len(branchStatus) > 0 { + coloredStatus := branchStatusColor(b, itemOperation).Sprint(branchStatus) + coloredName = fmt.Sprintf("%s %s", coloredName, coloredStatus) + } recencyColor := style.FgCyan if b.Recency == " *" { @@ -73,7 +104,7 @@ func getBranchDisplayStrings( res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b))) } - if fullDescription || userConfig.Gui.ShowBranchCommitHash { + if showCommitHash { res = append(res, utils.ShortSha(b.CommitHash)) } @@ -112,7 +143,7 @@ func GetBranchTextStyle(name string) style.TextStyle { } } -func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string { +func branchStatusColor(branch *models.Branch, itemOperation types.ItemOperation) style.TextStyle { colour := style.FgYellow if itemOperation != types.ItemOperationNone { colour = style.FgCyan @@ -124,13 +155,17 @@ func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperatio colour = style.FgMagenta } - return colour.Sprint(BranchStatus(branch, itemOperation, tr)) + return colour } -func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string { +func ColoredBranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string { + return branchStatusColor(branch, itemOperation).Sprint(BranchStatus(branch, itemOperation, tr, time.Now())) +} + +func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet, now time.Time) string { itemOperationStr := itemOperationToString(itemOperation, tr) if itemOperationStr != "" { - return itemOperationStr + " " + utils.Loader() + return itemOperationStr + " " + utils.Loader(now) } if !branch.IsTrackingRemote() { diff --git a/pkg/gui/presentation/branches_test.go b/pkg/gui/presentation/branches_test.go new file mode 100644 index 000000000..2c8374feb --- /dev/null +++ b/pkg/gui/presentation/branches_test.go @@ -0,0 +1,214 @@ +package presentation + +import ( + "fmt" + "testing" + "time" + + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" +) + +func Test_getBranchDisplayStrings(t *testing.T) { + scenarios := []struct { + branch *models.Branch + itemOperation types.ItemOperation + fullDescription bool + viewWidth int + useIcons bool + checkedOutByWorktree bool + expected []string + }{ + // First some tests for when the view is wide enough so that everything fits: + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branch_name"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: true, + expected: []string{"1m", "branch_name (worktree)"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 100, + useIcons: true, + checkedOutByWorktree: true, + expected: []string{"1m", "󰘬", "branch_name 󰌹"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + UpstreamRemote: "origin", + Pushables: "0", + Pullables: "0", + }, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branch_name ✓"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + UpstreamRemote: "origin", + Pushables: "3", + Pullables: "5", + }, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: true, + expected: []string{"1m", "branch_name (worktree) ↑3↓5"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationPushing, + fullDescription: false, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branch_name Pushing |"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + CommitHash: "1234567890", + UpstreamRemote: "origin", + UpstreamBranch: "branch_name", + Pushables: "0", + Pullables: "0", + Subject: "commit title", + }, + itemOperation: types.ItemOperationNone, + fullDescription: true, + viewWidth: 100, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"}, + }, + + // Now tests for how we truncate the branch name when there's not enough room: + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 14, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branch_na…"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 14, + useIcons: false, + checkedOutByWorktree: true, + expected: []string{"1m", "bra… (worktree)"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 14, + useIcons: true, + checkedOutByWorktree: true, + expected: []string{"1m", "󰘬", "branc… 󰌹"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + UpstreamRemote: "origin", + Pushables: "0", + Pullables: "0", + }, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 14, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branch_… ✓"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + UpstreamRemote: "origin", + Pushables: "3", + Pullables: "5", + }, + itemOperation: types.ItemOperationNone, + fullDescription: false, + viewWidth: 30, + useIcons: false, + checkedOutByWorktree: true, + expected: []string{"1m", "branch_na… (worktree) ↑3↓5"}, + }, + { + branch: &models.Branch{Name: "branch_name", Recency: "1m"}, + itemOperation: types.ItemOperationPushing, + fullDescription: false, + viewWidth: 20, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "branc… Pushing |"}, + }, + { + branch: &models.Branch{ + Name: "branch_name", + Recency: "1m", + CommitHash: "1234567890", + UpstreamRemote: "origin", + UpstreamBranch: "branch_name", + Pushables: "0", + Pullables: "0", + Subject: "commit title", + }, + itemOperation: types.ItemOperationNone, + fullDescription: true, + viewWidth: 20, + useIcons: false, + checkedOutByWorktree: false, + expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"}, + }, + } + + c := utils.NewDummyCommon() + + for i, s := range scenarios { + icons.SetNerdFontsVersion(lo.Ternary(s.useIcons, "3", "")) + + worktrees := []*models.Worktree{} + if s.checkedOutByWorktree { + worktrees = append(worktrees, &models.Worktree{Branch: s.branch.Name}) + } + + t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) { + strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig, worktrees, time.Time{}) + assert.Equal(t, s.expected, strings) + }) + } +} diff --git a/pkg/gui/presentation/icons/icons.go b/pkg/gui/presentation/icons/icons.go index fbfc9bb07..6bb031dc2 100644 --- a/pkg/gui/presentation/icons/icons.go +++ b/pkg/gui/presentation/icons/icons.go @@ -13,14 +13,18 @@ func IsIconEnabled() bool { } func SetNerdFontsVersion(version string) { - if !lo.Contains([]string{"2", "3"}, version) { - log.Fatalf("Unsupported nerdFontVersion %s", version) - } + if version == "" { + isIconEnabled = false + } else { + if !lo.Contains([]string{"2", "3"}, version) { + log.Fatalf("Unsupported nerdFontVersion %s", version) + } - if version == "2" { - patchGitIconsForNerdFontsV2() - patchFileIconsForNerdFontsV2() - } + if version == "2" { + patchGitIconsForNerdFontsV2() + patchFileIconsForNerdFontsV2() + } - isIconEnabled = true + isIconEnabled = true + } } diff --git a/pkg/gui/presentation/tags.go b/pkg/gui/presentation/tags.go index 9bdabcf40..c210bdebc 100644 --- a/pkg/gui/presentation/tags.go +++ b/pkg/gui/presentation/tags.go @@ -1,6 +1,8 @@ package presentation import ( + "time" + "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/presentation/icons" "github.com/jesseduffield/lazygit/pkg/gui/style" @@ -37,7 +39,7 @@ func getTagDisplayStrings(t *models.Tag, itemOperation types.ItemOperation, diff descriptionStr := descriptionColor.Sprint(t.Description()) itemOperationStr := itemOperationToString(itemOperation, tr) if itemOperationStr != "" { - descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader()) + " " + descriptionStr + descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader(time.Now())) + " " + descriptionStr } res = append(res, textStyle.Sprint(t.Name), descriptionStr) return res diff --git a/pkg/gui/status/status_manager.go b/pkg/gui/status/status_manager.go index 1f4aaa569..da4f5f14c 100644 --- a/pkg/gui/status/status_manager.go +++ b/pkg/gui/status/status_manager.go @@ -71,7 +71,7 @@ func (self *StatusManager) GetStatusString() string { } topStatus := self.statuses[0] if topStatus.statusType == "waiting" { - return topStatus.message + " " + utils.Loader() + return topStatus.message + " " + utils.Loader(time.Now()) } return topStatus.message } diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index c47945aee..aca694228 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -60,6 +60,9 @@ type IBaseContext interface { // determined independently. HasControlledBounds() bool + // true if the view needs to be rerendered when its width changes + NeedsRerenderOnWidthChange() 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 diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go index e0970f6a2..41c16ac0d 100644 --- a/pkg/integration/components/test.go +++ b/pkg/integration/components/test.go @@ -19,7 +19,7 @@ import ( const unitTestDescription = "test test" const ( - defaultWidth = 100 + defaultWidth = 150 defaultHeight = 100 ) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 12041719e..f40cd4fd3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -28,9 +28,8 @@ func GetProjectRoot() string { const LoaderAnimationInterval = 50 // Loader dumps a string to be displayed as a loader -func Loader() string { +func Loader(now time.Time) string { characters := "|/-\\" - now := time.Now() milliseconds := now.UnixMilli() index := milliseconds / LoaderAnimationInterval % int64(len(characters)) return characters[index : index+1]