simplify indent-only operation
This commit is contained in:
parent
259163c737
commit
f8e59c1465
183
indent.go
183
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 },
|
||||
)
|
||||
}
|
||||
|
||||
214
indent_test.go
214
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())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
10
lib.go
10
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
|
||||
|
||||
36
singleline.go
Normal file
36
singleline.go
Normal file
@ -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)
|
||||
}
|
||||
130
wrap.go
Normal file
130
wrap.go
Normal file
@ -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),
|
||||
)
|
||||
}
|
||||
237
wrap_test.go
Normal file
237
wrap_test.go
Normal file
@ -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())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user