Skip to content

Commit 372009e

Browse files
author
treilik
committed
handled leading and interposed zero ansi-arguments
Since its valid ANSI to have a in other arguments embedded zero: "\xB1[31;0;33m" we have to transform this into a end sequence and a new sequence for restarting after linebreak. All the while we have to ignore leading zeros, so that: "\xB1[0100m" does not become "\xB1[0m\x1B100m". After talking with muesli: Ignoring Write errors explicitly, maybe to be changed in the future.
1 parent 680d740 commit 372009e

File tree

2 files changed

+84
-26
lines changed

2 files changed

+84
-26
lines changed

wordwrap/wordwrap.go

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,23 @@ type WordWrap struct {
2323
Newline []rune
2424
KeepNewlines bool
2525
HardWrap bool
26-
TabReplace string // since tabs can have differrent lengths, replace them with this when hardwrap is enabled
26+
TabReplace string // since tabs can have different lengths, replace them with this when hardwrap is enabled
2727
PreserveSpaces bool
2828

2929
buf bytes.Buffer // processed and, in line, accepted bytes
3030
space bytes.Buffer // pending continues spaces bytes
3131
word ansi.Buffer // pending continues word bytes
3232

33-
lineLen int // the visible length of the line not accorat for tabs
33+
lineLen int // the visible length of the line not accurate for tabs
3434
ansi bool
3535

36-
wroteBegin bool // mark is since the last newline something has writen to the buffer (for ansi restart)
36+
wroteBegin bool // mark is since the last newline something has written to the buffer (for ansi restart)
3737
lastAnsi bytes.Buffer // hold last active ansi sequence
38+
39+
// the following are used to remove leading zeros from the single arguments of the ansi-sequence, but still detect single zeros:
40+
// \x1B[0031;0000m => \x1B[31;0m
41+
newArgument bool
42+
leadingZero bool
3843
}
3944

4045
// NewWriter returns a new instance of a word-wrapping writer, initialized with
@@ -64,8 +69,8 @@ func String(s string, limit int) string {
6469
return string(Bytes([]byte(s), limit))
6570
}
6671

