Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion text_area.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
currentLineWidth := 0
indexOfLastWhitespace := -1
var footNoteMatcher footNoteMatcher
var trailerMatcher trailerMatcher

cells := stringToTextAreaCells(content)
y := 0
Expand All @@ -94,9 +95,10 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
indexOfLastWhitespace = -1
currentLineWidth = 0
footNoteMatcher.reset()
trailerMatcher.reset()
} else {
currentLineWidth += c.width
if c.char == " " && !footNoteMatcher.isFootNote() {
if c.char == " " && !footNoteMatcher.isFootNote() && !trailerMatcher.isTrailer() {
indexOfLastWhitespace = currentPos + 1
} else if autoWrapWidth > 0 && currentLineWidth > autoWrapWidth && indexOfLastWhitespace >= 0 {
wrapAt := indexOfLastWhitespace
Expand All @@ -112,9 +114,11 @@ func contentToCells(content string, autoWrapWidth int) ([]TextAreaCell, []int) {
currentLineWidth += c1.width
}
footNoteMatcher.reset()
trailerMatcher.reset()
}

footNoteMatcher.addCharacter(c.char)
trailerMatcher.addCharacter(c.char)
}
}

Expand Down Expand Up @@ -167,6 +171,76 @@ func (self *footNoteMatcher) reset() {
self.didFailToMatch = false
}

var supportedTrailers = []string{
"Signed-off-by:",
"Co-authored-by:",
}

type trailerMatcher struct {
lineStr strings.Builder
didFailToMatch bool
didMatch bool
}

func (self *trailerMatcher) addCharacter(chr string) {
if self.didFailToMatch || self.didMatch {
return
}

if len(chr) != 1 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is minor, but footnotes are also all ASCII, and the same optimization can be used to skip some regex checks

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, but that would actually make it worse. I added a bit of benchmark data (587285e) for this, and adding the check to the footnote matcher increases the benchmark time by a couple of microseconds.

I need to keep the check for the trailer matcher though, because it's needed for correctness, since I access the bytes of the string a few lines below, so I first need to make sure it's really one byte.

// Trailers are all ASCII, so if we get a non-ASCII UTF-8 character (or even a multi-rune
// grapheme cluster), we can fail early.
self.didFailToMatch = true
return
}

if self.lineStr.Len() == 0 {
// If this is the first character, see if it could possibly match any supported trailer; if
// not, we can fail early and stop tracking further characters for this line.
if !anyOf(supportedTrailers, func(trailer string) bool { return trailer[0] == chr[0] }) {
self.didFailToMatch = true
return
}
}

self.lineStr.WriteString(chr)
}

func (self *trailerMatcher) isTrailer() bool {
if self.didFailToMatch {
return false
}

if self.didMatch {
return true
}

line := self.lineStr.String()
if anyOf(supportedTrailers, func(trailer string) bool { return line == trailer }) {
self.didMatch = true
return true
}

self.didFailToMatch = true
return false
}

func (self *trailerMatcher) reset() {
self.lineStr.Reset()
self.didFailToMatch = false
self.didMatch = false
}

func anyOf(strings []string, predicate func(s string) bool) bool {
for _, s := range strings {
if predicate(s) {
return true
}
}

return false
}

func (self *TextArea) updateCells() {
width := self.AutoWrapWidth
if !self.AutoWrap {
Expand Down
48 changes: 48 additions & 0 deletions text_area_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,20 @@ func Test_AutoWrapContent(t *testing.T) {
expectedWrappedContent: "abc\n[1]: normal \ntext \nfollows\ndef",
expectedSoftLineBreaks: []int{16, 21},
},
{
name: "don't break at space after trailer",
content: "abc\nSigned-off-by: John Doe <john@doe.com>\nCo-authored-by: Jane Smith <jane@smith.com>\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\nSigned-off-by: John Doe <john@doe.com>\nCo-authored-by: Jane Smith <jane@smith.com>\n",
expectedSoftLineBreaks: []int{},
},
{
name: "do break at space after trailer if there is no space after the colon",
content: "abc\nSigned-off-by:John Doe <john@doe.com>\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\nSigned-off-by:John \nDoe \n<john@doe.com>\n",
expectedSoftLineBreaks: []int{23, 27},
},
{
name: "hard line breaks",
content: "abc\ndef\n",
Expand Down Expand Up @@ -993,3 +1007,37 @@ func Test_AutoWrapContent(t *testing.T) {
})
}
}

var testContent string = `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Quisque vehicula mi at elit pellentesque, eu pulvinar ligula molestie.
In vitae orci vitae elit fermentum lobortis sed in nisi.
Nam non odio nisi.
Donec vitae elit enim.
Pellentesque faucibus dolor at metus elementum sollicitudin.
Mauris eu orci vel odio ornare feugiat eget ac nisl.
Nam at dolor erat.
Integer sit amet rutrum lectus, mollis pretium sapien.
Maecenas ligula ipsum, congue vitae rhoncus eget, volutpat at quam.
Donec ac ultricies tortor, sit amet sollicitudin urna.
Integer porta ornare diam a imperdiet.
Praesent vulputate mi turpis, in porttitor diam commodo a.
Donec ut enim ligula.

[Thïs-is-not-à-fôôtnöte]: https://example.com/footnote

Sïgned-öff-by: This is not a trailer

[1]: This is a footnote

Signed-off-by: John Doe <john@doe.com>
`

func BenchmarkTypeCharacter(b *testing.B) {
textArea := &TextArea{content: testContent, AutoWrapWidth: 72, AutoWrap: true}
textArea.SetCursor2D(0, 0)

b.ResetTimer()
for b.Loop() {
textArea.TypeCharacter("a")
}
}