From 339e973e9935f63078634e7dbcb3976c2c3067e4 Mon Sep 17 00:00:00 2001 From: sogla Date: Tue, 20 Jan 2026 03:53:19 +0100 Subject: [PATCH] Fix Co-authored-by and Signed-off-by auto-wrapping in addition to ignoring footnote, also ignore Co-authored-by and Signed-off-by --- ...uto_wrap_message_footnotes_and_trailers.go | 38 ++++++++++++ pkg/integration/tests/test_list.go | 1 + .../jesseduffield/gocui/text_area.go | 58 ++++++++++++------- 3 files changed, 75 insertions(+), 22 deletions(-) create mode 100644 pkg/integration/tests/commit/auto_wrap_message_footnotes_and_trailers.go diff --git a/pkg/integration/tests/commit/auto_wrap_message_footnotes_and_trailers.go b/pkg/integration/tests/commit/auto_wrap_message_footnotes_and_trailers.go new file mode 100644 index 00000000000..db81634d46f --- /dev/null +++ b/pkg/integration/tests/commit/auto_wrap_message_footnotes_and_trailers.go @@ -0,0 +1,38 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var AutoWrapMessageFootnotesAndTrailers = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Commit, and test how auto-wrap preserves footnotes and trailers", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + // Use a ridiculously small width so that we don't have to use so much test data + config.GetUserConfig().Git.Commit.AutoWrapWidth = 20 + }, + SetupRepo: func(shell *Shell) { + shell.CreateFile("file", "file content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + IsEmpty() + + t.Views().Files(). + IsFocused(). + PressPrimaryAction(). // stage file + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + Type("subject"). + SwitchToDescription(). + Type("[1]: https://github.com/jesseduffield/lazygit"). + AddNewline(). + Type("Co-authored-by: John Smith "). + Content(Equals("[1]: https://github.com/jesseduffield/lazygit\nCo-authored-by: John Smith ")). + SwitchToSummary(). + Confirm() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 26d6d30305f..8ec523c80ea 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -104,6 +104,7 @@ var tests = []*components.IntegrationTest{ commit.AmendWhenThereAreConflictsAndCancel, commit.AmendWhenThereAreConflictsAndContinue, commit.AutoWrapMessage, + commit.AutoWrapMessageFootnotesAndTrailers, commit.Checkout, commit.CheckoutFileFromCommit, commit.CheckoutFileFromRangeSelectionOfCommits, diff --git a/vendor/github.com/jesseduffield/gocui/text_area.go b/vendor/github.com/jesseduffield/gocui/text_area.go index da5562f8c25..608f4211e15 100644 --- a/vendor/github.com/jesseduffield/gocui/text_area.go +++ b/vendor/github.com/jesseduffield/gocui/text_area.go @@ -1,6 +1,7 @@ package gocui import ( + "bytes" "regexp" "slices" "strings" @@ -70,7 +71,7 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) { startOfLine := 0 currentLineWidth := 0 indexOfLastWhitespace := -1 - var footNoteMatcher footNoteMatcher + var noWrapLineMatcher noWrapLineMatcher cells := stringToTextAreaCells(content) y := 0 @@ -93,10 +94,10 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) { startOfLine = currentPos + 1 indexOfLastWhitespace = -1 currentLineWidth = 0 - footNoteMatcher.reset() + noWrapLineMatcher.reset() } else { currentLineWidth += c.width - if c.char == " " && !footNoteMatcher.isFootNote() { + if c.char == " " && !noWrapLineMatcher.isNoWrapLine() { indexOfLastWhitespace = currentPos + 1 } else if autoWrapWidth > 0 && currentLineWidth > autoWrapWidth && indexOfLastWhitespace >= 0 { wrapAt := indexOfLastWhitespace @@ -111,10 +112,10 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) { for _, c1 := range cells[startOfLine : currentPos+1] { currentLineWidth += c1.width } - footNoteMatcher.reset() + noWrapLineMatcher.reset() } - footNoteMatcher.addCharacter(c.char) + noWrapLineMatcher.addCharacter(c.char) } } @@ -125,45 +126,58 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) { var footNoteRe = regexp.MustCompile(`^\[\d+\]:\s*$`) -type footNoteMatcher struct { - lineStr strings.Builder +type noWrapLineMatcher struct { + buf [64]byte + bufLen int + matched bool didFailToMatch bool } -func (self *footNoteMatcher) addCharacter(chr string) { - if self.didFailToMatch { +func (self *noWrapLineMatcher) addCharacter(chr string) { + if self.didFailToMatch || self.matched || len(chr) == 0 { // don't bother tracking the rune if we know it can't possibly match any more + // or if we've already matched return } - if self.lineStr.Len() == 0 && chr != "[" { - // fail early if the first rune of a line isn't a '['; this is mainly to avoid a (possibly - // expensive) regex match + b := chr[0] + + if self.bufLen == 0 && b != '[' && b != 'C' && b != 'S' { + // fail early if we can self.didFailToMatch = true return } - self.lineStr.WriteString(chr) + if self.bufLen < len(self.buf) { + self.buf[self.bufLen] = b + self.bufLen++ + } } -func (self *footNoteMatcher) isFootNote() bool { +func (self *noWrapLineMatcher) isNoWrapLine() bool { + if self.matched { + return true + } if self.didFailToMatch { return false } - if footNoteRe.MatchString(self.lineStr.String()) { - // it's a footnote, so treat spaces as non-breaking. It's important not to reset the matcher - // here, because there could be multiple spaces after a footnote. - return true + if self.bufLen > 0 { + b := self.buf[:self.bufLen] + if footNoteRe.Match(b) || + bytes.HasPrefix(b, []byte("Co-authored-by:")) || + bytes.HasPrefix(b, []byte("Signed-off-by:")) { + self.matched = true + return true + } } - // no need to check again for this line - self.didFailToMatch = true return false } -func (self *footNoteMatcher) reset() { - self.lineStr.Reset() +func (self *noWrapLineMatcher) reset() { + self.bufLen = 0 + self.matched = false self.didFailToMatch = false }