From ac3824bd7c5976f5ec9f3e0a07663765758dcd34 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 20 Feb 2025 09:13:54 +0100 Subject: [PATCH 1/3] Bump gocui --- go.mod | 2 +- go.sum | 4 ++-- vendor/github.com/jesseduffield/gocui/view.go | 11 +++++++++-- vendor/modules.txt | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b2da578e3..df8cf660a 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/integrii/flaggy v1.4.0 github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d - github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951 + github.com/jesseduffield/gocui v0.3.1-0.20250220081214-b376cb0857ac github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e diff --git a/go.sum b/go.sum index d1259e996..019905ef7 100644 --- a/go.sum +++ b/go.sum @@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE= github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o= -github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951 h1:7/3M0yosAM9/aLAjTfzSJWhsWjT860ZVe4T76RPwE2k= -github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s= +github.com/jesseduffield/gocui v0.3.1-0.20250220081214-b376cb0857ac h1:vUNTiVEB9Bz16pTJ5kNgb/1HhnWdSA1P0GfFLUJeITI= +github.com/jesseduffield/gocui v0.3.1-0.20250220081214-b376cb0857ac/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s= github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g= github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index c4fc0a28c..5c40dbe77 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -195,6 +195,9 @@ type View struct { // if true, the view will underline hyperlinks only when the cursor is on // them; otherwise, they will always be underlined UnderlineHyperLinksOnlyOnHover bool + + // number of spaces per \t character, defaults to 4 + TabWidth int } type pos struct { @@ -424,6 +427,7 @@ func NewView(name string, x0, y0, x1, y1 int, mode OutputMode) *View { searcher: &searcher{}, TextArea: &TextArea{}, rangeSelectStartY: -1, + TabWidth: 4, } v.FgColor, v.BgColor = ColorDefault, ColorDefault @@ -923,9 +927,12 @@ func (v *View) parseInput(ch rune, x int, _ int) (bool, []cell) { return truncateLine, nil } else if ch == '\t' { // fill tab-sized space - const tabStop = 4 + tabWidth := v.TabWidth + if tabWidth < 1 { + tabWidth = 4 + } ch = ' ' - repeatCount = tabStop - (x % tabStop) + repeatCount = tabWidth - (x % tabWidth) } c := cell{ fgColor: v.ei.curFgColor, diff --git a/vendor/modules.txt b/vendor/modules.txt index 0f7e17462..7d77d84dd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -171,7 +171,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem github.com/jesseduffield/go-git/v5/utils/merkletrie/index github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame github.com/jesseduffield/go-git/v5/utils/merkletrie/noder -# github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951 +# github.com/jesseduffield/gocui v0.3.1-0.20250220081214-b376cb0857ac ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a From e5137b86cfae54f8bdceba66f07b09c1bc71064e Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 19 Feb 2025 18:23:35 +0100 Subject: [PATCH 2/3] Add a tabWidth parameter to WrapViewLinesToWidth to match gocui --- .../helpers/confirmation_helper.go | 21 ++++++++------- pkg/gui/patch_exploring/state.go | 2 +- pkg/utils/lines.go | 10 ++++--- pkg/utils/lines_test.go | 27 +++++++++++++++---- 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/pkg/gui/controllers/helpers/confirmation_helper.go b/pkg/gui/controllers/helpers/confirmation_helper.go index 7a53f9243..6e45087c0 100644 --- a/pkg/gui/controllers/helpers/confirmation_helper.go +++ b/pkg/gui/controllers/helpers/confirmation_helper.go @@ -56,8 +56,8 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() { self.clearConfirmationViewKeyBindings() } -func getMessageHeight(wrap bool, editable bool, message string, width int) int { - wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width) +func getMessageHeight(wrap bool, editable bool, message string, width int, tabWidth int) int { + wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width, tabWidth) return len(wrappedLines) } @@ -265,7 +265,7 @@ func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) { if selectedItem != nil { tooltip = self.TooltipForMenuItem(selectedItem) } - tooltipHeight := getMessageHeight(true, false, tooltip, contentWidth) + 2 // plus 2 for the frame + tooltipHeight := getMessageHeight(true, false, tooltip, contentWidth, self.c.Views().Menu.TabWidth) + 2 // plus 2 for the frame _, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0) } @@ -276,7 +276,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int { var promptLines []string prompt := self.c.Contexts().Menu.GetPrompt() if len(prompt) > 0 { - promptLines, _, _ = utils.WrapViewLinesToWidth(true, false, prompt, contentWidth) + promptLines, _, _ = utils.WrapViewLinesToWidth(true, false, prompt, contentWidth, self.c.Views().Menu.TabWidth) promptLines = append(promptLines, "") } self.c.Contexts().Menu.SetPromptLines(promptLines) @@ -305,17 +305,18 @@ func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types } panelWidth := self.getPopupPanelWidth() contentWidth := panelWidth - 2 // minus 2 for the frame - prompt := self.c.Views().Confirmation.Buffer() + confirmationView := self.c.Views().Confirmation + prompt := confirmationView.Buffer() wrap := true - editable := self.c.Views().Confirmation.Editable + editable := confirmationView.Editable if editable { - prompt = self.c.Views().Confirmation.TextArea.GetContent() + prompt = confirmationView.TextArea.GetContent() wrap = false } - panelHeight := getMessageHeight(wrap, editable, prompt, contentWidth) + suggestionsViewHeight + panelHeight := getMessageHeight(wrap, editable, prompt, contentWidth, confirmationView.TabWidth) + suggestionsViewHeight x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext) confirmationViewBottom := y1 - suggestionsViewHeight - _, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0) + _, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, confirmationViewBottom, 0) suggestionsViewTop := confirmationViewBottom + 1 _, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0) @@ -325,7 +326,7 @@ func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext typ panelWidth := self.getPopupPanelWidth() content := self.c.Views().CommitDescription.TextArea.GetContent() summaryViewHeight := 3 - panelHeight := getMessageHeight(false, true, content, panelWidth) + panelHeight := getMessageHeight(false, true, content, panelWidth, self.c.Views().CommitDescription.TabWidth) minHeight := 7 if panelHeight < minHeight { panelHeight = minHeight diff --git a/pkg/gui/patch_exploring/state.go b/pkg/gui/patch_exploring/state.go index 2b32d1e7f..074793f8e 100644 --- a/pkg/gui/patch_exploring/state.go +++ b/pkg/gui/patch_exploring/state.go @@ -323,6 +323,6 @@ func (s *State) CalculateOrigin(currentOrigin int, bufferHeight int, numLines in func wrapPatchLines(diff string, view *gocui.View) ([]int, []int) { _, viewLineIndices, patchLineIndices := utils.WrapViewLinesToWidth( - view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth()) + view.Wrap, view.Editable, strings.TrimSuffix(diff, "\n"), view.InnerWidth(), view.TabWidth) return viewLineIndices, patchLineIndices } diff --git a/pkg/utils/lines.go b/pkg/utils/lines.go index ebb131c1c..c601bb806 100644 --- a/pkg/utils/lines.go +++ b/pkg/utils/lines.go @@ -109,7 +109,7 @@ func ScanLinesAndTruncateWhenLongerThanBuffer(maxBufferSize int) func(data []byt // - the line indices of the original lines, indexed by the wrapped line indices // If wrap is false, the text is returned as is. // This code needs to behave the same as `gocui.lineWrap` does. -func WrapViewLinesToWidth(wrap bool, editable bool, text string, width int) ([]string, []int, []int) { +func WrapViewLinesToWidth(wrap bool, editable bool, text string, width int, tabWidth int) ([]string, []int, []int) { if !editable { text = strings.TrimSuffix(text, "\n") } @@ -126,14 +126,18 @@ func WrapViewLinesToWidth(wrap bool, editable bool, text string, width int) ([]s wrappedLineIndices := make([]int, 0, len(lines)) originalLineIndices := make([]int, 0, len(lines)) + if tabWidth < 1 { + tabWidth = 4 + } + for originalLineIdx, line := range lines { wrappedLineIndices = append(wrappedLineIndices, len(wrappedLines)) // convert tabs to spaces for i := 0; i < len(line); i++ { if line[i] == '\t' { - numSpaces := 4 - (i % 4) - line = line[:i] + " "[:numSpaces] + line[i+1:] + numSpaces := tabWidth - (i % tabWidth) + line = line[:i] + strings.Repeat(" ", numSpaces) + line[i+1:] i += numSpaces - 1 } } diff --git a/pkg/utils/lines_test.go b/pkg/utils/lines_test.go index 6011cf1fd..a67d59237 100644 --- a/pkg/utils/lines_test.go +++ b/pkg/utils/lines_test.go @@ -173,6 +173,7 @@ func TestWrapViewLinesToWidth(t *testing.T) { editable bool text string width int + tabWidth int expectedWrappedLines []string expectedWrappedLinesIndices []int expectedOriginalLinesIndices []int @@ -353,14 +354,25 @@ func TestWrapViewLinesToWidth(t *testing.T) { }, }, { - name: "Tabs", - wrap: true, - text: "\ta\tbb\tccc\tdddd\teeeee", - width: 50, + name: "Tabs, width 4", + wrap: true, + text: "\ta\tbb\tccc\tdddd\teeeee", + width: 50, + tabWidth: 4, expectedWrappedLines: []string{ " a bb ccc dddd eeeee", }, }, + { + name: "Tabs, width 8", + wrap: true, + text: "\ta\tbb\tccc\tdddddddd\teeeee", + width: 100, + tabWidth: 8, + expectedWrappedLines: []string{ + " a bb ccc dddddddd eeeee", + }, + }, { name: "Multiple lines", wrap: true, @@ -425,7 +437,11 @@ func TestWrapViewLinesToWidth(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - wrappedLines, wrappedLinesIndices, originalLinesIndices := WrapViewLinesToWidth(tt.wrap, tt.editable, tt.text, tt.width) + tabWidth := tt.tabWidth + if tabWidth == 0 { + tabWidth = 4 + } + wrappedLines, wrappedLinesIndices, originalLinesIndices := WrapViewLinesToWidth(tt.wrap, tt.editable, tt.text, tt.width, tabWidth) assert.Equal(t, tt.expectedWrappedLines, wrappedLines) if tt.expectedWrappedLinesIndices != nil { assert.Equal(t, tt.expectedWrappedLinesIndices, wrappedLinesIndices) @@ -436,6 +452,7 @@ func TestWrapViewLinesToWidth(t *testing.T) { // As a sanity check, also test that gocui's line wrapping behaves the same way view := gocui.NewView("", 0, 0, tt.width+1, 1000, gocui.OutputNormal) + view.TabWidth = tabWidth assert.Equal(t, tt.width, view.InnerWidth()) view.Wrap = tt.wrap view.Editable = tt.editable From 11616190eeb1f3341fd95248116d5e6af3807dc0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 19 Feb 2025 18:39:12 +0100 Subject: [PATCH 3/3] Add a gui.tabWidth config Affects everything that is shown in the main view, but probably mostly relevant for diffs. --- docs/Config.md | 4 ++++ pkg/config/user_config.go | 4 ++++ pkg/gui/views.go | 1 + schema/config.json | 6 ++++++ 4 files changed, 15 insertions(+) diff --git a/docs/Config.md b/docs/Config.md index 03a29ff18..49f884dac 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -47,6 +47,10 @@ gui: # One of: 'margin' (default) | 'jump' scrollOffBehavior: margin + # The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs. + # Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command. + tabWidth: 4 + # If true, capture mouse events. # When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS. mouseEvents: true diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 9fea5ea64..d6a27733e 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -64,6 +64,9 @@ type GuiConfig struct { ScrollOffMargin int `yaml:"scrollOffMargin"` // One of: 'margin' (default) | 'jump' ScrollOffBehavior string `yaml:"scrollOffBehavior"` + // The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs. + // Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command. + TabWidth int `yaml:"tabWidth" jsonschema:"minimum=1"` // If true, capture mouse events. // When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS. MouseEvents bool `yaml:"mouseEvents"` @@ -693,6 +696,7 @@ func GetDefaultConfig() *UserConfig { ScrollPastBottom: true, ScrollOffMargin: 2, ScrollOffBehavior: "margin", + TabWidth: 4, MouseEvents: true, SkipDiscardChangeWarning: false, SkipStashWarning: false, diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 526696b25..8a4d77119 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -203,6 +203,7 @@ func (gui *Gui) configureViewProperties() { for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} { view.CanScrollPastBottom = gui.c.UserConfig().Gui.ScrollPastBottom + view.TabWidth = gui.c.UserConfig().Gui.TabWidth } gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor diff --git a/schema/config.json b/schema/config.json index a7524eb77..ef8a642cf 100644 --- a/schema/config.json +++ b/schema/config.json @@ -46,6 +46,12 @@ "description": "One of: 'margin' (default) | 'jump'", "default": "margin" }, + "tabWidth": { + "type": "integer", + "minimum": 1, + "description": "The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs.\nNote that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command.", + "default": 4 + }, "mouseEvents": { "type": "boolean", "description": "If true, capture mouse events.\nWhen mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.",