Skip to content

Commit eff0c4c

Browse files
author
treilik
committed
Implemented Hardwrap - but not working yet
1 parent a292a27 commit eff0c4c

File tree

2 files changed

+55
-10
lines changed

2 files changed

+55
-10
lines changed

wordwrap/wordwrap.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strings"
66
"unicode"
77

8+
runewidth "github.com/mattn/go-runewidth"
89
"github.com/muesli/reflow/ansi"
910
)
1011

@@ -21,13 +22,17 @@ type WordWrap struct {
2122
Breakpoints []rune
2223
Newline []rune
2324
KeepNewlines bool
25+
HardBreak bool
2426

25-
buf bytes.Buffer
26-
space bytes.Buffer
27-
word ansi.Buffer
27+
buf bytes.Buffer // processed and in line accepted bytes
28+
space bytes.Buffer // pending continues spaces bytes
29+
word ansi.Buffer // pending continues word bytes
2830

29-
lineLen int
30-
ansi bool
31+
hardWriter ansi.Writer
32+
33+
lineLen int
34+
ansi bool
35+
lastAnsi bytes.Buffer
3136
}
3237

3338
// NewWriter returns a new instance of a word-wrapping writer, initialized with
@@ -57,6 +62,7 @@ func String(s string, limit int) string {
5762
return string(Bytes([]byte(s), limit))
5863
}
5964

