From 5e559cabecd2251bab3b9348582359af23948a52 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Aug 2026 10:21:07 +0200 Subject: [PATCH] Always show a file's staged changes in the lower pane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Which pane a side of a file's diff appeared in depended on what else the file had: the staged side had the lower pane while there were unstaged changes above it, and took over the upper one when there weren't. So the same content moved about, and which side a pane was showing was something the code had to work out from the file's status rather than knowing from the pane. Now each side has a pane of its own — unstaged above, staged below — shown when there is something on it. A file with nothing unstaged shows its staged changes in the lower pane alone, which then has the whole space, including the label of the key that focuses it. Nothing about this is visible to the user: the same diff appears in the same place, with the same title and the same label, and the same keys focus and scroll it. What it is for is the code, which no longer has to ask the file what a pane is showing — and, once the diff can be staged from, no longer has to make one key mean opposite things in the same pane. Co-authored-by: Claude Opus 5 (1M context) --- pkg/gocui/view.go | 6 +- pkg/gui/controllers/files_controller.go | 42 +++++---- .../switch_to_focused_main_view_controller.go | 8 +- pkg/gui/global_handlers.go | 25 +++--- pkg/gui/gui.go | 4 + pkg/gui/main_panels.go | 53 +++++++++++- pkg/gui/views.go | 6 +- pkg/integration/components/view_driver.go | 11 +++ .../conflicts/resolve_without_trailing_lf.go | 2 +- .../tests/diff/diff_and_apply_patch.go | 3 +- .../file/directory_diff_with_renamed_files.go | 6 +- .../pane_taking_over_starts_at_the_top.go | 85 +++++++++++++++++++ .../rename_similarity_threshold_change.go | 2 +- .../file/staged_changes_in_lower_pane.go | 81 ++++++++++++++++++ ...select_line_when_whole_file_is_one_hunk.go | 2 +- pkg/integration/tests/patch_building/apply.go | 2 +- .../tests/patch_building/apply_in_reverse.go | 2 +- .../apply_in_reverse_with_conflict.go | 2 +- .../apply_with_modified_file_no_conflict.go | 2 +- .../patch_building/move_range_to_index.go | 4 +- .../tests/patch_building/move_to_index.go | 2 +- ...e_to_index_part_of_adjacent_added_lines.go | 2 +- .../patch_building/move_to_index_partial.go | 2 +- pkg/integration/tests/submodule/add.go | 4 +- pkg/integration/tests/submodule/remove.go | 2 +- .../tests/submodule/remove_nested.go | 2 +- pkg/integration/tests/test_list.go | 2 + 27 files changed, 301 insertions(+), 63 deletions(-) create mode 100644 pkg/integration/tests/file/pane_taking_over_starts_at_the_top.go create mode 100644 pkg/integration/tests/file/staged_changes_in_lower_pane.go diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index e0fdb4357..80f9228da 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -1484,9 +1484,9 @@ func (v *View) CopyContent(from *View) { // A background task may be streaming output into the source view's buffer // via Write, so read it under its own lock. The source is always a - // different view than the destination (see the sole caller, - // moveMainContextToTop), and no other code holds two view write locks at - // once, so this can't deadlock. + // different view than the destination — its callers hand content from one + // view to another — and no other code holds two view write locks at once, so + // this can't deadlock. from.writeMutex.Lock() defer from.writeMutex.Unlock() diff --git a/pkg/gui/controllers/files_controller.go b/pkg/gui/controllers/files_controller.go index 936b02fb3..6579a4543 100644 --- a/pkg/gui/controllers/files_controller.go +++ b/pkg/gui/controllers/files_controller.go @@ -366,36 +366,34 @@ func (self *FilesController) renderNonTextualConflict(node *filetree.FileNode) { func (self *FilesController) renderWorkingTreeDiff(node *filetree.FileNode) { self.c.Helpers().MergeConflicts.ResetMergeState() - split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges()) - mainShowsStaged := !split && node.GetHasStagedChanges() + // The unstaged side of a file's diff is shown in the main pane and the staged side + // in the secondary one, each only where there is a side to show — so a side is + // always in the same place, whatever the file happens to have. A file with nothing + // unstaged therefore shows its staged changes in the secondary pane, which then has + // the whole section to itself. Configured to always split, both panes are shown + // whether or not there is anything on either side. + alwaysSplit := self.c.UserConfig().Gui.SplitDiff == "always" + showStaged := node.GetHasStagedChanges() || alwaysSplit + showUnstaged := node.GetHasUnstagedChanges() || alwaysSplit || !showStaged paths := self.pathsForDiff(node) - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, paths) - title := self.c.Tr.UnstagedChanges - if mainShowsStaged { - title = self.c.Tr.StagedChanges - } - refreshOpts := types.RefreshMainOpts{ - Pair: self.c.MainViewPairs().Normal, - Main: &types.ViewUpdateOpts{ + refreshOpts := types.RefreshMainOpts{Pair: self.c.MainViewPairs().Normal} + + if showUnstaged { + cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, false, paths) + refreshOpts.Main = &types.ViewUpdateOpts{ Task: types.NewRunDiffRendererTask(cmdObj.GetCmd()), SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), - Title: title, - }, - } - - if split { - cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, paths) - - title := self.c.Tr.StagedChanges - if mainShowsStaged { - title = self.c.Tr.UnstagedChanges + Title: self.c.Tr.UnstagedChanges, } + } + if showStaged { + cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, paths) refreshOpts.Secondary = &types.ViewUpdateOpts{ - Title: title, - SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), Task: types.NewRunDiffRendererTask(cmdObj.GetCmd()), + SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(), + Title: self.c.Tr.StagedChanges, } } diff --git a/pkg/gui/controllers/switch_to_focused_main_view_controller.go b/pkg/gui/controllers/switch_to_focused_main_view_controller.go index 70d222664..e179b3a66 100644 --- a/pkg/gui/controllers/switch_to_focused_main_view_controller.go +++ b/pkg/gui/controllers/switch_to_focused_main_view_controller.go @@ -70,7 +70,13 @@ func (self *SwitchToFocusedMainViewController) onClickSecondary(opts gocui.ViewM } func (self *SwitchToFocusedMainViewController) handleFocusMainView() error { - return self.focusMainView(self.c.Contexts().Normal, -1) + // Usually the main pane, but the content can be in the secondary one alone: a file + // with nothing but staged changes shows them there. + mainViewContext := self.c.Contexts().Normal + if self.c.State().GetRepoState().GetMainPanes() == types.SecondaryPaneOnly { + mainViewContext = self.c.Contexts().NormalSecondary + } + return self.focusMainView(mainViewContext, -1) } func (self *SwitchToFocusedMainViewController) focusMainView(mainViewContext *context.MainContext, clickedLineIdx int) error { diff --git a/pkg/gui/global_handlers.go b/pkg/gui/global_handlers.go index a5e59a84e..b85330f30 100644 --- a/pkg/gui/global_handlers.go +++ b/pkg/gui/global_handlers.go @@ -22,12 +22,7 @@ func (gui *Gui) scrollDownView(view *gocui.View) { } func (gui *Gui) scrollUpMain() error { - var view *gocui.View - if gui.c.Context().Current().GetWindowName() == "secondary" { - view = gui.secondaryView() - } else { - view = gui.mainView() - } + view := gui.mainSectionView() if view.Name() == "mergeConflicts" { // although we have this same logic in the controller, this method can be invoked @@ -43,12 +38,7 @@ func (gui *Gui) scrollUpMain() error { } func (gui *Gui) scrollDownMain() error { - var view *gocui.View - if gui.c.Context().Current().GetWindowName() == "secondary" { - view = gui.secondaryView() - } else { - view = gui.mainView() - } + view := gui.mainSectionView() if view.Name() == "mergeConflicts" { gui.State.Contexts.MergeConflicts.SetUserScrolling(true) @@ -59,6 +49,17 @@ func (gui *Gui) scrollDownMain() error { return nil } +// mainSectionView returns the view that the keys for scrolling the main section act +// on: the pane the focus is in when it is in one of them, and otherwise the pane the +// section is showing — which is the lower one whenever it has the section to itself. +func (gui *Gui) mainSectionView() *gocui.View { + if gui.c.Context().Current().GetWindowName() == "secondary" || + gui.State.MainPanes == types.SecondaryPaneOnly { + return gui.secondaryView() + } + return gui.mainView() +} + func (gui *Gui) mainView() *gocui.View { viewName := gui.helpers.Window.GetViewNameForWindow("main") view, _ := gui.g.View(viewName) diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 6739a2175..0292670a0 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -101,6 +101,10 @@ type Gui struct { // this tells us whether our views have been initially set up ViewsSetup bool + // the label for the key that focuses the main view, worn by whichever of the two + // main panes that key focuses (see showFocusMainViewJumpLabelOn) + focusMainViewJumpLabel string + Views types.Views // Log of the commands/actions logged in the Command Log panel. diff --git a/pkg/gui/main_panels.go b/pkg/gui/main_panels.go index 988e5df0b..8feeaccf6 100644 --- a/pkg/gui/main_panels.go +++ b/pkg/gui/main_panels.go @@ -110,10 +110,13 @@ func (gui *Gui) allMainContextPairs() []types.MainContextPair { func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { gui.moveMainContextPairToTop(opts.Pair) + panes := mainPanesFor(opts) + gui.handOverMainSection(opts.Pair, panes) + if opts.Main != nil { gui.RefreshMainView(opts.Main, opts.Pair.Main) } else { - opts.Pair.Main.GetView().Clear() + gui.clearMainView(opts.Pair.Main) } if opts.Secondary != nil { @@ -136,7 +139,37 @@ func (gui *Gui) refreshMainViews(opts types.RefreshMainOpts) { } } - gui.setMainPanes(mainPanesFor(opts)) + gui.setMainPanes(panes) +} + +// handOverMainSection carries the content of the main section from the pane that has +// been showing it on its own to the pane about to, when a render moves the section's +// content from one to the other — a file's changes going from unstaged to staged, say. +// +// The section is one region of the screen to the user, so a change of which pane holds +// it has to look like that region re-rendering rather than blanking and filling in +// again: the incoming pane shows what the outgoing one was showing, where it was +// showing it, until its own render has read enough to be swapped in. It renders from +// the top when it does, the content it took over not being its own (see +// clearMainView). +func (gui *Gui) handOverMainSection(pair types.MainContextPair, panes types.MainPanes) { + // The lower pane is always the same view, being the only one a render can leave + // holding the section on its own; the upper one is whichever view of the main + // window this render is for, which moveMainContextPairToTop has just given a copy + // of what that window was showing. + upper, lower := pair.Main.GetView(), gui.Views.Secondary + + var from, to *gocui.View + switch { + case gui.State.MainPanes == types.MainPaneOnly && panes == types.SecondaryPaneOnly: + from, to = upper, lower + case gui.State.MainPanes == types.SecondaryPaneOnly && panes == types.MainPaneOnly: + from, to = lower, upper + default: + return + } + + gui.g.CopyContent(from, to) } // mainPanesFor says which panes the given render occupies: the one it has content for, @@ -315,6 +348,22 @@ func (gui *Gui) mainContextForView(view *gocui.View) *context.MainContext { func (gui *Gui) setMainPanes(panes types.MainPanes) { gui.State.MainPanes = panes + + // The label for the key that focuses the main view belongs on the pane that key + // focuses, which is the secondary one while it is the only one shown. + if panes == types.SecondaryPaneOnly { + gui.showFocusMainViewJumpLabelOn(gui.Views.Secondary) + } else { + gui.showFocusMainViewJumpLabelOn(gui.Views.Main) + } +} + +// showFocusMainViewJumpLabelOn puts the main view's jump label on the given pane and +// takes it off the other one, so that only the pane the key focuses wears it. +func (gui *Gui) showFocusMainViewJumpLabelOn(view *gocui.View) { + gui.Views.Main.TitlePrefix = "" + gui.Views.Secondary.TitlePrefix = "" + view.TitlePrefix = gui.focusMainViewJumpLabel } // reApplySearch runs a search the view holds again over the content a render has just diff --git a/pkg/gui/views.go b/pkg/gui/views.go index e73b18dfd..fbe4eb444 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -283,11 +283,11 @@ func (gui *Gui) configureViewProperties() { } } + gui.focusMainViewJumpLabel = "" if gui.c.UserConfig().Gui.ShowPanelJumps { - gui.Views.Main.TitlePrefix = keyToTitlePrefix(gui.c.UserConfig().Keybinding.Universal.FocusMainView) - } else { - gui.Views.Main.TitlePrefix = "" + gui.focusMainViewJumpLabel = keyToTitlePrefix(gui.c.UserConfig().Keybinding.Universal.FocusMainView) } + gui.showFocusMainViewJumpLabelOn(gui.Views.Main) // Index the tab strips by view so we can both set them on views that are // part of a multi-tab panel and clear them on views that no longer are diff --git a/pkg/integration/components/view_driver.go b/pkg/integration/components/view_driver.go index 3a354c777..30e8a243f 100644 --- a/pkg/integration/components/view_driver.go +++ b/pkg/integration/components/view_driver.go @@ -88,6 +88,17 @@ func (self *ViewDriver) IsImmediatelyBelow(upper *ViewDriver) *ViewDriver { return self } +// TitlePrefix asserts on the label a view wears in front of its title, which is the +// key that jumps to it. +func (self *ViewDriver) TitlePrefix(expected *TextMatcher) *ViewDriver { + self.t.assertWithRetries(func() (bool, string) { + actual := self.getView().TitlePrefix + return expected.context(fmt.Sprintf("%s title prefix", self.context)).test(actual) + }) + + return self +} + func (self *ViewDriver) Clear() *ViewDriver { // clearing multiple times in case there's multiple lines // (the clear button only clears a single line at a time) diff --git a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go b/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go index 30ae73e54..1355ef520 100644 --- a/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go +++ b/pkg/integration/tests/conflicts/resolve_without_trailing_lf.go @@ -54,6 +54,6 @@ var ResolveWithoutTrailingLf = NewIntegrationTest(NewIntegrationTestArgs{ Contains("M file").IsSelected(), ) - t.Views().Main().Content(Contains("-a1\n+a2\n").DoesNotContain("-no eol")) + t.Views().Secondary().Content(Contains("-a1\n+a2\n").DoesNotContain("-no eol")) }, }) diff --git a/pkg/integration/tests/diff/diff_and_apply_patch.go b/pkg/integration/tests/diff/diff_and_apply_patch.go index 6801e7bea..79b54080c 100644 --- a/pkg/integration/tests/diff/diff_and_apply_patch.go +++ b/pkg/integration/tests/diff/diff_and_apply_patch.go @@ -73,6 +73,7 @@ var DiffAndApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{ Focus(). SelectedLine(Contains("file1")) - t.Views().Main().Content(Contains("+second line")) + // The patch was applied to the index, so the file's changes are all staged. + t.Views().Secondary().Content(Contains("+second line")) }, }) diff --git a/pkg/integration/tests/file/directory_diff_with_renamed_files.go b/pkg/integration/tests/file/directory_diff_with_renamed_files.go index 18906bf03..10890130e 100644 --- a/pkg/integration/tests/file/directory_diff_with_renamed_files.go +++ b/pkg/integration/tests/file/directory_diff_with_renamed_files.go @@ -32,7 +32,7 @@ var DirectoryDiffWithRenamedFiles = NewIntegrationTest(NewIntegrationTestArgs{ Equals(" R dir/nested/file3 → file3"), ) - t.Views().Main().ContainsLines( + t.Views().Secondary().ContainsLines( Equals("diff --git a/file1 b/dir/file1"), Equals("similarity index 100%"), Equals("rename from file1"), @@ -51,7 +51,7 @@ var DirectoryDiffWithRenamedFiles = NewIntegrationTest(NewIntegrationTestArgs{ SelectNextItem(). SelectedLine(Equals(" ▼ dir")) - t.Views().Main(). + t.Views().Secondary(). ContainsLines( Equals("diff --git a/file1 b/dir/file1"), Equals("similarity index 100%"), @@ -75,7 +75,7 @@ var DirectoryDiffWithRenamedFiles = NewIntegrationTest(NewIntegrationTestArgs{ Equals(" R file1 → file1"), ) - t.Views().Main(). + t.Views().Secondary(). ContainsLines( Equals("diff --git a/file1 b/dir/file1"), Equals("similarity index 100%"), diff --git a/pkg/integration/tests/file/pane_taking_over_starts_at_the_top.go b/pkg/integration/tests/file/pane_taking_over_starts_at_the_top.go new file mode 100644 index 000000000..fe8d673b9 --- /dev/null +++ b/pkg/integration/tests/file/pane_taking_over_starts_at_the_top.go @@ -0,0 +1,85 @@ +package file + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +// changedLines is a file's worth of numbered lines, prefixed so that each file's diff +// can be told from the other's on screen. +func changedLines(prefix string) string { + lines := make([]string, 40) + for i := range lines { + lines[i] = fmt.Sprintf("%s%02d", prefix, i+1) + } + return strings.Join(lines, "\n") + "\n" +} + +var PaneTakingOverStartsAtTheTop = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "A pane taking the main section over shows its diff from the top, rather than at the offset it was left at", + ExtraCmdArgs: []string{}, + Skip: false, + Width: 120, + Height: 30, + SetupConfig: func(cfg *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("file1", changedLines("one")) + shell.CreateFileAndAdd("file2", changedLines("two")) + shell.Commit("one") + + // One file's changes are unstaged and the other's are staged, so each is shown + // in a pane of its own — and selecting one after the other hands the section + // from one pane to the other. Both diffs are longer than the section, so either + // pane can be scrolled. + shell.UpdateFile("file1", changedLines("ONE")) + shell.UpdateFileAndAdd("file2", changedLines("TWO")) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + NavigateToLine(Contains("file1")) + + t.Views().Secondary().IsInvisible() + t.Views().Main(). + IsVisible(). + Title(Equals("Unstaged changes")). + ScrollWheelDown(). + ScrollWheelDown(). + OriginYAtLeast(1) + + t.Views().Files().NavigateToLine(Contains("file2")) + + t.Views().Main().IsInvisible() + t.Views().Secondary(). + IsVisible(). + Title(Equals("Staged changes")). + Content(Contains("+TWO40")). + OriginY(0). + ScrollWheelDown(). + ScrollWheelDown(). + OriginYAtLeast(1) + + // Back to the pane that was left scrolled: what it is given is a diff the user + // hasn't seen there, so it starts at the top like any other. + t.Views().Files().NavigateToLine(Contains("file1")) + + t.Views().Secondary().IsInvisible() + t.Views().Main(). + IsVisible(). + Title(Equals("Unstaged changes")). + Content(Contains("+ONE40")). + OriginY(0) + + t.Views().Files().NavigateToLine(Contains("file2")) + + t.Views().Main().IsInvisible() + t.Views().Secondary(). + IsVisible(). + Title(Equals("Staged changes")). + Content(Contains("+TWO40")). + OriginY(0) + }, +}) diff --git a/pkg/integration/tests/file/rename_similarity_threshold_change.go b/pkg/integration/tests/file/rename_similarity_threshold_change.go index ac3ae37d3..6151846c2 100644 --- a/pkg/integration/tests/file/rename_similarity_threshold_change.go +++ b/pkg/integration/tests/file/rename_similarity_threshold_change.go @@ -34,7 +34,7 @@ var RenameSimilarityThresholdChange = NewIntegrationTest(NewIntegrationTestArgs{ ). Press(keys.Universal.FocusMainView). Tap(func() { - t.Views().Main(). + t.Views().Secondary(). Press(keys.Universal.IncreaseRenameSimilarityThreshold) t.ExpectToast(Equals("Changed rename similarity threshold to 50%")) }). diff --git a/pkg/integration/tests/file/staged_changes_in_lower_pane.go b/pkg/integration/tests/file/staged_changes_in_lower_pane.go new file mode 100644 index 000000000..e23fb9f06 --- /dev/null +++ b/pkg/integration/tests/file/staged_changes_in_lower_pane.go @@ -0,0 +1,81 @@ +package file + +import ( + "fmt" + "strings" + + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var StagedChangesInLowerPane = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "A file's staged changes are shown in the lower pane whether or not it also has unstaged ones", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(cfg *config.AppConfig) {}, + SetupRepo: func(shell *Shell) { + shell.CreateFileAndAdd("both", "one\n") + shell.CreateFileAndAdd("indexOnly", "one\n") + shell.CreateFileAndAdd("worktreeOnly", "one\n") + shell.Commit("one") + + shell.UpdateFileAndAdd("both", "one\nstaged\n") + shell.UpdateFile("both", "one\nstaged\nunstaged\n") + // More staged lines than fit in the pane, so that it can be scrolled. + staged := make([]string, 40) + for i := range staged { + staged[i] = fmt.Sprintf("staged%02d", i+1) + } + shell.UpdateFileAndAdd("indexOnly", "one\n"+strings.Join(staged, "\n")+"\n") + shell.UpdateFile("worktreeOnly", "one\nunstaged\n") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files(). + IsFocused(). + NavigateToLine(Contains("both")) + + // With changes on both sides, each side has its own pane. + t.Views().Main(). + Title(Equals("Unstaged changes")). + Content(Contains("+unstaged")) + t.Views().Secondary(). + Title(Equals("Staged changes")). + Content(Contains("+staged")) + + // With nothing unstaged, the staged side keeps its pane, which then has the + // whole space to itself. + t.Views().Files().NavigateToLine(Contains("indexOnly")) + + t.Views().Main().IsInvisible() + t.Views().Secondary(). + IsVisible(). + Title(Equals("Staged changes")). + Content(Contains("+staged01")). + // The key that focuses the diff wears its label, wherever the diff is. + TitlePrefix(Equals("[0]")). + OriginY(0) + + // And the keys for scrolling the diff scroll the pane it is in. + t.GlobalPress(keys.Universal.ScrollDownMain) + t.Views().Secondary().OriginYAtLeast(1) + t.GlobalPress(keys.Universal.ScrollUpMain) + t.Views().Secondary().OriginY(0) + + // And focusing the diff focuses the pane it is in. + t.Views().Files().Press(keys.Universal.FocusMainView) + t.Views().Secondary().IsFocused() + t.Views().Secondary().PressEscape() + + // With nothing staged, only the upper pane is shown. + t.Views().Files(). + IsFocused(). + NavigateToLine(Contains("worktreeOnly")) + + t.Views().Secondary().IsInvisible() + t.Views().Main(). + IsVisible(). + Title(Equals("Unstaged changes")). + Content(Contains("+unstaged")). + TitlePrefix(Equals("[0]")) + }, +}) diff --git a/pkg/integration/tests/main_view/select_line_when_whole_file_is_one_hunk.go b/pkg/integration/tests/main_view/select_line_when_whole_file_is_one_hunk.go index af176bdd8..4ae3d0100 100644 --- a/pkg/integration/tests/main_view/select_line_when_whole_file_is_one_hunk.go +++ b/pkg/integration/tests/main_view/select_line_when_whole_file_is_one_hunk.go @@ -26,7 +26,7 @@ var SelectLineWhenWholeFileIsOneHunk = NewIntegrationTest(NewIntegrationTestArgs // Every line of the file is an addition, so widening to the change block would // select the file entire; hunk mode gives way to a single line. - t.Views().Main(). + t.Views().Secondary(). IsFocused(). SelectedLines( Contains("+one"), diff --git a/pkg/integration/tests/patch_building/apply.go b/pkg/integration/tests/patch_building/apply.go index 533ad1e23..8ecb34fe4 100644 --- a/pkg/integration/tests/patch_building/apply.go +++ b/pkg/integration/tests/patch_building/apply.go @@ -58,7 +58,7 @@ var Apply = NewIntegrationTest(NewIntegrationTestArgs{ Contains("file1").IsSelected(), ) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("second line")) }, }) diff --git a/pkg/integration/tests/patch_building/apply_in_reverse.go b/pkg/integration/tests/patch_building/apply_in_reverse.go index f2aa6b3a8..485ac9f02 100644 --- a/pkg/integration/tests/patch_building/apply_in_reverse.go +++ b/pkg/integration/tests/patch_building/apply_in_reverse.go @@ -45,7 +45,7 @@ var ApplyInReverse = NewIntegrationTest(NewIntegrationTestArgs{ Contains("D").Contains("file1").IsSelected(), ) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("-file1 content")) }, }) diff --git a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go b/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go index d9f99a703..5b02510b8 100644 --- a/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go +++ b/pkg/integration/tests/patch_building/apply_in_reverse_with_conflict.go @@ -88,7 +88,7 @@ var ApplyInReverseWithConflict = NewIntegrationTest(NewIntegrationTestArgs{ Equals(" M file2"), ) - t.Views().Main(). + t.Views().Secondary(). ContainsLines( Contains(" file1 content"), Contains("-more file1 content"), diff --git a/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go b/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go index 66d32a654..5f8ff1006 100644 --- a/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go +++ b/pkg/integration/tests/patch_building/apply_with_modified_file_no_conflict.go @@ -63,7 +63,7 @@ var ApplyWithModifiedFileNoConflict = NewIntegrationTest(NewIntegrationTestArgs{ Equals("M file1").IsSelected(), ) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("-1\n+11\n 2\n 3\n+4")) }, }) diff --git a/pkg/integration/tests/patch_building/move_range_to_index.go b/pkg/integration/tests/patch_building/move_range_to_index.go index c8d379c97..5059e3797 100644 --- a/pkg/integration/tests/patch_building/move_range_to_index.go +++ b/pkg/integration/tests/patch_building/move_range_to_index.go @@ -62,12 +62,12 @@ var MoveRangeToIndex = NewIntegrationTest(NewIntegrationTestArgs{ Equals(" A file2"), ) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("second line")) t.Views().Files().Focus().NavigateToLine(Contains("file2")) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("file two content")) }, }) diff --git a/pkg/integration/tests/patch_building/move_to_index.go b/pkg/integration/tests/patch_building/move_to_index.go index 6eca8865f..f51ad4819 100644 --- a/pkg/integration/tests/patch_building/move_to_index.go +++ b/pkg/integration/tests/patch_building/move_to_index.go @@ -62,7 +62,7 @@ var MoveToIndex = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Files(). Focus() - t.Views().Main(). + t.Views().Secondary(). Content(Contains("file1 content")) }, }) diff --git a/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go b/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go index bf06270b7..9e210717a 100644 --- a/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go +++ b/pkg/integration/tests/patch_building/move_to_index_part_of_adjacent_added_lines.go @@ -63,7 +63,7 @@ var MoveToIndexPartOfAdjacentAddedLines = NewIntegrationTest(NewIntegrationTestA Contains("M").Contains("file1"), ) - t.Views().Main(). + t.Views().Secondary(). Content(Contains("+1st line\n 2nd line")) }, }) diff --git a/pkg/integration/tests/patch_building/move_to_index_partial.go b/pkg/integration/tests/patch_building/move_to_index_partial.go index 2f2e3ea42..050346090 100644 --- a/pkg/integration/tests/patch_building/move_to_index_partial.go +++ b/pkg/integration/tests/patch_building/move_to_index_partial.go @@ -85,7 +85,7 @@ var MoveToIndexPartial = NewIntegrationTest(NewIntegrationTestArgs{ t.Views().Files(). Focus() - t.Views().Main(). + t.Views().Secondary(). ContainsLines( Contains(`-first line`), Contains(`+first line2`), diff --git a/pkg/integration/tests/submodule/add.go b/pkg/integration/tests/submodule/add.go index a82a71227..dbd452947 100644 --- a/pkg/integration/tests/submodule/add.go +++ b/pkg/integration/tests/submodule/add.go @@ -50,7 +50,7 @@ var Add = NewIntegrationTest(NewIntegrationTestArgs{ ). SelectNextItem(). Tap(func() { - t.Views().Main().Content( + t.Views().Secondary().Content( Contains("[submodule \"my_submodule\"]"). Contains("path = my_submodule_path"). Contains("url = ../other_repo"), @@ -58,7 +58,7 @@ var Add = NewIntegrationTest(NewIntegrationTestArgs{ }). SelectNextItem(). Tap(func() { - t.Views().Main().Content( + t.Views().Secondary().Content( Contains("Submodule my_submodule_path"). Contains("(new submodule)"), ) diff --git a/pkg/integration/tests/submodule/remove.go b/pkg/integration/tests/submodule/remove.go index ca9a90b33..84cbcca4e 100644 --- a/pkg/integration/tests/submodule/remove.go +++ b/pkg/integration/tests/submodule/remove.go @@ -41,7 +41,7 @@ var Remove = NewIntegrationTest(NewIntegrationTestArgs{ ). SelectNextItem() - t.Views().Main().Content( + t.Views().Secondary().Content( Contains("-[submodule \"my_submodule_name\"]"). Contains("- path = my_submodule_path"). Contains("- url = ../my_submodule_name"), diff --git a/pkg/integration/tests/submodule/remove_nested.go b/pkg/integration/tests/submodule/remove_nested.go index b143096cd..73e9ca476 100644 --- a/pkg/integration/tests/submodule/remove_nested.go +++ b/pkg/integration/tests/submodule/remove_nested.go @@ -46,7 +46,7 @@ var RemoveNested = NewIntegrationTest(NewIntegrationTestArgs{ ). NavigateToLine(Contains(".gitmodules")) - t.Views().Main().Content( + t.Views().Secondary().Content( Contains("-[submodule \"innerSubName\"]"). Contains("- path = modules/innerSubPath"). Contains("- url = ../innerSubmodule"), diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 946bb7884..cf903e635 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -254,6 +254,7 @@ var tests = []*components.IntegrationTest{ file.Gitignore, file.GitignoreSpecialCharacters, file.PaneShownAgainStartsAtTheTop, + file.PaneTakingOverStartsAtTheTop, file.RememberCommitMessageAfterFail, file.RenameSimilarityThresholdChange, file.RenamedFiles, @@ -262,6 +263,7 @@ var tests = []*components.IntegrationTest{ file.StageChildrenRangeSelect, file.StageDeletedRangeSelect, file.StageRangeSelect, + file.StagedChangesInLowerPane, filter_and_search.FilterByFileStatus, filter_and_search.FilterCommitFiles, filter_and_search.FilterCommitFilesToggleDirectory,