Files
lazygit/pkg/gocui/text_area_test.go
Stefan Haller b6deefacd2 Exclude more commit trailers from auto-wrapping
We already excluded the most commonly used commit trailers from being
auto-wrapped when typing or rewording a commit message, but this was
limited to two hard-coded ones ("Signed-off-by:" and "Co-authored-by:").
Extend this mechanism to use a heuristic to prevent more trailers from
wrapping; the heuristic kicks in for any "Key: Value" line if Key
contains a dash, or the value looks like a URL (so that it also catches
things like "Bug: https://my-bug-tracker/345").

To avoid mistaking a "Key: Value"-looking line in the message body for a
trailer, only apply the heuristic in the last paragraph of the message,
i.e. the block of lines at the end that is separated from the body by a
blank line. Each line there is judged on its own, so a line that isn't
recognized as a trailer still wraps without affecting the real trailers
next to it.
2026-08-02 19:22:34 +02:00

1089 lines
28 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gocui
import (
"reflect"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTextArea(t *testing.T) {
tests := []struct {
actions func(*TextArea)
wrapWidth int
expectedContent string
expectedCursor int
expectedClipboard string
}{
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("c")
},
expectedContent: "abc",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
},
expectedContent: "a\nc",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcd")
},
expectedContent: "abcd",
expectedCursor: 4,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("a字cd")
},
expectedContent: "a字cd",
expectedCursor: 6,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.BackSpaceChar()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.BackSpaceChar()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.BackSpaceChar()
},
expectedContent: "a",
expectedCursor: 1,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.DeleteChar()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.DeleteChar()
},
expectedContent: "a",
expectedCursor: 1,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.MoveCursorLeft()
textarea.DeleteChar()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("c")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.DeleteChar()
},
expectedContent: "ac",
expectedCursor: 1,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.MoveCursorLeft()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.MoveCursorLeft()
},
expectedContent: "a",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.MoveCursorLeft()
},
expectedContent: "ab",
expectedCursor: 1,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.MoveCursorRight()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.MoveCursorRight()
},
expectedContent: "a",
expectedCursor: 1,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.MoveCursorLeft()
textarea.MoveCursorRight()
},
expectedContent: "ab",
expectedCursor: 2,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("漢")
textarea.TypeCharacter("字")
textarea.MoveCursorLeft()
},
expectedContent: "漢字",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.ToggleOverwrite()
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
},
expectedContent: "ab",
expectedCursor: 2,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("c")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.ToggleOverwrite()
textarea.TypeCharacter("d")
},
expectedContent: "adc",
expectedCursor: 2,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.MoveLeftWord()
},
expectedContent: "aaa bbb",
expectedCursor: 4,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa\nbbb")
textarea.MoveLeftWord()
},
expectedContent: "aaa\nbbb",
expectedCursor: 4,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.GoToStartOfLine()
textarea.MoveLeftWord()
},
wrapWidth: 4,
expectedContent: "aaa bbb",
expectedCursor: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb\n")
textarea.MoveLeftWord()
},
expectedContent: "aaa bbb\n",
expectedCursor: 7,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.MoveLeftWord()
textarea.MoveLeftWord()
},
expectedContent: "aaa bbb",
expectedCursor: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa")
textarea.GoToStartOfLine()
textarea.MoveLeftWord()
},
expectedContent: "aaa",
expectedCursor: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.MoveRightWord()
},
expectedContent: "aaa bbb",
expectedCursor: 7,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa\nbbb")
textarea.GoToStartOfLine()
textarea.MoveCursorLeft()
textarea.MoveRightWord()
},
expectedContent: "aaa\nbbb",
expectedCursor: 4,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.GoToStartOfLine()
textarea.MoveCursorLeft()
textarea.MoveRightWord()
},
wrapWidth: 4,
expectedContent: "aaa bbb",
expectedCursor: 7,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb")
textarea.GoToStartOfLine()
textarea.MoveRightWord()
},
expectedContent: "aaa bbb",
expectedCursor: 3,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("aaa bbb\n")
textarea.MoveCursorLeft()
textarea.GoToStartOfLine()
textarea.MoveRightWord()
textarea.MoveRightWord()
},
expectedContent: "aaa bbb\n",
expectedCursor: 7,
},
{
actions: func(textarea *TextArea) {
// overwrite mode acts same as normal mode when cursor is at the end
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("c")
textarea.ToggleOverwrite()
textarea.TypeCharacter("d")
},
expectedContent: "abcd",
expectedCursor: 4,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.DeleteToStartOfLine()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.DeleteToStartOfLine()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "ab",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.DeleteToStartOfLine()
},
expectedContent: "ab",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.DeleteToStartOfLine()
},
expectedContent: "ab",
expectedCursor: 2,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
textarea.TypeCharacter("d")
textarea.DeleteToStartOfLine()
},
expectedContent: "ab\n",
expectedCursor: 3,
expectedClipboard: "cd",
},
{
actions: func(textarea *TextArea) {
textarea.GoToStartOfLine()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.MoveCursorLeft()
textarea.GoToStartOfLine()
},
expectedContent: "a",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
textarea.TypeCharacter("d")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.GoToStartOfLine()
},
expectedContent: "ab\ncd",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
textarea.TypeCharacter("d")
textarea.GoToStartOfLine()
},
expectedContent: "ab\ncd",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
textarea.TypeCharacter("d")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.GoToStartOfLine()
},
expectedContent: "ab\ncd",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.GoToEndOfLine()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("a")
textarea.TypeCharacter("b")
textarea.TypeCharacter("\n")
textarea.TypeCharacter("c")
textarea.TypeCharacter("d")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.GoToEndOfLine()
},
expectedContent: "ab\ncd",
expectedCursor: 5,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.SetCursor2D(10, 10)
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.SetCursor2D(-1, -1)
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\ncd")
textarea.SetCursor2D(0, 0)
},
expectedContent: "ab\ncd",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\ncd")
textarea.SetCursor2D(2, 0)
},
expectedContent: "ab\ncd",
expectedCursor: 2,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\ncd\nef")
textarea.SetCursor2D(2, 1)
},
expectedContent: "ab\ncd\nef",
expectedCursor: 5,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcd\n\nijkl")
textarea.MoveCursorUp()
},
expectedContent: "abcd\n\nijkl",
expectedCursor: 5,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcdef\n老老老")
textarea.MoveCursorLeft()
textarea.MoveCursorUp()
},
expectedContent: "abcdef\n老老老",
expectedCursor: 4,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcdef\n老老老")
textarea.MoveCursorUp()
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorDown()
},
expectedContent: "abcdef\n老老老",
expectedCursor: 13,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcd\nef")
textarea.MoveCursorUp()
textarea.GoToEndOfLine()
textarea.MoveCursorDown()
},
expectedContent: "abcd\nef",
expectedCursor: 7,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abcd")
textarea.MoveCursorUp()
},
expectedContent: "abcd",
expectedCursor: 4,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abcdefg`)
textarea.Clear()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abcdefg`)
textarea.Clear()
},
expectedContent: "",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc def`)
textarea.MoveCursorLeft()
textarea.BackSpaceWord()
},
expectedContent: "abc f",
expectedCursor: 4,
expectedClipboard: "de",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc def `)
textarea.BackSpaceWord()
},
expectedContent: "abc ",
expectedCursor: 5,
expectedClipboard: "def ",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc def\nghi")
textarea.BackSpaceWord()
},
expectedContent: "abc def\n",
expectedCursor: 8,
expectedClipboard: "ghi",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc def\nghi")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.BackSpaceWord()
},
expectedContent: "abc defghi",
expectedCursor: 7,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc(def)`)
textarea.BackSpaceWord()
},
expectedContent: "abc(def",
expectedCursor: 7,
expectedClipboard: ")",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc(def`)
textarea.BackSpaceWord()
},
expectedContent: "abc(",
expectedCursor: 4,
expectedClipboard: "def",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc`)
textarea.GoToStartOfLine()
textarea.BackSpaceWord()
},
expectedContent: "abc",
expectedCursor: 0,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc`)
textarea.Yank()
},
expectedContent: "abc",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc def`)
textarea.DeleteToStartOfLine()
textarea.Yank()
textarea.Yank()
},
expectedContent: "abc defabc def",
expectedCursor: 14,
expectedClipboard: "abc def",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc\ndef")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorUp()
textarea.DeleteToEndOfLine()
},
expectedContent: "a\ndef",
expectedCursor: 1,
expectedClipboard: "bc",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc\ndef")
textarea.MoveCursorUp()
textarea.DeleteToEndOfLine()
},
expectedContent: "abcdef",
expectedCursor: 3,
expectedClipboard: "",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc def`)
textarea.BackSpaceWord()
textarea.Yank()
textarea.Yank()
},
expectedContent: "abc defdef",
expectedCursor: 10,
expectedClipboard: "def",
},
{
actions: func(textarea *TextArea) {
textarea.TypeString(`abc def`)
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.DeleteToEndOfLine()
textarea.Yank()
textarea.Yank()
},
expectedContent: "abc defef",
expectedCursor: 9,
expectedClipboard: "ef",
},
}
for i, test := range tests {
t.Run(strconv.Itoa(i+1), func(t *testing.T) {
textarea := &TextArea{}
if test.wrapWidth > 0 {
textarea.AutoWrap = true
textarea.AutoWrapWidth = test.wrapWidth
}
test.actions(textarea)
assert.EqualValues(t, test.expectedContent, textarea.GetUnwrappedContent())
assert.EqualValues(t, test.expectedCursor, textarea.cursor)
assert.EqualValues(t, test.expectedClipboard, textarea.clipboard)
})
}
}
func TestGetCursorXY(t *testing.T) {
tests := []struct {
actions func(*TextArea)
wrapWidth int
expectedX int
expectedY int
}{
{
actions: func(textarea *TextArea) {
// do nothing
},
expectedX: 0,
expectedY: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("\n")
},
expectedX: 0,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("\na")
},
expectedX: 1,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("\n")
textarea.MoveCursorUp()
},
expectedX: 0,
expectedY: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("\n\n")
textarea.MoveCursorUp()
},
expectedX: 0,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\ncd")
},
expectedX: 2,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\n")
},
expectedX: 0,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\n")
textarea.MoveCursorLeft()
},
expectedX: 2,
expectedY: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\n\n")
},
expectedX: 0,
expectedY: 2,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("ab\n\n")
textarea.MoveCursorLeft()
},
expectedX: 0,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeCharacter("漢")
textarea.TypeCharacter("字")
},
expectedX: 4,
expectedY: 0,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc de")
textarea.MoveCursorLeft()
},
wrapWidth: 4,
expectedX: 1,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc de")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
},
wrapWidth: 4,
expectedX: 0,
expectedY: 1,
},
{
actions: func(textarea *TextArea) {
textarea.TypeString("abc de")
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
textarea.MoveCursorLeft()
},
wrapWidth: 4,
expectedX: 3,
expectedY: 0,
},
}
for i, test := range tests {
t.Run(strconv.Itoa(i+1), func(t *testing.T) {
textarea := &TextArea{}
if test.wrapWidth > 0 {
textarea.AutoWrap = true
textarea.AutoWrapWidth = test.wrapWidth
}
test.actions(textarea)
x, y := textarea.GetCursorXY()
assert.EqualValues(t, test.expectedX, x)
assert.EqualValues(t, test.expectedY, y)
// As a sanity check, test that setting the cursor back to (x, y) results in the same cursor position:
cursor := textarea.cursor
textarea.SetCursor2D(x, y)
assert.EqualValues(t, cursor, textarea.cursor)
})
}
}
func Test_AutoWrapContent(t *testing.T) {
tests := []struct {
name string
content string
autoWrapWidth int
expectedWrappedContent string
expectedSoftLineBreaks []int
}{
{
name: "empty content",
content: "",
autoWrapWidth: 7,
expectedWrappedContent: "",
expectedSoftLineBreaks: []int{},
},
{
name: "no wrapping necessary",
content: "abcde",
autoWrapWidth: 7,
expectedWrappedContent: "abcde",
expectedSoftLineBreaks: []int{},
},
{
name: "wrap at whitespace",
content: "abcde xyz",
autoWrapWidth: 7,
expectedWrappedContent: "abcde \nxyz",
expectedSoftLineBreaks: []int{6},
},
{
name: "take wide characters into account",
content: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 x y", // the flag has a width of 2
autoWrapWidth: 7,
expectedWrappedContent: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 x \ny",
expectedSoftLineBreaks: []int{60},
},
{
name: "take wide characters into account at line end",
content: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿", // the flag has a width of 2
autoWrapWidth: 7,
expectedWrappedContent: "🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁥󠁮󠁧󠁿 \n🏴󠁧󠁢󠁥󠁮󠁧󠁿",
expectedSoftLineBreaks: []int{58},
},
{
name: "lots of whitespace is preserved at end of line",
content: "abcde xyz",
autoWrapWidth: 7,
expectedWrappedContent: "abcde \nxyz",
expectedSoftLineBreaks: []int{11},
},
{
name: "don't wrap inside long word when there's no whitespace",
content: "abc defghijklmn opq",
autoWrapWidth: 7,
expectedWrappedContent: "abc \ndefghijklmn \nopq",
expectedSoftLineBreaks: []int{4, 16},
},
{
name: "don't break at space after footnote symbol",
content: "abc\n[1]: https://long/link\ndef",
autoWrapWidth: 7,
expectedWrappedContent: "abc\n[1]: https://long/link\ndef",
expectedSoftLineBreaks: []int{},
},
{
name: "don't break at space after footnote symbol at soft line start",
content: "abc def [1]: https://long/link\nghi",
autoWrapWidth: 7,
expectedWrappedContent: "abc def \n[1]: https://long/link\nghi",
expectedSoftLineBreaks: []int{8},
},
{
name: "do break at subsequent space after footnote symbol",
content: "abc\n[1]: normal text follows\ndef",
autoWrapWidth: 7,
expectedWrappedContent: "abc\n[1]: normal \ntext \nfollows\ndef",
expectedSoftLineBreaks: []int{16, 21},
},
{
name: "don't break at space after trailer at beginning of message",
content: "Signed-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
autoWrapWidth: 10,
expectedWrappedContent: "Signed-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
expectedSoftLineBreaks: []int{},
},
{
name: "don't break at space after trailer in a trailer block at the end of a message",
content: "abc\n\nSigned-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nSigned-off-by: John Doe <john@doe.com>\nDepends-on: Some dependency with spaces\n",
expectedSoftLineBreaks: []int{},
},
{
name: "don't break at space after trailer with URL value",
content: "abc\n\nBug: https://example.com/a/very/long/path\nIssue: http://example.com/a/very/long/path\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nBug: https://example.com/a/very/long/path\nIssue: http://example.com/a/very/long/path\n",
expectedSoftLineBreaks: []int{},
},
{
name: "do break at space if trailer is not in a trailer block at the end",
content: "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\nMore text here\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nSigned-off-by: \nJohn Doe \n<john@doe.com>\n\nMore text \nhere\n",
expectedSoftLineBreaks: []int{20, 29, 55},
},
{
// Each line in the trailer block is judged on its own, so a line
// that isn't recognized as a trailer wraps without affecting the
// real trailers around it.
name: "keep a trailer next to a non-trailer line in the same block",
content: "abc\n\nFixes: a long description that wraps\nSigned-off-by: John Doe <john@doe.com>\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nFixes: a \nlong \ndescription \nthat wraps\nSigned-off-by: John Doe <john@doe.com>\n",
expectedSoftLineBreaks: []int{14, 19, 31},
},
{
name: "don't break at space after trailer when the block ends in a blank line",
content: "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nSigned-off-by: John Doe <john@doe.com>\n\n",
expectedSoftLineBreaks: []int{},
},
{
name: "do break normal text after non-hyphenated key",
content: "However: in this commit blah blah blah\n",
autoWrapWidth: 10,
expectedWrappedContent: "However: \nin this \ncommit \nblah blah \nblah\n",
expectedSoftLineBreaks: []int{9, 17, 24, 34},
},
{
name: "do break at space after trailer if there is no space after the colon",
content: "abc\n\nSigned-off-by:John Doe <john@doe.com>\n",
autoWrapWidth: 10,
expectedWrappedContent: "abc\n\nSigned-off-by:John \nDoe \n<john@doe.com>\n",
expectedSoftLineBreaks: []int{24, 28},
},
{
name: "hard line breaks",
content: "abc\ndef\n",
autoWrapWidth: 7,
expectedWrappedContent: "abc\ndef\n",
expectedSoftLineBreaks: []int{},
},
{
name: "mixture of hard and soft line breaks",
content: "abc def ghi jkl mno\npqr stu vwx yz\n",
autoWrapWidth: 7,
expectedWrappedContent: "abc def \nghi jkl \nmno\npqr stu \nvwx yz\n",
expectedSoftLineBreaks: []int{8, 16, 28},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
textArea := &TextArea{content: tt.content, AutoWrapWidth: tt.autoWrapWidth, AutoWrap: true}
cells, softLineBreakIndices := contentToCells(tt.content, tt.autoWrapWidth)
textArea.cells = cells
if !reflect.DeepEqual(textArea.GetContent(), tt.expectedWrappedContent) {
t.Errorf("autoWrapContentImpl() wrappedContent = %v, expected %v", textArea.GetContent(), tt.expectedWrappedContent)
}
if !reflect.DeepEqual(softLineBreakIndices, tt.expectedSoftLineBreaks) {
t.Errorf("autoWrapContentImpl() softLineBreakIndices = %v, expected %v", softLineBreakIndices, tt.expectedSoftLineBreaks)
}
// As a sanity check, run through all characters of the original content,
// convert the cursor to the wrapped cursor, and check that the character
// in the wrapped content at that position is the same:
origCursor := 0
for _, chr := range stringToGraphemes(tt.content) {
wrappedIndex := textArea.contentCursorToCellCursor(origCursor)
if chr != textArea.cells[wrappedIndex].char {
t.Errorf("Runes in orig content and wrapped content don't match at %d: expected %v, got %v", origCursor, chr, textArea.cells[wrappedIndex].char)
}
// Also, check that converting the wrapped position back to the
// orig position yields the original value again:
origIndexAgain := textArea.cellCursorToContentCursor(wrappedIndex)
if origCursor != origIndexAgain {
t.Errorf("wrappedCursorToOrigCursor doesn't yield original position: expected %d, got %d", origCursor, origIndexAgain)
}
origCursor += len(chr)
}
})
}
}
var testContent = `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")
}
}