67-
// HardWrap is a shorthand for declaring a new hardwraping WordWrap instance,
68-
// since varibale length characters can not be hard wraped to a fixed length,
72+
// HardWrap is a shorthand for declaring a new hardwrapping WordWrap instance,
73+
// since variable length characters can not be hard wrapped to a fixed length,
6974
// tabs will be replaced by TabReplace, use according amount of spaces.
7075
func HardWrap(s string, limit int, tabReplace string) string {
7176
f := NewWriter(limit)
@@ -77,22 +82,22 @@ func HardWrap(s string, limit int, tabReplace string) string {
7782
return f.String()
7883
}
7984

80-
// addes pending spaces to the buf(fer) and then resets the space buffer.
85+
// adds pending spaces to the buf(fer) and then resets the space buffer.
8186
func (w *WordWrap) addSpace() {
8287
if w.space.Len() <= w.Limit-w.lineLen {
8388
w.lineLen += w.space.Len()
84-
w.buf.Write(w.space.Bytes())
89+
_, _ = w.buf.Write(w.space.Bytes())
8590
} else {
8691
length := w.space.Len()
8792
first := w.Limit - w.lineLen
88-
w.buf.WriteString(strings.Repeat(" ", first))
93+
_, _ = w.buf.WriteString(strings.Repeat(" ", first))
8994
length -= first
9095
for length >= w.Limit {
91-
w.buf.WriteString("\n" + strings.Repeat(" ", w.Limit))
96+
_, _ = w.buf.WriteString("\n" + strings.Repeat(" ", w.Limit))
9297
length -= w.Limit
9398
}
9499
if length > 0 {
95-
w.buf.WriteString("\n" + strings.Repeat(" ", length))
100+
_, _ = w.buf.WriteString("\n" + strings.Repeat(" ", length))
96101
}
97102
w.lineLen = length
98103
}
@@ -113,10 +118,10 @@ func (w *WordWrap) addNewLine() {
113118
w.addSpace()
114119
}
115120
if w.lastAnsi.Len() != 0 {
116-
// end ansi befor linebreak
117-
w.buf.WriteString("\x1b[0m")
121+
// end ansi before linebreak
122+
_, _ = w.buf.WriteString("\x1B[0m")
118123
}
119-
w.buf.WriteRune('\n')
124+
_, _ = w.buf.WriteRune('\n')
120125
w.lineLen = 0
121126
w.space.Reset()
122127
w.wroteBegin = false
@@ -149,25 +154,60 @@ func (w *WordWrap) Write(b []byte) (int, error) {
149154
for _, c := range s {
150155
// Restart Ansi after line break if there is more text
151156
if !w.wroteBegin && !w.ansi && w.lastAnsi.Len() != 0 {
152-
w.buf.Write(w.lastAnsi.Bytes())
157+
_, _ = w.buf.Write(w.lastAnsi.Bytes())
153158
w.addWord()
154159
}
155160
w.wroteBegin = true
156161
if c == '\x1B' {
157162
// ANSI escape sequence
158-
w.word.WriteRune(c)
159-
w.lastAnsi.WriteRune(c)
163+
_, _ = w.word.WriteRune(c)
164+
_, _ = w.lastAnsi.WriteRune(c)
160165
w.ansi = true
166+
w.newArgument = true
161167
} else if w.ansi {
162-
w.word.WriteRune(c)
163-
w.lastAnsi.WriteRune(c)
168+
169+
// ignore leading zeros but remember single ones.
170+
if c == '0' && w.newArgument {
171+
w.leadingZero = true
172+
continue
173+
}
174+
w.newArgument = false
175+
// if a digit other then zero is encountered reset leading zero since we can ignore the leading zeroes if there where any.
176+
if inGroup([]rune{'1', '2', '3', '4', '5', '6', '7', '8', '9'}, c) {
177+
w.leadingZero = false
178+
}
179+
180+
// check if new ANSI-argument starts
181+
if inGroup([]rune{'[', ';'}, c) {
182+
w.newArgument = true
183+
// if w.leadingZero is here, we know that its a valid zero => reset and restart sequence.
184+
if w.leadingZero {
185+
// since we are still in the middle of the sequence and have reset the last ansi, we have to restart a new sequence:
186+
w.lastAnsi.Reset()
187+
_, _ = w.lastAnsi.WriteString("\x1B[")
188+
w.leadingZero = false
189+
_, _ = w.word.WriteString("0m\x1B[")
190+
// "\x1B[31;0;32m" => "\x1B[31;0m\x1B[32m"
191+
continue // dont write "replace" semicolon
192+
}
193+
}
194+
195+
_, _ = w.lastAnsi.WriteRune(c)
196+
164197
if (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) {
198+
// dont restart lastAnsi since its a end of a sequence. (not in the middle of one)
199+
if w.leadingZero {
200+
_, _ = w.word.WriteRune('0')
201+
202+
w.lastAnsi.Reset()
203+
w.leadingZero = false
204+
}
165205
// ANSI sequence terminated
166206
w.ansi = false
167207
}
168-
if c == 'm' && strings.HasSuffix(w.lastAnsi.String(), "\x1b[0m") {
169-
w.lastAnsi.Reset()
170-
}
208+
209+
_, _ = w.word.WriteRune(c)
210+
171211
} else if inGroup(w.Newline, c) {
172212
// end of current line
173213
// see if we can add the content of the space buffer to the current line
@@ -191,10 +231,10 @@ func (w *WordWrap) Write(b []byte) (int, error) {
191231
// valid breakpoint
192232
w.addSpace()
193233
w.addWord()
194-
w.buf.WriteRune(c)
234+
_, _ = w.buf.WriteRune(c)
195235
} else if w.HardWrap && w.lineLen+w.word.PrintableRuneWidth()+runewidth.RuneWidth(c)+w.space.Len() == w.Limit {
196-
// Word is at the limite -> begin new word
197-
w.word.WriteRune(c)
236+
// Word is at the limit -> begin new word
237+
_, _ = w.word.WriteRune(c)
198238
w.addWord()
199239
} else {
200240
// any other character
@@ -224,13 +264,13 @@ func (w *WordWrap) Close() error {
224264
}
225265

226266
// Bytes returns the word-wrapped result as a byte slice.
227-
// Make sure to have closed the worwrapper, befor calling it.
267+
// Make sure to have closed the wordwrapper, before calling it.
228268
func (w *WordWrap) Bytes() []byte {
229269
return w.buf.Bytes()
230270
}
231271

232272
// String returns the word-wrapped result as a string.
233-
// Make sure to have closed the worwrapper, befor calling it.
273+
// Make sure to have closed the wordwrapper, before calling it.
234274
func (w *WordWrap) String() string {
235275
return w.buf.String()
236276
}

wordwrap/wordwrap_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,24 @@ func TestHardWrap(t *testing.T) {
250250
true,
251251
"",
252252
},
253+
// check if befor zero gets reset and after gets remembered/repeated
254+
{
255+
"\x1b[34mblue\x1b[33;0;31mred\x1b[0m",
256+
"\x1b[34mblue\x1b[33;0m\x1b[31mre\x1b[0m\n\x1b[31md\x1b[0m",
257+
6,
258+
true,
259+
true,
260+
"",
261+
},
262+
// check squash multiple zeros into one and ignore leading zeros
263+
{
264+
"\x1b[034mblue\x1b[33;00000000000000000000;000000000000031mred\x1b[0m",
265+
"\x1b[34mblue\x1b[33;0m\x1b[31mre\x1b[0m\n\x1b[31md\x1b[0m",
266+
6,
267+
true,
268+
true,
269+
"",
270+
},
253271
}
254272
for i, tc := range tt {
255273
f := NewWriter(tc.Limit)

0 commit comments

Comments
 (0)