From ad95b633eb6c79033b5f3f3c1a03dfdb602e7b51 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 16 Aug 2026 13:51:34 +0200 Subject: [PATCH] Let a main pane be named without the context package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mode of a diff view's selection lives on the context of the pane showing it, and until now only the controllers that drive the selection needed to name that pane, all of which have it as the concrete context it is. A side panel is about to be handed the pane a command was invoked in, so that it can act on what is selected there — and it can't be handed a context, the interface it is handed one through being declared where a context's concrete type isn't available. So the state moves to where such an interface can speak of it, and the panes gain one: a context that has a diff selection. Co-authored-by: Claude Opus 5 (1M context) --- pkg/gui/context/main_context.go | 36 ++++----------------- pkg/gui/controllers/main_view_controller.go | 34 +++++++++---------- pkg/gui/types/context.go | 9 ++++++ pkg/gui/types/diff_select.go | 28 ++++++++++++++++ 4 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 pkg/gui/types/diff_select.go diff --git a/pkg/gui/context/main_context.go b/pkg/gui/context/main_context.go index b3e7ab21c..1ce340b8b 100644 --- a/pkg/gui/context/main_context.go +++ b/pkg/gui/context/main_context.go @@ -9,45 +9,21 @@ type MainContext struct { *SimpleContext *SearchTrait - diffSelect DiffSelectState + diffSelect types.DiffSelectState // selectableContentRenderKey names the render whose content HasSelectableContent // was worked out from. What there is to select is a property of the content, so an // answer about the content of another render says nothing about this one. selectableContentRenderKey string } -var _ types.ISearchableContext = (*MainContext)(nil) - -// DiffSelectMode is how the focused main view's diff selection extends from the -// cursor: a single line, a range from a fixed anchor, or the change block (hunk) -// around the cursor. -type DiffSelectMode int - -const ( - DiffSelectModeLine DiffSelectMode = iota - DiffSelectModeRange - DiffSelectModeHunk +var ( + _ types.ISearchableContext = (*MainContext)(nil) + _ types.DiffPaneContext = (*MainContext)(nil) ) -// DiffSelectState holds the *mode* of the focused main view's diff selection. The -// selected line and the range anchor themselves live in the gocui view (its cursor -// and range-select start), so only the mode lives here. It's on the context rather -// than on the controller because the controller that drives the selection, the -// controller that establishes it on focus, and the pane-toggle that seeds it on the -// other pane all reach the pane through its context. -type DiffSelectState struct { - Mode DiffSelectMode - // When a range is sticky, moving the cursor without holding shift extends the - // range; otherwise it collapses the range back to a single line. - RangeIsSticky bool - // Whether hunk mode was turned on by the user rather than being the configured - // default. This decides whether escape leaves hunk mode or leaves the view. - UserEnabledHunkMode bool -} - // DiffSelectState returns the focused main view's selection mode state, for the // controllers to read and mutate directly. -func (self *MainContext) DiffSelectState() *DiffSelectState { +func (self *MainContext) DiffSelectState() *types.DiffSelectState { return &self.diffSelect } @@ -56,7 +32,7 @@ func (self *MainContext) DiffSelectState() *DiffSelectState { // view's range anchor is cleared too, so the next render highlights the cursor line // only. func (self *MainContext) ResetDiffSelectMode() { - self.diffSelect.Mode = DiffSelectModeLine + self.diffSelect.Mode = types.DiffSelectModeLine self.diffSelect.RangeIsSticky = false self.diffSelect.UserEnabledHunkMode = false self.GetView().CancelRangeSelect() diff --git a/pkg/gui/controllers/main_view_controller.go b/pkg/gui/controllers/main_view_controller.go index 53154745d..4c2c993bb 100644 --- a/pkg/gui/controllers/main_view_controller.go +++ b/pkg/gui/controllers/main_view_controller.go @@ -43,7 +43,7 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty Keys: opts.GetKeys(opts.Config.Main.ToggleSelectHunk), Handler: self.toggleSelectHunk, DescriptionFunc: self.diffSelectionDescription(func() string { - if self.diffSelectState().Mode == context.DiffSelectModeHunk { + if self.diffSelectState().Mode == types.DiffSelectModeHunk { return self.c.Tr.SelectLineByLine } return self.c.Tr.SelectHunk @@ -163,7 +163,7 @@ func (self *MainViewController) escapeDescription() string { // selectingHunkEnabledByUser reports whether we are in hunk mode because the user // asked for it, as opposed to it being the configured default. func (self *MainViewController) selectingHunkEnabledByUser() bool { - return self.diffSelectState().Mode == context.DiffSelectModeHunk && self.diffSelectState().UserEnabledHunkMode + return self.diffSelectState().Mode == types.DiffSelectModeHunk && self.diffSelectState().UserEnabledHunkMode } // isDiffView reports whether the focused main view currently shows a diff, and so @@ -189,7 +189,7 @@ func (self *MainViewController) diffMainViewType() types.DiffMainViewType { } // diffSelectState returns this pane's diff selection mode state. -func (self *MainViewController) diffSelectState() *context.DiffSelectState { +func (self *MainViewController) diffSelectState() *types.DiffSelectState { return self.context.DiffSelectState() } @@ -250,7 +250,7 @@ func (self *MainViewController) selectClickedDiffLine(viewLine int) { return } view := self.context.GetView() - if self.diffSelectState().Mode == context.DiffSelectModeHunk && + if self.diffSelectState().Mode == types.DiffSelectModeHunk && self.c.Helpers().DiffLine.IsChangeLine(view, viewLine) { self.selectHunkAround(viewLine, false) return @@ -313,7 +313,7 @@ func (self *MainViewController) selectHunkAround(changeViewLine int, scrollIntoV view := self.context.GetView() start, end, ok := self.c.Helpers().DiffLine.ChangeBlockBounds(view, changeViewLine) if !ok { - self.diffSelectState().Mode = context.DiffSelectModeLine + self.diffSelectState().Mode = types.DiffSelectModeLine view.CancelRangeSelect() showSelectionAtLine(view, changeViewLine, scrollIntoView) return @@ -370,7 +370,7 @@ func (self *MainViewController) placeNavigationTarget(target int) { v.SetOrigin(0, target) return } - if self.diffSelectState().Mode == context.DiffSelectModeHunk { + if self.diffSelectState().Mode == types.DiffSelectModeHunk { self.selectHunkAround(target, true) return } @@ -403,9 +403,9 @@ func (self *MainViewController) moveCursor(delta int) { // sticky range is kept, so the move extends it. func (self *MainViewController) collapseForLineMove() { sel := self.diffSelectState() - if sel.Mode == context.DiffSelectModeHunk || - (sel.Mode == context.DiffSelectModeRange && !sel.RangeIsSticky) { - sel.Mode = context.DiffSelectModeLine + if sel.Mode == types.DiffSelectModeHunk || + (sel.Mode == types.DiffSelectModeRange && !sel.RangeIsSticky) { + sel.Mode = types.DiffSelectModeLine self.context.GetView().CancelRangeSelect() } } @@ -419,7 +419,7 @@ func (self *MainViewController) adjustSelection(delta int) { self.handleLineChange(delta) return } - if self.diffSelectState().Mode == context.DiffSelectModeHunk && (delta == 1 || delta == -1) { + if self.diffSelectState().Mode == types.DiffSelectModeHunk && (delta == 1 || delta == -1) { self.navigate(self.c.Helpers().DiffLine.AdjacentChangeBlock, delta > 0) return } @@ -438,7 +438,7 @@ func (self *MainViewController) selectAbsoluteLine(target int) { // range mode and either it's sticky or the anchor and cursor differ, i.e. a // non-sticky range that has actually been extended. func (self *MainViewController) selectingRange() bool { - if self.diffSelectState().Mode != context.DiffSelectModeRange { + if self.diffSelectState().Mode != types.DiffSelectModeRange { return false } start, end := self.context.GetView().SelectedLineRange() @@ -453,11 +453,11 @@ func (self *MainViewController) toggleSelectHunk() error { return nil } sel := self.diffSelectState() - if sel.Mode == context.DiffSelectModeHunk { - sel.Mode = context.DiffSelectModeLine + if sel.Mode == types.DiffSelectModeHunk { + sel.Mode = types.DiffSelectModeLine v.CancelRangeSelect() } else { - sel.Mode = context.DiffSelectModeHunk + sel.Mode = types.DiffSelectModeHunk sel.UserEnabledHunkMode = true self.selectHunkAround(v.SelectedLineIdx(), true) } @@ -473,11 +473,11 @@ func (self *MainViewController) toggleRangeSelect() error { } sel := self.diffSelectState() if self.selectingRange() { - sel.Mode = context.DiffSelectModeLine + sel.Mode = types.DiffSelectModeLine sel.RangeIsSticky = false v.CancelRangeSelect() } else { - sel.Mode = context.DiffSelectModeRange + sel.Mode = types.DiffSelectModeRange sel.RangeIsSticky = true v.SetRangeSelectStart(v.SelectedLineIdx()) } @@ -493,7 +493,7 @@ func (self *MainViewController) extendRange(forward bool) error { } sel := self.diffSelectState() if !self.selectingRange() { - sel.Mode = context.DiffSelectModeRange + sel.Mode = types.DiffSelectModeRange v.SetRangeSelectStart(v.SelectedLineIdx()) } sel.RangeIsSticky = false diff --git a/pkg/gui/types/context.go b/pkg/gui/types/context.go index c787d7504..2201b66a8 100644 --- a/pkg/gui/types/context.go +++ b/pkg/gui/types/context.go @@ -211,6 +211,15 @@ const ( DiffMainViewTypePatchBuilding ) +// DiffPaneContext is one of the two panes the main section can show, as the thing +// that holds a diff with a selection in it. The panels that act on such a selection +// are handed the pane it was made in, and speak to it through this. +type DiffPaneContext interface { + Context + + DiffSelectState() *DiffSelectState +} + type IListContext interface { Context diff --git a/pkg/gui/types/diff_select.go b/pkg/gui/types/diff_select.go new file mode 100644 index 000000000..041ac5969 --- /dev/null +++ b/pkg/gui/types/diff_select.go @@ -0,0 +1,28 @@ +package types + +// DiffSelectMode is how the focused main view's diff selection extends from the +// cursor: a single line, a range from a fixed anchor, or the change block (hunk) +// around the cursor. +type DiffSelectMode int + +const ( + DiffSelectModeLine DiffSelectMode = iota + DiffSelectModeRange + DiffSelectModeHunk +) + +// DiffSelectState holds the *mode* of the focused main view's diff selection. The +// selected line and the range anchor themselves live in the gocui view (its cursor +// and range-select start), so only the mode lives here. It's on the context rather +// than on the controller because the controller that drives the selection, the +// controller that establishes it on focus, and the pane-toggle that seeds it on the +// other pane all reach the pane through its context. +type DiffSelectState struct { + Mode DiffSelectMode + // When a range is sticky, moving the cursor without holding shift extends the + // range; otherwise it collapses the range back to a single line. + RangeIsSticky bool + // Whether hunk mode was turned on by the user rather than being the configured + // default. This decides whether escape leaves hunk mode or leaves the view. + UserEnabledHunkMode bool +}