From a6001dc76e3746a6f9c54b00124a5a8116afef3b Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 4 Jul 2025 19:40:43 +0200 Subject: [PATCH 1/3] Cleanup: rename a badly named function We used the term "change line" to mean "a line that was added or deleted", but it sounded like a verb in the function name. --- pkg/gui/controllers/patch_building_controller.go | 2 +- pkg/gui/patch_exploring/state.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go index faddbd5f8..173ee979a 100644 --- a/pkg/gui/controllers/patch_building_controller.go +++ b/pkg/gui/controllers/patch_building_controller.go @@ -134,7 +134,7 @@ func (self *PatchBuildingController) toggleSelection() error { state := self.context().GetState() // Get added/deleted lines in the selected patch range - lineIndicesToToggle := state.ChangeLinesInSelectedPatchRange() + lineIndicesToToggle := state.LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange() if len(lineIndicesToToggle) == 0 { // Only context lines or header lines selected, so nothing to do return nil diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index acf2b0ef2..416f398f5 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -335,7 +335,7 @@ func (s *State) SelectedPatchRange() (int, int) { } // Returns the line indices of the selected patch range that are changes (i.e. additions or deletions) -func (s *State) ChangeLinesInSelectedPatchRange() []int { +func (s *State) LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange() []int { viewStart, viewEnd := s.SelectedViewRange() patchStart, patchEnd := s.patchLineIndices[viewStart], s.patchLineIndices[viewEnd] lines := s.patch.Lines() From 2961c991a4fd685b082dab75b20e621847ce1627 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 4 Jul 2025 13:23:37 +0200 Subject: [PATCH 2/3] Add user config to use hunk mode by default when entering staging view --- docs/Config.md | 3 +++ pkg/config/user_config.go | 3 +++ pkg/gui/controllers/helpers/patch_building_helper.go | 2 +- pkg/gui/controllers/helpers/staging_helper.go | 5 +++-- pkg/gui/patch_exploring/state.go | 12 ++++++++---- schema/config.json | 5 +++++ 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index 96c96a410..925ceb1b3 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -116,6 +116,9 @@ gui: # paragraphs of markdown text. wrapLinesInStagingView: true + # If true, hunk selection mode will be enabled by default when entering the staging view. + useHunkModeInStagingView: false + # One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' language: auto diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 8e940b6fa..d490e3e10 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -107,6 +107,8 @@ type GuiConfig struct { // makes it much easier to work with diffs that have long lines, e.g. // paragraphs of markdown text. WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"` + // If true, hunk selection mode will be enabled by default when entering the staging view. + UseHunkModeInStagingView bool `yaml:"useHunkModeInStagingView"` // One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"` // Format used when displaying time e.g. commit time. @@ -745,6 +747,7 @@ func GetDefaultConfig() *UserConfig { MainPanelSplitMode: "flexible", EnlargedSideViewLocation: "left", WrapLinesInStagingView: true, + UseHunkModeInStagingView: false, Language: "auto", TimeFormat: "02 Jan 06", ShortTimeFormat: time.Kitchen, diff --git a/pkg/gui/controllers/helpers/patch_building_helper.go b/pkg/gui/controllers/helpers/patch_building_helper.go index 116e8b293..c69435954 100644 --- a/pkg/gui/controllers/helpers/patch_building_helper.go +++ b/pkg/gui/controllers/helpers/patch_building_helper.go @@ -84,7 +84,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt oldState := context.GetState() - state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState) + state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView) context.SetState(state) if state == nil { self.Escape() diff --git a/pkg/gui/controllers/helpers/staging_helper.go b/pkg/gui/controllers/helpers/staging_helper.go index 3d9762541..55b9c133b 100644 --- a/pkg/gui/controllers/helpers/staging_helper.go +++ b/pkg/gui/controllers/helpers/staging_helper.go @@ -62,12 +62,13 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) { mainContext.GetMutex().Lock() secondaryContext.GetMutex().Lock() + hunkMode := self.c.UserConfig().Gui.UseHunkModeInStagingView mainContext.SetState( - patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()), + patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode), ) secondaryContext.SetState( - patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()), + patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode), ) mainState := mainContext.GetState() diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 416f398f5..01d34b4e0 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -39,7 +39,7 @@ const ( HUNK ) -func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State) *State { +func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *State, useHunkModeByDefault bool) *State { if oldState != nil && diff == oldState.diff && selectedLineIdx == -1 { // if we're here then we can return the old state. If selectedLineIdx was not -1 // then that would mean we were trying to click and potentially drag a range, which @@ -61,6 +61,10 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat } selectMode := LINE + if useHunkModeByDefault { + selectMode = HUNK + } + // if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line if selectedLineIdx >= 0 { // Clamp to the number of wrapped view lines; index might be out of @@ -70,9 +74,9 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat selectMode = RANGE rangeStartLineIdx = selectedLineIdx } else if oldState != nil { - // if we previously had a selectMode of RANGE, we want that to now be line again - if oldState.selectMode == HUNK { - selectMode = HUNK + // if we previously had a selectMode of RANGE, we want that to now be line again (or hunk, if that's the default) + if oldState.selectMode != RANGE { + selectMode = oldState.selectMode } selectedLineIdx = viewLineIndices[patch.GetNextChangeIdx(oldState.patchLineIndices[oldState.selectedLineIdx])] } else { diff --git a/schema/config.json b/schema/config.json index 4e63e6087..520f2e0c6 100644 --- a/schema/config.json +++ b/schema/config.json @@ -530,6 +530,11 @@ "description": "If true, wrap lines in the staging view to the width of the view. This\nmakes it much easier to work with diffs that have long lines, e.g.\nparagraphs of markdown text.", "default": true }, + "useHunkModeInStagingView": { + "type": "boolean", + "description": "If true, hunk selection mode will be enabled by default when entering the staging view.", + "default": false + }, "language": { "type": "string", "enum": [ From 0a73123a66bcbb69fb1725d2f05f93d417a05ac1 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Fri, 4 Jul 2025 14:35:48 +0200 Subject: [PATCH 3/3] Escape out of hunk mode only if it was turned on by the user If hunk mode is on by default because of the config, then it's annoying for escape to go to line mode. --- .../controllers/patch_building_controller.go | 2 +- pkg/gui/controllers/staging_controller.go | 2 +- pkg/gui/patch_exploring/state.go | 33 ++++++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/pkg/gui/controllers/patch_building_controller.go b/pkg/gui/controllers/patch_building_controller.go index 173ee979a..166cc4a2b 100644 --- a/pkg/gui/controllers/patch_building_controller.go +++ b/pkg/gui/controllers/patch_building_controller.go @@ -170,7 +170,7 @@ func (self *PatchBuildingController) Escape() error { context := self.c.Contexts().CustomPatchBuilder state := context.GetState() - if state.SelectingRange() || state.SelectingHunk() { + if state.SelectingRange() || state.SelectingHunkEnabledByUser() { state.SetLineSelectMode() self.c.PostRefreshUpdate(context) return nil diff --git a/pkg/gui/controllers/staging_controller.go b/pkg/gui/controllers/staging_controller.go index 6b251883b..44bcffb19 100644 --- a/pkg/gui/controllers/staging_controller.go +++ b/pkg/gui/controllers/staging_controller.go @@ -168,7 +168,7 @@ func (self *StagingController) EditFile() error { } func (self *StagingController) Escape() error { - if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunk() { + if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunkEnabledByUser() { self.context.GetState().SetLineSelectMode() self.c.PostRefreshUpdate(self.context) return nil diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 01d34b4e0..6223a9979 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -28,6 +28,12 @@ type State struct { viewLineIndices []int // Array of indices of the original patch lines indexed by a wrapped view line index patchLineIndices []int + + // whether the user has switched to hunk mode manually; if hunk mode is on + // but this is false, then hunk mode was enabled because the config makes it + // on by default. + // this makes a difference for whether we want to escape out of hunk mode + userEnabledHunkMode bool } // these represent what select mode we're in @@ -65,6 +71,11 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat selectMode = HUNK } + userEnabledHunkMode := false + if oldState != nil { + userEnabledHunkMode = oldState.userEnabledHunkMode + } + // if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line if selectedLineIdx >= 0 { // Clamp to the number of wrapped view lines; index might be out of @@ -84,14 +95,15 @@ func NewState(diff string, selectedLineIdx int, view *gocui.View, oldState *Stat } return &State{ - patch: patch, - selectedLineIdx: selectedLineIdx, - selectMode: selectMode, - rangeStartLineIdx: rangeStartLineIdx, - rangeIsSticky: false, - diff: diff, - viewLineIndices: viewLineIndices, - patchLineIndices: patchLineIndices, + patch: patch, + selectedLineIdx: selectedLineIdx, + selectMode: selectMode, + rangeStartLineIdx: rangeStartLineIdx, + rangeIsSticky: false, + diff: diff, + viewLineIndices: viewLineIndices, + patchLineIndices: patchLineIndices, + userEnabledHunkMode: userEnabledHunkMode, } } @@ -129,6 +141,7 @@ func (s *State) ToggleSelectHunk() { s.selectMode = LINE } else { s.selectMode = HUNK + s.userEnabledHunkMode = true // If we are not currently on a change line, select the next one (or the // previous one if there is no next one): @@ -159,6 +172,10 @@ func (s *State) SelectingHunk() bool { return s.selectMode == HUNK } +func (s *State) SelectingHunkEnabledByUser() bool { + return s.selectMode == HUNK && s.userEnabledHunkMode +} + func (s *State) SelectingRange() bool { return s.selectMode == RANGE && (s.rangeIsSticky || s.rangeStartLineIdx != s.selectedLineIdx) }