@effect-tui/react 0.16.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/src/codeblock.d.ts +1 -1
- package/dist/src/codeblock.d.ts.map +1 -1
- package/dist/src/codeblock.js +2 -2
- package/dist/src/codeblock.js.map +1 -1
- package/dist/src/components/Markdown.js +3 -3
- package/dist/src/components/Markdown.js.map +1 -1
- package/dist/src/components/MultilineTextInput.d.ts.map +1 -1
- package/dist/src/components/MultilineTextInput.js +133 -305
- package/dist/src/components/MultilineTextInput.js.map +1 -1
- package/dist/src/components/TextInput.d.ts.map +1 -1
- package/dist/src/components/TextInput.js +51 -98
- package/dist/src/components/TextInput.js.map +1 -1
- package/dist/src/components/text-editing.d.ts +61 -0
- package/dist/src/components/text-editing.d.ts.map +1 -1
- package/dist/src/components/text-editing.js +131 -0
- package/dist/src/components/text-editing.js.map +1 -1
- package/dist/src/hosts/base.d.ts +13 -2
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +74 -2
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts +2 -2
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +29 -2
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/canvas.d.ts +22 -2
- package/dist/src/hosts/canvas.d.ts.map +1 -1
- package/dist/src/hosts/canvas.js +99 -31
- package/dist/src/hosts/canvas.js.map +1 -1
- package/dist/src/hosts/codeblock.d.ts +8 -10
- package/dist/src/hosts/codeblock.d.ts.map +1 -1
- package/dist/src/hosts/codeblock.js +36 -33
- package/dist/src/hosts/codeblock.js.map +1 -1
- package/dist/src/hosts/flex-container.d.ts +2 -2
- package/dist/src/hosts/flex-container.d.ts.map +1 -1
- package/dist/src/hosts/flex-container.js +17 -2
- package/dist/src/hosts/flex-container.js.map +1 -1
- package/dist/src/hosts/index.d.ts +1 -1
- package/dist/src/hosts/index.d.ts.map +1 -1
- package/dist/src/hosts/index.js.map +1 -1
- package/dist/src/hosts/overlay-item.d.ts +2 -2
- package/dist/src/hosts/overlay-item.d.ts.map +1 -1
- package/dist/src/hosts/overlay-item.js +7 -2
- package/dist/src/hosts/overlay-item.js.map +1 -1
- package/dist/src/hosts/overlay.d.ts +2 -2
- package/dist/src/hosts/overlay.d.ts.map +1 -1
- package/dist/src/hosts/overlay.js +2 -2
- package/dist/src/hosts/overlay.js.map +1 -1
- package/dist/src/hosts/scroll.d.ts +7 -2
- package/dist/src/hosts/scroll.d.ts.map +1 -1
- package/dist/src/hosts/scroll.js +126 -45
- package/dist/src/hosts/scroll.js.map +1 -1
- package/dist/src/hosts/single-child.d.ts.map +1 -1
- package/dist/src/hosts/single-child.js +2 -0
- package/dist/src/hosts/single-child.js.map +1 -1
- package/dist/src/hosts/spacer.d.ts +1 -1
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +6 -1
- package/dist/src/hosts/spacer.js.map +1 -1
- package/dist/src/hosts/text.d.ts +20 -15
- package/dist/src/hosts/text.d.ts.map +1 -1
- package/dist/src/hosts/text.js +104 -71
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/hosts/zstack.d.ts +2 -2
- package/dist/src/hosts/zstack.d.ts.map +1 -1
- package/dist/src/hosts/zstack.js +7 -2
- package/dist/src/hosts/zstack.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/internal/renderer/index.d.ts.map +1 -1
- package/dist/src/internal/renderer/index.js +41 -16
- package/dist/src/internal/renderer/index.js.map +1 -1
- package/dist/src/internal/renderer/types.d.ts +4 -0
- package/dist/src/internal/renderer/types.d.ts.map +1 -1
- package/dist/src/motion/hooks.d.ts +1 -1
- package/dist/src/motion/hooks.js +1 -1
- package/dist/src/reconciler/host-config.js +2 -2
- package/dist/src/reconciler/host-config.js.map +1 -1
- package/dist/src/reconciler/types.d.ts +5 -1
- package/dist/src/reconciler/types.d.ts.map +1 -1
- package/dist/src/utils/border.d.ts +1 -1
- package/dist/src/utils/border.d.ts.map +1 -1
- package/dist/src/utils/border.js +2 -0
- package/dist/src/utils/border.js.map +1 -1
- package/dist/src/utils/index.d.ts +2 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +2 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/text-layout.d.ts +22 -0
- package/dist/src/utils/text-layout.d.ts.map +1 -0
- package/dist/src/utils/text-layout.js +37 -0
- package/dist/src/utils/text-layout.js.map +1 -0
- package/dist/src/utils/text-wrap.d.ts +26 -1
- package/dist/src/utils/text-wrap.d.ts.map +1 -1
- package/dist/src/utils/text-wrap.js +106 -11
- package/dist/src/utils/text-wrap.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/codeblock.tsx +2 -2
- package/src/components/Markdown.tsx +3 -3
- package/src/components/MultilineTextInput.tsx +138 -344
- package/src/components/TextInput.tsx +54 -99
- package/src/components/text-editing.ts +180 -0
- package/src/hosts/base.ts +86 -3
- package/src/hosts/box.ts +37 -2
- package/src/hosts/canvas.ts +120 -31
- package/src/hosts/codeblock.ts +46 -33
- package/src/hosts/flex-container.ts +21 -2
- package/src/hosts/index.ts +1 -1
- package/src/hosts/overlay-item.ts +8 -2
- package/src/hosts/overlay.ts +2 -2
- package/src/hosts/scroll.ts +142 -45
- package/src/hosts/single-child.ts +2 -0
- package/src/hosts/spacer.ts +6 -1
- package/src/hosts/text.ts +122 -75
- package/src/hosts/zstack.ts +7 -2
- package/src/index.ts +1 -1
- package/src/internal/renderer/index.ts +53 -20
- package/src/internal/renderer/types.ts +4 -0
- package/src/motion/hooks.ts +1 -1
- package/src/reconciler/host-config.ts +2 -2
- package/src/reconciler/types.ts +7 -1
- package/src/utils/border.ts +11 -1
- package/src/utils/index.ts +15 -1
- package/src/utils/text-layout.ts +65 -0
- package/src/utils/text-wrap.ts +135 -13
package/src/utils/index.ts
CHANGED
|
@@ -20,5 +20,19 @@ export {
|
|
|
20
20
|
styleSpecFromProps,
|
|
21
21
|
toColorValue,
|
|
22
22
|
} from "./styles.js"
|
|
23
|
-
export {
|
|
23
|
+
export {
|
|
24
|
+
buildTextLayout,
|
|
25
|
+
type LineLayout,
|
|
26
|
+
type TextLayout,
|
|
27
|
+
type TextLayoutOptions,
|
|
28
|
+
type VisualLine,
|
|
29
|
+
} from "./text-layout.js"
|
|
30
|
+
export {
|
|
31
|
+
splitSpansByNewline,
|
|
32
|
+
spansDisplayWidth,
|
|
33
|
+
wrapLineWithRanges,
|
|
34
|
+
wrapSpans,
|
|
35
|
+
wrapSpansByLine,
|
|
36
|
+
wrapText,
|
|
37
|
+
} from "./text-wrap.js"
|
|
24
38
|
export { isWhitespace, matchNextWord, matchPrevWord, splitWords } from "./word-boundaries.js"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { displayWidth, graphemes } from "@effect-tui/core"
|
|
2
|
+
import { wrapLineWithRanges } from "./text-wrap.js"
|
|
3
|
+
|
|
4
|
+
export type VisualLine = {
|
|
5
|
+
logicalRow: number
|
|
6
|
+
startCol: number
|
|
7
|
+
endCol: number
|
|
8
|
+
text: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type LineLayout = {
|
|
12
|
+
graphemeList: string[]
|
|
13
|
+
widths: number[]
|
|
14
|
+
prefixWidths: number[]
|
|
15
|
+
visualLines: VisualLine[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type TextLayout = {
|
|
19
|
+
lines: LineLayout[]
|
|
20
|
+
allVisualLines: VisualLine[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TextLayoutOptions = {
|
|
24
|
+
wrap?: boolean
|
|
25
|
+
preserveWhitespace?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildTextLayout(text: string, maxWidth: number, options?: TextLayoutOptions): TextLayout {
|
|
29
|
+
const logicalLines = text.split("\n")
|
|
30
|
+
const wrap = options?.wrap ?? true
|
|
31
|
+
const preserveWhitespace = options?.preserveWhitespace ?? false
|
|
32
|
+
const lines: LineLayout[] = []
|
|
33
|
+
const allVisualLines: VisualLine[] = []
|
|
34
|
+
|
|
35
|
+
for (let row = 0; row < logicalLines.length; row++) {
|
|
36
|
+
const line = logicalLines[row]
|
|
37
|
+
const graphemeList = graphemes(line)
|
|
38
|
+
const widths = graphemeList.map((g) => displayWidth(g))
|
|
39
|
+
const prefixWidths: number[] = [0]
|
|
40
|
+
for (let i = 0; i < widths.length; i++) {
|
|
41
|
+
prefixWidths.push(prefixWidths[i] + widths[i])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const visualLines: VisualLine[] = wrap
|
|
45
|
+
? wrapLineWithRanges(line, maxWidth, { preserveWhitespace }).map((range) => ({
|
|
46
|
+
logicalRow: row,
|
|
47
|
+
startCol: range.startCol,
|
|
48
|
+
endCol: range.endCol,
|
|
49
|
+
text: range.text,
|
|
50
|
+
}))
|
|
51
|
+
: [
|
|
52
|
+
{
|
|
53
|
+
logicalRow: row,
|
|
54
|
+
startCol: 0,
|
|
55
|
+
endCol: graphemeList.length,
|
|
56
|
+
text: line,
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
lines.push({ graphemeList, widths, prefixWidths, visualLines })
|
|
61
|
+
allVisualLines.push(...visualLines)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { lines, allVisualLines }
|
|
65
|
+
}
|
package/src/utils/text-wrap.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { displayWidth } from "@effect-tui/core"
|
|
1
|
+
import { displayWidth, graphemes } from "@effect-tui/core"
|
|
2
2
|
import { isWhitespace, splitWords } from "./word-boundaries.js"
|
|
3
3
|
|
|
4
4
|
type SpanLike = { text: string }
|
|
@@ -6,8 +6,11 @@ type SpanLike = { text: string }
|
|
|
6
6
|
type TokenSpec<T> = {
|
|
7
7
|
text: string
|
|
8
8
|
width: number
|
|
9
|
+
graphemeCount: number
|
|
9
10
|
isWhitespace: boolean
|
|
10
|
-
|
|
11
|
+
startCol?: number
|
|
12
|
+
segments?: string[]
|
|
13
|
+
make: (text: string, segmentStart: number, segmentEnd: number) => T
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
type LineToken<T> = {
|
|
@@ -16,8 +19,13 @@ type LineToken<T> = {
|
|
|
16
19
|
isWhitespace: boolean
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
type WrapOptions = {
|
|
22
|
+
export type WrapOptions = {
|
|
20
23
|
trimTrailingWhitespace?: boolean
|
|
24
|
+
preserveWhitespace?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function spansDisplayWidth<T extends { text: string }>(spans: T[]): number {
|
|
28
|
+
return spans.reduce((sum, span) => sum + displayWidth(span.text), 0)
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapOptions): T[][] {
|
|
@@ -40,8 +48,15 @@ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapO
|
|
|
40
48
|
lineWidth = 0
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
const addToken = (
|
|
44
|
-
|
|
51
|
+
const addToken = (
|
|
52
|
+
token: TokenSpec<T>,
|
|
53
|
+
text: string,
|
|
54
|
+
width: number,
|
|
55
|
+
isWhitespace: boolean,
|
|
56
|
+
segmentStart: number,
|
|
57
|
+
segmentEnd: number,
|
|
58
|
+
) => {
|
|
59
|
+
lines[lines.length - 1].push({ value: token.make(text, segmentStart, segmentEnd), width, isWhitespace })
|
|
45
60
|
lineWidth += width
|
|
46
61
|
}
|
|
47
62
|
|
|
@@ -51,11 +66,12 @@ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapO
|
|
|
51
66
|
const isWs = token.isWhitespace
|
|
52
67
|
|
|
53
68
|
if (lineWidth + tokenWidth <= maxWidth) {
|
|
54
|
-
|
|
69
|
+
const start = token.startCol ?? 0
|
|
70
|
+
addToken(token, token.text, tokenWidth, isWs, start, start + token.graphemeCount)
|
|
55
71
|
continue
|
|
56
72
|
}
|
|
57
73
|
|
|
58
|
-
if (isWs) {
|
|
74
|
+
if (isWs && !options?.preserveWhitespace) {
|
|
59
75
|
// Skip whitespace at line break
|
|
60
76
|
continue
|
|
61
77
|
}
|
|
@@ -65,7 +81,8 @@ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapO
|
|
|
65
81
|
if (lines[lines.length - 1].length > 0) {
|
|
66
82
|
startNewLine()
|
|
67
83
|
}
|
|
68
|
-
|
|
84
|
+
const start = token.startCol ?? 0
|
|
85
|
+
addToken(token, token.text, tokenWidth, isWs, start, start + token.graphemeCount)
|
|
69
86
|
continue
|
|
70
87
|
}
|
|
71
88
|
|
|
@@ -75,26 +92,32 @@ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapO
|
|
|
75
92
|
startNewLine()
|
|
76
93
|
}
|
|
77
94
|
|
|
95
|
+
const segments = token.segments ?? Array.from(token.text)
|
|
96
|
+
const tokenStart = token.startCol ?? 0
|
|
78
97
|
let segment = ""
|
|
79
98
|
let segmentWidth = 0
|
|
99
|
+
let segmentStartOffset = 0
|
|
100
|
+
let segmentOffset = 0
|
|
80
101
|
|
|
81
|
-
for (const ch of
|
|
102
|
+
for (const ch of segments) {
|
|
82
103
|
const chWidth = displayWidth(ch)
|
|
83
104
|
if (lineWidth + segmentWidth + chWidth > maxWidth && (segment || lineWidth > 0)) {
|
|
84
105
|
if (segment) {
|
|
85
|
-
addToken(token, segment, segmentWidth, false)
|
|
106
|
+
addToken(token, segment, segmentWidth, false, tokenStart + segmentStartOffset, tokenStart + segmentOffset)
|
|
86
107
|
}
|
|
87
108
|
startNewLine()
|
|
88
109
|
segment = ch
|
|
89
110
|
segmentWidth = chWidth
|
|
111
|
+
segmentStartOffset = segmentOffset
|
|
90
112
|
} else {
|
|
91
113
|
segment += ch
|
|
92
114
|
segmentWidth += chWidth
|
|
93
115
|
}
|
|
116
|
+
segmentOffset += 1
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
if (segment) {
|
|
97
|
-
addToken(token, segment, segmentWidth, false)
|
|
120
|
+
addToken(token, segment, segmentWidth, false, tokenStart + segmentStartOffset, tokenStart + segmentOffset)
|
|
98
121
|
}
|
|
99
122
|
}
|
|
100
123
|
|
|
@@ -106,25 +129,121 @@ function wrapTokens<T>(tokens: TokenSpec<T>[], maxWidth: number, options?: WrapO
|
|
|
106
129
|
return lines.length > 0 ? lines.map((line) => line.map((token) => token.value)) : [[]]
|
|
107
130
|
}
|
|
108
131
|
|
|
132
|
+
export type WrappedLineRange = {
|
|
133
|
+
text: string
|
|
134
|
+
startCol: number
|
|
135
|
+
endCol: number
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Wrap a single line into visual lines with grapheme ranges.
|
|
140
|
+
*/
|
|
141
|
+
export function wrapLineWithRanges(line: string, maxWidth: number, options?: WrapOptions): WrappedLineRange[] {
|
|
142
|
+
if (line === "") {
|
|
143
|
+
return [{ text: "", startCol: 0, endCol: 0 }]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const tokens: Array<TokenSpec<WrappedLineRange>> = []
|
|
147
|
+
let col = 0
|
|
148
|
+
for (const token of splitWords(line)) {
|
|
149
|
+
if (!token) continue
|
|
150
|
+
const segments = graphemes(token)
|
|
151
|
+
const graphemeCount = segments.length
|
|
152
|
+
const startCol = col
|
|
153
|
+
tokens.push({
|
|
154
|
+
text: token,
|
|
155
|
+
width: displayWidth(token),
|
|
156
|
+
graphemeCount,
|
|
157
|
+
startCol,
|
|
158
|
+
segments,
|
|
159
|
+
isWhitespace: isWhitespace(token),
|
|
160
|
+
make: (text, segmentStart, segmentEnd) => ({ text, startCol: segmentStart, endCol: segmentEnd }),
|
|
161
|
+
})
|
|
162
|
+
col += graphemeCount
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const wrapped = wrapTokens(tokens, maxWidth, options)
|
|
166
|
+
const lines: WrappedLineRange[] = []
|
|
167
|
+
|
|
168
|
+
for (const lineTokens of wrapped) {
|
|
169
|
+
if (lineTokens.length === 0) {
|
|
170
|
+
lines.push({ text: "", startCol: 0, endCol: 0 })
|
|
171
|
+
continue
|
|
172
|
+
}
|
|
173
|
+
lines.push({
|
|
174
|
+
text: lineTokens.map((token) => token.text).join(""),
|
|
175
|
+
startCol: lineTokens[0].startCol,
|
|
176
|
+
endCol: lineTokens[lineTokens.length - 1].endCol,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lines.length > 0 ? lines : [{ text: "", startCol: 0, endCol: 0 }]
|
|
181
|
+
}
|
|
182
|
+
|
|
109
183
|
/**
|
|
110
184
|
* Wrap spans into lines, breaking at word boundaries.
|
|
111
185
|
* Preserves span styling by cloning span objects with updated text.
|
|
112
186
|
*/
|
|
113
|
-
export function wrapSpans<T extends SpanLike>(spans: T[], maxWidth: number): T[][] {
|
|
187
|
+
export function wrapSpans<T extends SpanLike>(spans: T[], maxWidth: number, options?: WrapOptions): T[][] {
|
|
114
188
|
const tokens: Array<TokenSpec<T>> = []
|
|
115
189
|
for (const span of spans) {
|
|
116
190
|
for (const token of splitWords(span.text)) {
|
|
117
191
|
if (!token) continue
|
|
192
|
+
const segments = graphemes(token)
|
|
118
193
|
tokens.push({
|
|
119
194
|
text: token,
|
|
120
195
|
width: displayWidth(token),
|
|
196
|
+
graphemeCount: segments.length,
|
|
197
|
+
segments,
|
|
121
198
|
isWhitespace: isWhitespace(token),
|
|
122
199
|
make: (text) => ({ ...span, text }),
|
|
123
200
|
})
|
|
124
201
|
}
|
|
125
202
|
}
|
|
126
203
|
|
|
127
|
-
return wrapTokens(tokens, maxWidth)
|
|
204
|
+
return wrapTokens(tokens, maxWidth, options)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Split spans into logical lines at newline boundaries.
|
|
209
|
+
* Preserves empty lines.
|
|
210
|
+
*/
|
|
211
|
+
export function splitSpansByNewline<T extends SpanLike>(spans: T[]): T[][] {
|
|
212
|
+
const lines: T[][] = [[]]
|
|
213
|
+
|
|
214
|
+
for (const span of spans) {
|
|
215
|
+
if (!span.text) continue
|
|
216
|
+
const parts = span.text.split("\n")
|
|
217
|
+
for (let i = 0; i < parts.length; i++) {
|
|
218
|
+
if (i > 0) {
|
|
219
|
+
lines.push([])
|
|
220
|
+
}
|
|
221
|
+
const part = parts[i]
|
|
222
|
+
if (part) {
|
|
223
|
+
lines[lines.length - 1].push({ ...span, text: part })
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return lines.length > 0 ? lines : [[]]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Wrap spans per logical line (newline-aware).
|
|
233
|
+
*/
|
|
234
|
+
export function wrapSpansByLine<T extends SpanLike>(spans: T[], maxWidth: number, options?: WrapOptions): T[][] {
|
|
235
|
+
const logicalLines = splitSpansByNewline(spans)
|
|
236
|
+
const lines: T[][] = []
|
|
237
|
+
|
|
238
|
+
for (const line of logicalLines) {
|
|
239
|
+
if (line.length === 0) {
|
|
240
|
+
lines.push([])
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
lines.push(...wrapSpans(line, maxWidth, options))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return lines.length > 0 ? lines : [[]]
|
|
128
247
|
}
|
|
129
248
|
|
|
130
249
|
/**
|
|
@@ -141,9 +260,12 @@ export function wrapText(text: string, maxWidth: number): string[] {
|
|
|
141
260
|
const tokens: Array<TokenSpec<string>> = []
|
|
142
261
|
for (const token of splitWords(rawLine)) {
|
|
143
262
|
if (!token) continue
|
|
263
|
+
const segments = graphemes(token)
|
|
144
264
|
tokens.push({
|
|
145
265
|
text: token,
|
|
146
266
|
width: displayWidth(token),
|
|
267
|
+
graphemeCount: segments.length,
|
|
268
|
+
segments,
|
|
147
269
|
isWhitespace: isWhitespace(token),
|
|
148
270
|
make: (segment) => segment,
|
|
149
271
|
})
|