@@ -14,7 +14,7 @@ import (
14
14
type LineBuffer struct {
15
15
line string // underlying string with ansi codes. utf-8 encoded bytes
16
16
lineNoAnsi string // line without ansi codes. utf-8 encoded bytes
17
- lineNoAnsiRuneWidths []uint8 // terminal cell widths
17
+ lineNoAnsiRuneWidths []uint8 // packed terminal cell widths, 4 widths per byte (2 bits each)
18
18
ansiCodeIndexes [][]uint32 // slice of startByte, endByte indexes of ansi codes
19
19
numNoAnsiRunes int // number of runes in lineNoAnsi
20
20
totalWidth int // total width in terminal cells
@@ -73,15 +73,24 @@ func New(line string) LineBuffer {
73
73
lb .sparseRuneIdxToNoAnsiByteOffset = make ([]uint32 , sparseLen )
74
74
lb .sparseLineNoAnsiCumRuneWidths = make ([]uint32 , sparseLen )
75
75
76
- lb .lineNoAnsiRuneWidths = make ([]uint8 , numRunes )
76
+ // calculate size needed for packed rune widths (4 widths per byte)
77
+ packedLen := (numRunes + 3 ) / 4
78
+ lb .lineNoAnsiRuneWidths = make ([]uint8 , packedLen )
77
79
78
80
var currentOffset uint32
79
81
var cumWidth uint32
80
82
runeIdx := 0
81
83
for byteOffset := 0 ; byteOffset < len (lb .lineNoAnsi ); {
82
84
r , runeNumBytes := utf8 .DecodeRuneInString (lb .lineNoAnsi [byteOffset :])
83
85
width := uint8 (runewidth .RuneWidth (r ))
84
- lb .lineNoAnsiRuneWidths [runeIdx ] = width
86
+
87
+ // pack 4 widths per byte (2 bits each)
88
+ packedIdx := runeIdx / 4
89
+ bitPos := (runeIdx % 4 ) * 2
90
+ // clear the 2 bits at the position and set the new width
91
+ lb .lineNoAnsiRuneWidths [packedIdx ] &= ^ (uint8 (3 ) << bitPos )
92
+ lb .lineNoAnsiRuneWidths [packedIdx ] |= width << bitPos
93
+
85
94
cumWidth += uint32 (width )
86
95
if runeIdx % lb .sparsity == 0 {
87
96
lb .sparseRuneIdxToNoAnsiByteOffset [runeIdx / lb .sparsity ] = currentOffset
@@ -123,7 +132,7 @@ func (l LineBuffer) Take(
123
132
widthToLeft = min (widthToLeft , l .Width ())
124
133
startRuneIdx := l .findRuneIndexWithWidthToLeft (widthToLeft )
125
134
126
- if startRuneIdx >= len ( l . lineNoAnsiRuneWidths ) || takeWidth == 0 {
135
+ if startRuneIdx >= l . numNoAnsiRunes || takeWidth == 0 {
127
136
return "" , 0
128
137
}
129
138
@@ -133,9 +142,9 @@ func (l LineBuffer) Take(
133
142
startByteOffset := l .getByteOffsetAtRuneIdx (startRuneIdx )
134
143
135
144
runesWritten := 0
136
- for ; remainingWidth > 0 && leftRuneIdx < len ( l . lineNoAnsiRuneWidths ) ; leftRuneIdx ++ {
145
+ for ; remainingWidth > 0 && leftRuneIdx < l . numNoAnsiRunes ; leftRuneIdx ++ {
137
146
r := l .runeAt (leftRuneIdx )
138
- runeWidth := l .lineNoAnsiRuneWidths [ leftRuneIdx ]
147
+ runeWidth := l .getRuneWidth ( leftRuneIdx )
139
148
if int (runeWidth ) > remainingWidth {
140
149
break
141
150
}
@@ -157,7 +166,7 @@ func (l LineBuffer) Take(
157
166
158
167
// write the subsequent zero-width runes, e.g. the accent on an 'e'
159
168
if result .Len () > 0 {
160
- for ; leftRuneIdx < len ( l . lineNoAnsiRuneWidths ) ; leftRuneIdx ++ {
169
+ for ; leftRuneIdx < l . numNoAnsiRunes ; leftRuneIdx ++ {
161
170
r := l .runeAt (leftRuneIdx )
162
171
if runewidth .RuneWidth (r ) == 0 {
163
172
result .WriteRune (r )
@@ -175,7 +184,7 @@ func (l LineBuffer) Take(
175
184
}
176
185
177
186
// apply left/right line continuation indicators
178
- if len (continuation ) > 0 && (startRuneIdx > 0 || leftRuneIdx < len ( l . lineNoAnsiRuneWidths ) ) {
187
+ if len (continuation ) > 0 && (startRuneIdx > 0 || leftRuneIdx < l . numNoAnsiRunes ) {
179
188
continuationRunes := []rune (continuation )
180
189
181
190
// if more runes to the left of the result, replace start runes with continuation indicator
@@ -184,7 +193,7 @@ func (l LineBuffer) Take(
184
193
}
185
194
186
195
// if more runes to the right, replace final runes in result with continuation indicator
187
- if leftRuneIdx < len ( l . lineNoAnsiRuneWidths ) {
196
+ if leftRuneIdx < l . numNoAnsiRunes {
188
197
res = replaceEndWithContinuation (res , continuationRunes )
189
198
}
190
199
}
@@ -223,7 +232,7 @@ func (l LineBuffer) WrappedLines(
223
232
return []string {l .line }
224
233
}
225
234
226
- lastRuneIdx := len ( l . lineNoAnsiRuneWidths ) - 1
235
+ lastRuneIdx := l . numNoAnsiRunes - 1
227
236
totalWidth := l .getCumulativeWidthAtRuneIdx (lastRuneIdx )
228
237
totalLines := (int (totalWidth ) + width - 1 ) / width
229
238
return getWrappedLines (
@@ -289,6 +298,17 @@ func (l LineBuffer) getByteOffsetAtRuneIdx(runeIdx int) uint32 {
289
298
return byteOffset
290
299
}
291
300
301
+ // getRuneWidth extracts the width of a rune from the packed array
302
+ func (l LineBuffer ) getRuneWidth (runeIdx int ) uint8 {
303
+ if runeIdx < 0 || runeIdx >= l .numNoAnsiRunes {
304
+ return 0
305
+ }
306
+
307
+ packedIdx := runeIdx / 4
308
+ bitPos := (runeIdx % 4 ) * 2
309
+ return (l .lineNoAnsiRuneWidths [packedIdx ] >> bitPos ) & 3
310
+ }
311
+
292
312
func (l LineBuffer ) getCumulativeWidthAtRuneIdx (runeIdx int ) uint32 {
293
313
if runeIdx < 0 {
294
314
return 0
@@ -308,7 +328,7 @@ func (l LineBuffer) getCumulativeWidthAtRuneIdx(runeIdx int) uint32 {
308
328
// sum the widths from the last stored point to our target index
309
329
var additionalWidth uint32
310
330
for i := baseRuneIdx + 1 ; i <= runeIdx ; i ++ {
311
- additionalWidth += uint32 (l .lineNoAnsiRuneWidths [ i ] )
331
+ additionalWidth += uint32 (l .getRuneWidth ( i ) )
312
332
}
313
333
314
334
return l .sparseLineNoAnsiCumRuneWidths [sparseIdx ] + additionalWidth
@@ -319,16 +339,16 @@ func (l LineBuffer) findRuneIndexWithWidthToLeft(widthToLeft int) int {
319
339
if widthToLeft < 0 {
320
340
panic ("widthToLeft less than 0" )
321
341
}
322
- if widthToLeft == 0 || len ( l . lineNoAnsiRuneWidths ) == 0 {
342
+ if widthToLeft == 0 || l . numNoAnsiRunes == 0 {
323
343
return 0
324
344
}
325
345
if widthToLeft > l .Width () {
326
346
panic ("widthToLeft greater than total width" )
327
347
}
328
348
329
- left , right := 0 , len ( l . lineNoAnsiRuneWidths ) - 1
349
+ left , right := 0 , l . numNoAnsiRunes - 1
330
350
if l .getCumulativeWidthAtRuneIdx (right ) < uint32 (widthToLeft ) {
331
- return len ( l . lineNoAnsiRuneWidths )
351
+ return l . numNoAnsiRunes
332
352
}
333
353
334
354
for left < right {
0 commit comments