From f8e59c1465b43c61edb9075f714c9961dd030e76 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Sun, 2 Nov 2025 01:04:54 +0100 Subject: [PATCH] simplify indent-only operation --- indent.go | 183 ++++---------------------------------- indent_test.go | 214 ++++---------------------------------------- lib.go | 10 +-- singleline.go | 36 ++++++++ wrap.go | 130 +++++++++++++++++++++++++++ wrap_test.go | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 442 insertions(+), 368 deletions(-) create mode 100644 singleline.go create mode 100644 wrap.go create mode 100644 wrap_test.go diff --git a/indent.go b/indent.go index 20a081a..713b5b2 100644 --- a/indent.go +++ b/indent.go @@ -1,181 +1,34 @@ package textedit -import "unicode" - -const nonbreakSpace = '\u00a0' - -type wrapIndentState struct { - currentWord []rune - currentLineLength int - multipleLines bool +type indentState struct { + started, lineStarted bool } -func indentLength(i []rune) int { - var l int - for _, ii := range i { - if ii == '\t' { - l += 8 - continue +func indentEdit(first, rest []rune) func(rune, indentState) ([]rune, indentState) { + return func(r rune, s indentState) ([]rune, indentState) { + if r == '\n' { + s.started = true + s.lineStarted = false + return []rune{'\n'}, s } - l++ - } - - return l -} - -func wrapIndentEdit(first, rest []rune, firstWidth, restWidth int) func(rune, wrapIndentState) ([]rune, wrapIndentState) { - firstIndentLength := indentLength(first) - restIndentLength := indentLength(rest) - return func(r rune, state wrapIndentState) ([]rune, wrapIndentState) { var ret []rune - indent := first - il := firstIndentLength - width := firstWidth - if state.multipleLines { - indent = rest - il = restIndentLength - width = restWidth - } - - nw := width <= 0 - cl := state.currentLineLength - wl := len(state.currentWord) - nl := r == '\n' - ws := unicode.IsSpace(r) && r != nonbreakSpace - if nw && nl && wl > 0 { - if cl == 0 { - ret = append(ret, indent...) - } - - if cl > 0 { - ret = append(ret, ' ') - } - - ret = append(ret, state.currentWord...) - ret = append(ret, '\n') - state.currentWord = nil - state.currentLineLength = 0 - state.multipleLines = true - return ret, state - } - - if nw && nl { - ret = append(ret, '\n') - state.currentLineLength = 0 - state.multipleLines = true - return ret, state - } - - if nw && ws && wl > 0 { - if cl == 0 { - ret = append(ret, indent...) - } - - if cl > 0 { - ret = append(ret, ' ') - state.currentLineLength++ - } - - ret = append(ret, state.currentWord...) - state.currentLineLength += wl - state.currentWord = nil - return ret, state - } - - if nw && ws { - return nil, state - } - - if nw { - state.currentWord = append(state.currentWord, r) - return nil, state - } - - if ws && cl > 0 && cl+wl+il+1 > width && wl > 0 { - ret = append(ret, '\n') + if !s.started { + ret = append(ret, first...) + } else if !s.lineStarted { ret = append(ret, rest...) - ret = append(ret, state.currentWord...) - state.currentLineLength = wl - state.multipleLines = true - state.currentWord = nil - return ret, state } - if ws && cl > 0 && cl+il+1 > width { - ret = append(ret, '\n') - state.currentLineLength = 0 - state.multipleLines = true - return ret, state - } - - if ws && cl > 0 && wl > 0 { - ret = append(ret, ' ') - ret = append(ret, state.currentWord...) - state.currentLineLength++ - state.currentLineLength += wl - state.currentWord = nil - return ret, state - } - - if ws && wl > 0 { - ret = append(ret, indent...) - ret = append(ret, state.currentWord...) - state.currentLineLength += wl - state.currentWord = nil - return ret, state - } - - if ws { - return nil, state - } - - state.currentWord = append(state.currentWord, r) - return nil, state + ret = append(ret, r) + s.started = true + s.lineStarted = true + return ret, s } } -func wrapIndentRelease(first, rest []rune, firstWidth, restWidth int) func(wrapIndentState) []rune { - return func(state wrapIndentState) []rune { - if len(state.currentWord) == 0 { - return nil - } - - var ret []rune - indent := first - width := firstWidth - if state.multipleLines { - indent = rest - width = restWidth - } - - if width > 0 && state.currentLineLength > 0 && - state.currentLineLength+len(state.currentWord)+1+len(indent) > width { - ret = append(ret, '\n') - state.currentLineLength = 0 - state.multipleLines = true - } - - if state.multipleLines { - indent = rest - } - - if state.currentLineLength == 0 { - ret = append(ret, indent...) - } - - if state.currentLineLength > 0 { - ret = append(ret, ' ') - } - - ret = append(ret, state.currentWord...) - return ret - } -} - -func wrapIndent(first, rest []rune, firstWidth, restWidth int) Editor { +func indent(first, rest []rune) Editor { return Func( - wrapIndentEdit(first, rest, firstWidth, restWidth), - wrapIndentRelease(first, rest, firstWidth, restWidth), + indentEdit(first, rest), + func(indentState) []rune { return nil }, ) } diff --git a/indent_test.go b/indent_test.go index 7e006fe..2769bd5 100644 --- a/indent_test.go +++ b/indent_test.go @@ -33,7 +33,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("", "")) w.Write([]byte("foo bar\n baz\nqux quux")) w.Flush() - if b.String() != "foo bar\nbaz\nqux quux" { + if b.String() != "foo bar\n baz\nqux quux" { t.Fatal(b.String()) } }) @@ -43,7 +43,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("", "")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "foo bar\nbaz\nqux quux\n" { + if b.String() != "foo bar\n baz\nqux quux\n" { t.Fatal(b.String()) } }) @@ -53,17 +53,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("", "")) w.Write([]byte("foo bar bar foo\n baz\nqux quux\n")) w.Flush() - if b.String() != "foo bar bar foo\nbaz\nqux quux\n" { - t.Fatal(b.String()) - } - }) - - t.Run("indent last word", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.Indent("xxxx", "xxxx")) - w.Write([]byte("foo bar bar foo\n baz\nqux quux\nfoo")) - w.Flush() - if b.String() != "xxxxfoo bar bar foo\nxxxxbaz\nxxxxqux quux\nxxxxfoo" { + if b.String() != "foo bar bar foo\n baz\nqux quux\n" { t.Fatal(b.String()) } }) @@ -75,7 +65,17 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("xx", "xx")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "xxfoo bar\nxxbaz\nxxqux quux\n" { + if b.String() != "xxfoo bar\nxx baz\nxxqux quux\n" { + t.Fatal(b.String()) + } + }) + + t.Run("indent last word", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Indent("xxxx", "xxxx")) + w.Write([]byte("foo bar bar foo\n baz\nqux quux\nfoo")) + w.Flush() + if b.String() != "xxxxfoo bar bar foo\nxxxx baz\nxxxxqux quux\nxxxxfoo" { t.Fatal(b.String()) } }) @@ -85,7 +85,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("", "xx")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "foo bar\nxxbaz\nxxqux quux\n" { + if b.String() != "foo bar\nxx baz\nxxqux quux\n" { t.Fatal(b.String()) } }) @@ -95,7 +95,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("xx", "")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "xxfoo bar\nbaz\nqux quux\n" { + if b.String() != "xxfoo bar\n baz\nqux quux\n" { t.Fatal(b.String()) } }) @@ -105,7 +105,7 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("xx", "xxxx")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "xxfoo bar\nxxxxbaz\nxxxxqux quux\n" { + if b.String() != "xxfoo bar\nxxxx baz\nxxxxqux quux\n" { t.Fatal(b.String()) } }) @@ -115,187 +115,9 @@ func TestIndent(t *testing.T) { w := textedit.New(&b, textedit.Indent("xxxx", "xx")) w.Write([]byte("foo bar\n baz\nqux quux\n")) w.Flush() - if b.String() != "xxxxfoo bar\nxxbaz\nxxqux quux\n" { + if b.String() != "xxxxfoo bar\nxx baz\nxxqux quux\n" { t.Fatal(b.String()) } }) }) - - t.Run("wrap", func(t *testing.T) { - t.Run("same first and rest", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.Wrap(9, 9)) - w.Write([]byte("foo bar\n baz\nqux quux\n")) - w.Flush() - if b.String() != "foo bar\nbaz qux\nquux" { - t.Fatal(b.String()) - } - }) - - t.Run("shorter first", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.Wrap(5, 9)) - w.Write([]byte("foo bar\n baz\nqux quux\n")) - w.Flush() - if b.String() != "foo\nbar baz\nqux quux" { - t.Fatal(b.String()) - } - }) - - t.Run("longer first", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.Wrap(9, 5)) - w.Write([]byte("foo bar\n baz\nqux quux\n")) - w.Flush() - if b.String() != "foo bar\nbaz\nqux\nquux" { - t.Fatal(b.String()) - } - }) - - t.Run("word longer than width", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.Wrap(2, 2)) - w.Write([]byte("foo bar\n baz\nqux quux\n")) - w.Flush() - if b.String() != "foo\nbar\nbaz\nqux\nquux" { - t.Fatal(b.String()) - } - }) - }) - - t.Run("indent and wrap", func(t *testing.T) { - const text = ` - Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant - presence of Scots pine (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and - silver birch (Betula pendula), their canopies creating a mosaic of light and shadow on the - forest floor. The sandy, acidic soils typical of this region support a ground layer rich in - ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves - now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in - sunnier clearings. Closer inspection of the understory reveals common wood sorrel (Oxalis - acetosella) thriving in moister pockets, while various moss species—including the feathery - fronds of Hypnum cupressiforme—carpet fallen logs in varying stages of decay. The drier sections - host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old pines have been - felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their - growth marking the forest's continuous cycle of regeneration in this characteristically glacial - landscape of the North European Plain. - ` - - t.Run("uniform", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent(" ", " ", 120, 120)) - w.Write([]byte(text)) - w.Flush() - const expect = ` - Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine - (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies - creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a - ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves - now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. - Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, - while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying - stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old - pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their - growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the - North European Plain.` - - if "\n"+b.String() != expect { - t.Fatal("\n" + b.String()) - } - }) - - t.Run("classic", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent(" ", "", 120, 120)) - w.Write([]byte(text)) - w.Flush() - const expect = ` - Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine -(Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies -creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a -ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves now -tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. Closer -inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, while various -moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying stages of decay. The -drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old pines have been felled, -pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their growth marking the forest's -continuous cycle of regeneration in this characteristically glacial landscape of the North European Plain.` - - if "\n"+b.String() != expect { - t.Fatal("\n" + b.String()) - } - }) - - t.Run("indent out", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent("", " ", 120, 120)) - w.Write([]byte(text)) - w.Flush() - const expect = ` -Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine (Pinus - sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies - creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a - ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves - now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. - Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, - while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying - stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old - pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their - growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the - North European Plain.` - - if "\n"+b.String() != expect { - t.Fatal("\n" + b.String()) - } - }) - - t.Run("indent out with same width", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent("", " ", 116, 120)) - w.Write([]byte(text)) - w.Flush() - const expect = ` -Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine - (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies - creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a - ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves - now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. - Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, - while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying - stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old - pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their - growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the - North European Plain.` - - if "\n"+b.String() != expect { - t.Fatal("\n" + b.String()) - } - }) - - t.Run("tab is 8", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent("\t", "", 12, 12)) - w.Write([]byte("foo bar\n baz\nqux quux\n")) - w.Flush() - if b.String() != "\tfoo\nbar baz qux\nquux" { - t.Fatal(b.String()) - } - }) - - t.Run("wrap on release", func(t *testing.T) { - var b bytes.Buffer - w := textedit.New(&b, textedit.WrapIndent(" ", " ", 15, 15)) - w.Write([]byte("Some sample text...\n on multiple lines.")) - w.Flush() - - const expect = ` - Some sample - text... on - multiple - lines.` - - if "\n"+b.String() != expect { - t.Fatal("\n" + b.String()) - } - }) - }) } diff --git a/lib.go b/lib.go index 08b6d5e..85a1c98 100644 --- a/lib.go +++ b/lib.go @@ -72,10 +72,9 @@ func Escape(esc rune, chars ...rune) Editor { return escape(esc, chars...) } -// Indent applies indentation to multi-line text. Duplicate, leading, trailing and non-space whitespaces, other -// than the newline character, are collapsed into a single space. +// Indent applies indentation to multi-line text. Whitespaces are preserved. func Indent(first, rest string) Editor { - return wrapIndent([]rune(first), []rune(rest), 0, 0) + return indent([]rune(first), []rune(rest)) } // Wrap wrap wraps multiline text to define maximual text width. Duplicate, leading, trailing and non-space @@ -91,10 +90,7 @@ func WrapIndent(firstIndent, restIndent string, firstWidth, restWidth int) Edito // SingleLine is like Wrap, but with infinite text width. func SingleLine() Editor { - return sequence( - replace("\n", " "), - wrapIndent(nil, nil, 0, 0), - ) + return singleLine() } // New initializes an editing writer. The editor instances will be called in the order they are passed in to diff --git a/singleline.go b/singleline.go new file mode 100644 index 0000000..34f5747 --- /dev/null +++ b/singleline.go @@ -0,0 +1,36 @@ +package textedit + +import "unicode" + +type singleLineState struct { + started bool + currentWord []rune +} + +func singleLineEdit(r rune, s singleLineState) ([]rune, singleLineState) { + if !unicode.IsSpace(r) || r == nonbreakSpace { + s.currentWord = append(s.currentWord, r) + return nil, s + } + + if len(s.currentWord) == 0 { + return nil, s + } + + ret := s.currentWord + if s.started { + ret = append([]rune{' '}, ret...) + } + + s.currentWord = nil + s.started = true + return ret, s +} + +func singleLineReleaseState(s singleLineState) []rune { + return s.currentWord +} + +func singleLine() Editor { + return Func(singleLineEdit, singleLineReleaseState) +} diff --git a/wrap.go b/wrap.go new file mode 100644 index 0000000..0071ae7 --- /dev/null +++ b/wrap.go @@ -0,0 +1,130 @@ +package textedit + +import "unicode" + +const nonbreakSpace = '\u00a0' + +type wrapIndentState struct { + currentWord []rune + currentLineLength int + multipleLines bool +} + +func indentLength(i []rune) int { + var l int + for _, ii := range i { + if ii == '\t' { + l += 8 + continue + } + + l++ + } + + return l +} + +func wrapIndentEdit(first, rest []rune, firstWidth, restWidth int) func(rune, wrapIndentState) ([]rune, wrapIndentState) { + firstIndentLength := indentLength(first) + restIndentLength := indentLength(rest) + return func(r rune, state wrapIndentState) ([]rune, wrapIndentState) { + var ret []rune + indent := first + il := firstIndentLength + width := firstWidth + if state.multipleLines { + indent = rest + il = restIndentLength + width = restWidth + } + + cl := state.currentLineLength + wl := len(state.currentWord) + ws := unicode.IsSpace(r) && r != nonbreakSpace + if ws && cl > 0 && cl+wl+il+1 > width && wl > 0 { + ret = append(ret, '\n') + ret = append(ret, rest...) + ret = append(ret, state.currentWord...) + state.currentLineLength = wl + state.multipleLines = true + state.currentWord = nil + return ret, state + } + + if ws && cl > 0 && cl+il+1 > width { + ret = append(ret, '\n') + state.currentLineLength = 0 + state.multipleLines = true + return ret, state + } + + if ws && cl > 0 && wl > 0 { + ret = append(ret, ' ') + ret = append(ret, state.currentWord...) + state.currentLineLength++ + state.currentLineLength += wl + state.currentWord = nil + return ret, state + } + + if ws && wl > 0 { + ret = append(ret, indent...) + ret = append(ret, state.currentWord...) + state.currentLineLength += wl + state.currentWord = nil + return ret, state + } + + if ws { + return nil, state + } + + state.currentWord = append(state.currentWord, r) + return nil, state + } +} + +func wrapIndentRelease(first, rest []rune, firstWidth, restWidth int) func(wrapIndentState) []rune { + return func(state wrapIndentState) []rune { + if len(state.currentWord) == 0 { + return nil + } + + var ret []rune + indent := first + width := firstWidth + if state.multipleLines { + indent = rest + width = restWidth + } + + if state.currentLineLength > 0 && + state.currentLineLength+len(state.currentWord)+1+len(indent) > width { + ret = append(ret, '\n') + state.currentLineLength = 0 + state.multipleLines = true + } + + if state.multipleLines { + indent = rest + } + + if state.currentLineLength == 0 { + ret = append(ret, indent...) + } + + if state.currentLineLength > 0 { + ret = append(ret, ' ') + } + + ret = append(ret, state.currentWord...) + return ret + } +} + +func wrapIndent(first, rest []rune, firstWidth, restWidth int) Editor { + return Func( + wrapIndentEdit(first, rest, firstWidth, restWidth), + wrapIndentRelease(first, rest, firstWidth, restWidth), + ) +} diff --git a/wrap_test.go b/wrap_test.go new file mode 100644 index 0000000..b33a5d0 --- /dev/null +++ b/wrap_test.go @@ -0,0 +1,237 @@ +package textedit_test + +import ( + "bytes" + "code.squareroundforest.org/arpio/textedit" + "testing" +) + +func TestWrap(t *testing.T) { + t.Run("wrap", func(t *testing.T) { + t.Run("same first and rest", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(9, 9)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo bar\nbaz qux\nquux" { + t.Fatal(b.String()) + } + }) + + t.Run("shorter first", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(5, 9)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo\nbar baz\nqux quux" { + t.Fatal(b.String()) + } + }) + + t.Run("longer first", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(9, 5)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo bar\nbaz\nqux\nquux" { + t.Fatal(b.String()) + } + }) + + t.Run("word longer than width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(2, 2)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo\nbar\nbaz\nqux\nquux" { + t.Fatal(b.String()) + } + }) + + t.Run("zero width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(0, 0)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo\nbar\nbaz\nqux\nquux" { + t.Fatal(b.String()) + } + }) + + t.Run("negative width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.Wrap(-2, -2)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "foo\nbar\nbaz\nqux\nquux" { + t.Fatal(b.String()) + } + }) + }) + + t.Run("indent and wrap", func(t *testing.T) { + const text = ` + Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant + presence of Scots pine (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and + silver birch (Betula pendula), their canopies creating a mosaic of light and shadow on the + forest floor. The sandy, acidic soils typical of this region support a ground layer rich in + ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves + now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in + sunnier clearings. Closer inspection of the understory reveals common wood sorrel (Oxalis + acetosella) thriving in moister pockets, while various moss species—including the feathery + fronds of Hypnum cupressiforme—carpet fallen logs in varying stages of decay. The drier sections + host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old pines have been + felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their + growth marking the forest's continuous cycle of regeneration in this characteristically glacial + landscape of the North European Plain. + ` + + t.Run("uniform", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent(" ", " ", 120, 120)) + w.Write([]byte(text)) + w.Flush() + const expect = ` + Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine + (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies + creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a + ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves + now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. + Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, + while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying + stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old + pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their + growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the + North European Plain.` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("classic", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent(" ", "", 120, 120)) + w.Write([]byte(text)) + w.Flush() + const expect = ` + Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine +(Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies +creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a +ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves now +tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. Closer +inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, while various +moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying stages of decay. The +drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old pines have been felled, +pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their growth marking the forest's +continuous cycle of regeneration in this characteristically glacial landscape of the North European Plain.` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("indent out", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("", " ", 120, 120)) + w.Write([]byte(text)) + w.Flush() + const expect = ` +Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine (Pinus + sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies + creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a + ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves + now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. + Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, + while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying + stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old + pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their + growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the + North European Plain.` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("indent out with same width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("", " ", 116, 120)) + w.Write([]byte(text)) + w.Flush() + const expect = ` +Walking through the mixed forests of Brandenburg in early autumn, one notices the dominant presence of Scots pine + (Pinus sylvestris) interspersed with sessile oak (Quercus petraea) and silver birch (Betula pendula), their canopies + creating a mosaic of light and shadow on the forest floor. The sandy, acidic soils typical of this region support a + ground layer rich in ericaceous plants, particularly bilberry (Vaccinium myrtillus) with its dark-green oval leaves + now tinged with burgundy, and the occasional patch of heather (Calluna vulgaris) persisting in sunnier clearings. + Closer inspection of the understory reveals common wood sorrel (Oxalis acetosella) thriving in moister pockets, + while various moss species—including the feathery fronds of Hypnum cupressiforme—carpet fallen logs in varying + stages of decay. The drier sections host wavy hair-grass (Deschampsia flexuosa) in delicate tufts, and where old + pines have been felled, pioneering stands of downy birch and rowan (Sorbus aucuparia) compete for space, their + growth marking the forest's continuous cycle of regeneration in this characteristically glacial landscape of the + North European Plain.` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("tab is 8", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("\t", "", 12, 12)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "\tfoo\nbar baz qux\nquux" { + t.Fatal(b.String()) + } + }) + + t.Run("wrap on release", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent(" ", " ", 15, 15)) + w.Write([]byte("Some sample text...\n on multiple lines.")) + w.Flush() + + const expect = ` + Some sample + text... on + multiple + lines.` + + if "\n"+b.String() != expect { + t.Fatal("\n" + b.String()) + } + }) + + t.Run("zero width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("xx", "xx", 0, 0)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "xxfoo\nxxbar\nxxbaz\nxxqux\nxxquux" { + t.Fatal(b.String()) + } + }) + + t.Run("negative width", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("xx", "xx", -2, -2)) + w.Write([]byte("foo bar\n baz\nqux quux\n")) + w.Flush() + if b.String() != "xxfoo\nxxbar\nxxbaz\nxxqux\nxxquux" { + t.Fatal(b.String()) + } + }) + + t.Run("last word on the same line", func(t *testing.T) { + var b bytes.Buffer + w := textedit.New(&b, textedit.WrapIndent("xx", "xx", 9, 9)) + w.Write([]byte("foo bar")) + w.Flush() + if b.String() != "xxfoo bar" { + t.Fatal(b.String()) + } + }) + }) +}