65+
// addes pending spaces to the buf(fer) ... and resets the space buffer
6066
func (w *WordWrap) addSpace() {
6167
w.lineLen += w.space.Len()
6268
_, _ = w.buf.Write(w.space.Bytes())
@@ -73,7 +79,11 @@ func (w *WordWrap) addWord() {
7379
}
7480

7581
func (w *WordWrap) addNewLine() {
76-
_, _ = w.buf.WriteRune('\n')
82+
if w.lastAnsi.Len() != 0 {
83+
// end ansi befor linebreak
84+
w.buf.WriteString("\x1b[0m")
85+
}
86+
w.buf.WriteRune('\n')
7787
w.lineLen = 0
7888
w.space.Reset()
7989
}
@@ -99,16 +109,26 @@ func (w *WordWrap) Write(b []byte) (int, error) {
99109
}
100110

101111
for _, c := range s {
112+
// Restart Ansi after line break if there is more text
113+
if w.lineLen == 0 && w.buf.Len() == 0 && w.lastAnsi.Len() != 0 {
114+
w.buf.Write(w.lastAnsi.Bytes())
115+
w.addWord()
116+
}
102117
if c == '\x1B' {
103118
// ANSI escape sequence
104-
_, _ = w.word.WriteRune(c)
119+
w.word.WriteRune(c)
120+
w.lastAnsi.WriteRune(c)
105121
w.ansi = true
106122
} else if w.ansi {
107-
_, _ = w.word.WriteRune(c)
123+
w.word.WriteRune(c)
124+
w.lastAnsi.WriteRune(c)
108125
if (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) {
109126
// ANSI sequence terminated
110127
w.ansi = false
111128
}
129+
if w.lastAnsi.String() == "\x1b[0m" {
130+
w.lastAnsi.Reset()
131+
}
112132
} else if inGroup(w.Newline, c) {
113133
// end of current line
114134
// see if we can add the content of the space buffer to the current line
@@ -132,7 +152,12 @@ func (w *WordWrap) Write(b []byte) (int, error) {
132152
// valid breakpoint
133153
w.addSpace()
134154
w.addWord()
135-
_, _ = w.buf.WriteRune(c)
155+
w.buf.WriteRune(c)
156+
} else if w.HardBreak && w.lineLen+runewidth.RuneWidth(c) > w.Limit {
157+
// Word excends the limite -> break and begin new line/word
158+
w.addWord()
159+
w.addNewLine()
160+
w.buf.WriteRune(c)
136161
} else {
137162
// any other character
138163
_, _ = w.word.WriteRune(c)
@@ -157,11 +182,13 @@ func (w *WordWrap) Close() error {
157182
}
158183

159184
// Bytes returns the word-wrapped result as a byte slice.
185+
// Make sure to have closed the worwrapper, befor calling it
160186
func (w *WordWrap) Bytes() []byte {
161187
return w.buf.Bytes()
162188
}
163189

164190
// String returns the word-wrapped result as a string.
191+
// Make sure to have closed the worwrapper, befor calling it
165192
func (w *WordWrap) String() string {
166193
return w.buf.String()
167194
}

wordwrap/wordwrap_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ func TestWordWrap(t *testing.T) {
1010
Expected string
1111
Limit int
1212
KeepNewlines bool
13+
HardWrap bool
1314
}{
1415
// No-op, should pass through, including trailing whitespace:
1516
{
1617
"foobar\n ",
1718
"foobar\n ",
1819
0,
1920
true,
21+
false,
2022
},
2123
// Nothing to wrap here, should pass through:
2224
{
2325
"foo",
2426
"foo",
2527
4,
2628
true,
29+
false,
2730
},
2831
// A single word that is too long passes through.
2932
// We do not break long words:
@@ -32,27 +35,31 @@ func TestWordWrap(t *testing.T) {
3235
"foobarfoo",
3336
4,
3437
true,
38+
false,
3539
},
3640
// Lines are broken at whitespace:
3741
{
3842
"foo bar foo",
3943
"foo\nbar\nfoo",
4044
4,
4145
true,
46+
false,
4247
},
4348
// A hyphen is a valid breakpoint:
4449
{
4550
"foo-foobar",
4651
"foo-\nfoobar",
4752
4,
4853
true,
54+
false,
4955
},
5056
// Space buffer needs to be emptied before breakpoints:
5157
{
5258
"foo --bar",
5359
"foo --bar",
5460
9,
5561
true,
62+
false,
5663
},
5764
// Lines are broken at whitespace, even if words
5865
// are too long. We do not break words:
@@ -61,13 +68,15 @@ func TestWordWrap(t *testing.T) {
6168
"foo\nbars\nfoobars",
6269
4,
6370
true,
71+
false,
6472
},
6573
// A word that would run beyond the limit is wrapped:
6674
{
6775
"foo bar",
6876
"foo\nbar",
6977
5,
7078
true,
79+
false,
7180
},
7281
// Whitespace that trails a line and fits the width
7382
// passes through, as does whitespace prefixing an
@@ -77,6 +86,7 @@ func TestWordWrap(t *testing.T) {
7786
"foo\nb\t a\n bar",
7887
4,
7988
true,
89+
false,
8090
},
8191
// Trailing whitespace is removed if it doesn't fit the width.
8292
// Runs of whitespace on which a line is broken are removed:
@@ -85,53 +95,61 @@ func TestWordWrap(t *testing.T) {
8595
"foo\nb\nar",
8696
4,
8797
true,
98+
false,
8899
},
89100
// An explicit line break at the end of the input is preserved:
90101
{
91102
"foo bar foo\n",
92103
"foo\nbar\nfoo\n",
93104
4,
94105
true,
106+
false,
95107
},
96108
// Explicit break are always preserved:
97109
{
98110
"\nfoo bar\n\n\nfoo\n",
99111
"\nfoo\nbar\n\n\nfoo\n",
100112
4,
101113
true,
114+
false,
102115
},
103116
// Unless we ask them to be ignored:
104117
{
105118
"\nfoo bar\n\n\nfoo\n",
106119
"foo\nbar\nfoo",
107120
4,
108121
false,
122+
false,
109123
},
110124
// Complete example:
111125
{
112126
" This is a list: \n\n\t* foo\n\t* bar\n\n\n\t* foo \nbar ",
113127
" This\nis a\nlist: \n\n\t* foo\n\t* bar\n\n\n\t* foo\nbar",
114128
6,
115129
true,
130+
false,
116131
},
117132
// ANSI sequence codes don't affect length calculation:
118133
{
119134
"\x1B[38;2;249;38;114mfoo\x1B[0m\x1B[38;2;248;248;242m \x1B[0m\x1B[38;2;230;219;116mbar\x1B[0m",
120135
"\x1B[38;2;249;38;114mfoo\x1B[0m\x1B[38;2;248;248;242m \x1B[0m\x1B[38;2;230;219;116mbar\x1B[0m",
121136
7,
122137
true,
138+
false,
123139
},
124140
// ANSI control codes don't get wrapped, but get finished and again started at each line break:
125141
{
126142
"\x1B[38;2;249;38;114m(\x1B[0m\x1B[38;2;248;248;242mjust another test\x1B[38;2;249;38;114m)\x1B[0m",
127-
"\x1B[38;2;249;38;114m(\x1B[0m\x1B[38;2;248;248;242mjust\x1B[0m\n\x1B[38;2;248;248;242manother\x1B[0m\x1B[0m\n\x1B[38;2;248;248;242mtest\x1B[38;2;249;38;114m)\x1B[0m",
143+
"\x1B[38;2;249;38;114m(\x1B[0m\x1B[38;2;248;248;242mjust\x1B[0m\n\x1B[38;2;248;248;242manother\x1B[0m\n\x1B[38;2;248;248;242mtest\x1B[38;2;249;38;114m)\x1B[0m",
128144
3,
129145
true,
146+
false,
130147
},
131148
}
132149

133150
for i, tc := range tt {
134151
f := NewWriter(tc.Limit)
152+
f.HardBreak = tc.HardWrap
135153
f.KeepNewlines = tc.KeepNewlines
136154

137155
_, err := f.Write([]byte(tc.Input))

0 commit comments

Comments
 (0)