@effect-tui/core 0.1.0 → 0.1.4
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 +31 -11
- package/dist/ansi.d.ts +127 -32
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +159 -37
- package/dist/ansi.js.map +1 -1
- package/dist/colors.d.ts +139 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +339 -0
- package/dist/colors.js.map +1 -0
- package/dist/index.d.ts +6 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +21 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +199 -58
- package/dist/keys.js.map +1 -1
- package/dist/layout/axis-helpers.d.ts +19 -0
- package/dist/layout/axis-helpers.d.ts.map +1 -0
- package/dist/layout/axis-helpers.js +19 -0
- package/dist/layout/axis-helpers.js.map +1 -0
- package/dist/output.d.ts +59 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +142 -0
- package/dist/output.js.map +1 -0
- package/dist/render/buffer.d.ts.map +1 -1
- package/dist/render/buffer.js +6 -25
- package/dist/render/buffer.js.map +1 -1
- package/dist/render/graphemes.d.ts +15 -0
- package/dist/render/graphemes.d.ts.map +1 -0
- package/dist/render/graphemes.js +28 -0
- package/dist/render/graphemes.js.map +1 -0
- package/dist/render/measure.d.ts +1 -0
- package/dist/render/measure.d.ts.map +1 -1
- package/dist/render/measure.js +14 -36
- package/dist/render/measure.js.map +1 -1
- package/dist/render/palette.d.ts.map +1 -1
- package/dist/render/palette.js +26 -1
- package/dist/render/palette.js.map +1 -1
- package/dist/render/segmenter.d.ts +8 -0
- package/dist/render/segmenter.d.ts.map +1 -0
- package/dist/render/segmenter.js +23 -0
- package/dist/render/segmenter.js.map +1 -0
- package/dist/render/surface.d.ts +6 -32
- package/dist/render/surface.d.ts.map +1 -1
- package/dist/render/surface.js +11 -80
- package/dist/render/surface.js.map +1 -1
- package/dist/runtime/backend_node.d.ts.map +1 -1
- package/dist/runtime/backend_node.js.map +1 -1
- package/dist/tailwind-colors.d.ts +291 -0
- package/dist/tailwind-colors.d.ts.map +1 -0
- package/dist/tailwind-colors.js +291 -0
- package/dist/tailwind-colors.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -55
- package/src/ansi.ts +201 -73
- package/src/colors.ts +468 -0
- package/src/index.ts +28 -14
- package/src/keys.ts +467 -287
- package/src/layout/axis-helpers.ts +33 -0
- package/src/output.ts +175 -0
- package/src/render/buffer.ts +161 -184
- package/src/render/graphemes.ts +34 -0
- package/src/render/measure.ts +15 -38
- package/src/render/palette.ts +98 -77
- package/src/render/segmenter.ts +27 -0
- package/src/render/surface.ts +139 -225
- package/src/runtime/backend_node.ts +71 -71
- package/src/tailwind-colors.ts +295 -0
- package/src/types.ts +18 -0
- package/dist/anim.d.ts +0 -4
- package/dist/anim.d.ts.map +0 -1
- package/dist/anim.js +0 -5
- package/dist/anim.js.map +0 -1
- package/dist/layout/linearStack.d.ts +0 -17
- package/dist/layout/linearStack.d.ts.map +0 -1
- package/dist/layout/linearStack.js +0 -86
- package/dist/layout/linearStack.js.map +0 -1
- package/dist/motion-value.d.ts +0 -58
- package/dist/motion-value.d.ts.map +0 -1
- package/dist/motion-value.js +0 -250
- package/dist/motion-value.js.map +0 -1
- package/dist/present/display.d.ts +0 -58
- package/dist/present/display.d.ts.map +0 -1
- package/dist/present/display.js +0 -168
- package/dist/present/display.js.map +0 -1
- package/dist/present/writers/fullscreen.d.ts +0 -19
- package/dist/present/writers/fullscreen.d.ts.map +0 -1
- package/dist/present/writers/fullscreen.js +0 -55
- package/dist/present/writers/fullscreen.js.map +0 -1
- package/dist/present/writers/inline.d.ts +0 -20
- package/dist/present/writers/inline.d.ts.map +0 -1
- package/dist/present/writers/inline.js +0 -92
- package/dist/present/writers/inline.js.map +0 -1
- package/dist/render/color-utils.d.ts +0 -18
- package/dist/render/color-utils.d.ts.map +0 -1
- package/dist/render/color-utils.js +0 -58
- package/dist/render/color-utils.js.map +0 -1
- package/dist/render/diff.d.ts +0 -30
- package/dist/render/diff.d.ts.map +0 -1
- package/dist/render/diff.js +0 -83
- package/dist/render/diff.js.map +0 -1
- package/dist/spring-physics.d.ts +0 -36
- package/dist/spring-physics.d.ts.map +0 -1
- package/dist/spring-physics.js +0 -113
- package/dist/spring-physics.js.map +0 -1
- package/dist/spring.d.ts +0 -73
- package/dist/spring.d.ts.map +0 -1
- package/dist/spring.js +0 -136
- package/dist/spring.js.map +0 -1
- package/dist/ui/containers/canvas.d.ts +0 -13
- package/dist/ui/containers/canvas.d.ts.map +0 -1
- package/dist/ui/containers/canvas.js +0 -16
- package/dist/ui/containers/canvas.js.map +0 -1
- package/dist/ui/containers/geometry-reader.d.ts +0 -17
- package/dist/ui/containers/geometry-reader.d.ts.map +0 -1
- package/dist/ui/containers/geometry-reader.js +0 -24
- package/dist/ui/containers/geometry-reader.js.map +0 -1
- package/dist/ui/containers/hstack.d.ts +0 -12
- package/dist/ui/containers/hstack.d.ts.map +0 -1
- package/dist/ui/containers/hstack.js +0 -28
- package/dist/ui/containers/hstack.js.map +0 -1
- package/dist/ui/containers/scroll.d.ts +0 -28
- package/dist/ui/containers/scroll.d.ts.map +0 -1
- package/dist/ui/containers/scroll.js +0 -97
- package/dist/ui/containers/scroll.js.map +0 -1
- package/dist/ui/containers/shared.d.ts +0 -12
- package/dist/ui/containers/shared.d.ts.map +0 -1
- package/dist/ui/containers/shared.js +0 -19
- package/dist/ui/containers/shared.js.map +0 -1
- package/dist/ui/containers/vstack.d.ts +0 -12
- package/dist/ui/containers/vstack.d.ts.map +0 -1
- package/dist/ui/containers/vstack.js +0 -28
- package/dist/ui/containers/vstack.js.map +0 -1
- package/dist/ui/containers/zstack.d.ts +0 -14
- package/dist/ui/containers/zstack.d.ts.map +0 -1
- package/dist/ui/containers/zstack.js +0 -36
- package/dist/ui/containers/zstack.js.map +0 -1
- package/dist/ui/core/geometry-store.d.ts +0 -22
- package/dist/ui/core/geometry-store.d.ts.map +0 -1
- package/dist/ui/core/geometry-store.js +0 -29
- package/dist/ui/core/geometry-store.js.map +0 -1
- package/dist/ui/core/geometry.d.ts +0 -34
- package/dist/ui/core/geometry.d.ts.map +0 -1
- package/dist/ui/core/geometry.js +0 -14
- package/dist/ui/core/geometry.js.map +0 -1
- package/dist/ui/core/view.d.ts +0 -25
- package/dist/ui/core/view.d.ts.map +0 -1
- package/dist/ui/core/view.js +0 -34
- package/dist/ui/core/view.js.map +0 -1
- package/dist/ui/index.d.ts +0 -44
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js +0 -39
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/inlinetext.d.ts +0 -24
- package/dist/ui/inlinetext.d.ts.map +0 -1
- package/dist/ui/inlinetext.js +0 -131
- package/dist/ui/inlinetext.js.map +0 -1
- package/dist/ui/install.d.ts +0 -22
- package/dist/ui/install.d.ts.map +0 -1
- package/dist/ui/install.js +0 -66
- package/dist/ui/install.js.map +0 -1
- package/dist/ui/markdown.d.ts +0 -40
- package/dist/ui/markdown.d.ts.map +0 -1
- package/dist/ui/markdown.js +0 -351
- package/dist/ui/markdown.js.map +0 -1
- package/dist/ui/modifiers/border.d.ts +0 -33
- package/dist/ui/modifiers/border.d.ts.map +0 -1
- package/dist/ui/modifiers/border.js +0 -82
- package/dist/ui/modifiers/border.js.map +0 -1
- package/dist/ui/modifiers/fill.d.ts +0 -14
- package/dist/ui/modifiers/fill.d.ts.map +0 -1
- package/dist/ui/modifiers/fill.js +0 -25
- package/dist/ui/modifiers/fill.js.map +0 -1
- package/dist/ui/modifiers/frame.d.ts +0 -23
- package/dist/ui/modifiers/frame.d.ts.map +0 -1
- package/dist/ui/modifiers/frame.js +0 -54
- package/dist/ui/modifiers/frame.js.map +0 -1
- package/dist/ui/modifiers/offset.d.ts +0 -15
- package/dist/ui/modifiers/offset.d.ts.map +0 -1
- package/dist/ui/modifiers/offset.js +0 -21
- package/dist/ui/modifiers/offset.js.map +0 -1
- package/dist/ui/modifiers/opacity.d.ts +0 -15
- package/dist/ui/modifiers/opacity.d.ts.map +0 -1
- package/dist/ui/modifiers/opacity.js +0 -95
- package/dist/ui/modifiers/opacity.js.map +0 -1
- package/dist/ui/modifiers/padding.d.ts +0 -20
- package/dist/ui/modifiers/padding.d.ts.map +0 -1
- package/dist/ui/modifiers/padding.js +0 -36
- package/dist/ui/modifiers/padding.js.map +0 -1
- package/dist/ui/modifiers/styled.d.ts +0 -14
- package/dist/ui/modifiers/styled.d.ts.map +0 -1
- package/dist/ui/modifiers/styled.js +0 -26
- package/dist/ui/modifiers/styled.js.map +0 -1
- package/dist/ui/primitives/rectangle.d.ts +0 -15
- package/dist/ui/primitives/rectangle.d.ts.map +0 -1
- package/dist/ui/primitives/rectangle.js +0 -23
- package/dist/ui/primitives/rectangle.js.map +0 -1
- package/dist/ui/primitives/spacer.d.ts +0 -13
- package/dist/ui/primitives/spacer.d.ts.map +0 -1
- package/dist/ui/primitives/spacer.js +0 -16
- package/dist/ui/primitives/spacer.js.map +0 -1
- package/dist/ui/primitives/text.d.ts +0 -15
- package/dist/ui/primitives/text.d.ts.map +0 -1
- package/dist/ui/primitives/text.js +0 -79
- package/dist/ui/primitives/text.js.map +0 -1
- package/dist/ui/primitives/wrapped-text.d.ts +0 -30
- package/dist/ui/primitives/wrapped-text.d.ts.map +0 -1
- package/dist/ui/primitives/wrapped-text.js +0 -117
- package/dist/ui/primitives/wrapped-text.js.map +0 -1
- package/dist/ui/shinytext.d.ts +0 -66
- package/dist/ui/shinytext.d.ts.map +0 -1
- package/dist/ui/shinytext.js +0 -99
- package/dist/ui/shinytext.js.map +0 -1
- package/dist/ui/text/layout.d.ts +0 -35
- package/dist/ui/text/layout.d.ts.map +0 -1
- package/dist/ui/text/layout.js +0 -102
- package/dist/ui/text/layout.js.map +0 -1
- package/dist/ui/textinput.d.ts +0 -140
- package/dist/ui/textinput.d.ts.map +0 -1
- package/dist/ui/textinput.js +0 -402
- package/dist/ui/textinput.js.map +0 -1
- package/dist/ui/view-constructors.d.ts +0 -72
- package/dist/ui/view-constructors.d.ts.map +0 -1
- package/dist/ui/view-constructors.js +0 -74
- package/dist/ui/view-constructors.js.map +0 -1
- package/src/anim.ts +0 -5
- package/src/layout/linearStack.ts +0 -115
- package/src/motion-value.ts +0 -335
- package/src/present/display.ts +0 -206
- package/src/present/writers/fullscreen.ts +0 -58
- package/src/present/writers/inline.ts +0 -101
- package/src/render/color-utils.ts +0 -60
- package/src/render/diff.ts +0 -95
- package/src/spring-physics.ts +0 -151
- package/src/spring.ts +0 -234
- package/src/ui/__snapshots__/wrappedtext.test.ts.snap +0 -57
- package/src/ui/containers/canvas.ts +0 -18
- package/src/ui/containers/geometry-reader.ts +0 -32
- package/src/ui/containers/hstack.ts +0 -33
- package/src/ui/containers/scroll.ts +0 -106
- package/src/ui/containers/shared.ts +0 -27
- package/src/ui/containers/vstack.ts +0 -34
- package/src/ui/containers/zstack.ts +0 -37
- package/src/ui/core/geometry-store.ts +0 -42
- package/src/ui/core/geometry.ts +0 -30
- package/src/ui/core/view.ts +0 -49
- package/src/ui/index.ts +0 -84
- package/src/ui/inlinetext.ts +0 -135
- package/src/ui/install.ts +0 -110
- package/src/ui/markdown.test.ts +0 -74
- package/src/ui/markdown.ts +0 -388
- package/src/ui/modifiers/border.ts +0 -100
- package/src/ui/modifiers/fill.ts +0 -28
- package/src/ui/modifiers/frame.ts +0 -74
- package/src/ui/modifiers/offset.ts +0 -23
- package/src/ui/modifiers/opacity.ts +0 -93
- package/src/ui/modifiers/padding.ts +0 -53
- package/src/ui/modifiers/styled.ts +0 -31
- package/src/ui/primitives/rectangle.ts +0 -25
- package/src/ui/primitives/spacer.ts +0 -18
- package/src/ui/primitives/text.ts +0 -85
- package/src/ui/primitives/wrapped-text.ts +0 -131
- package/src/ui/shinytext.ts +0 -159
- package/src/ui/text/layout.ts +0 -119
- package/src/ui/textinput.ts +0 -496
- package/src/ui/view-constructors.ts +0 -96
- package/src/ui/wrappedtext.test.ts +0 -138
package/src/render/palette.ts
CHANGED
|
@@ -7,12 +7,32 @@ export type ColorValue = number | { r: number; g: number; b: number }
|
|
|
7
7
|
|
|
8
8
|
// Minimal set of attributes we support. Additional flags can be added as needed.
|
|
9
9
|
export type StyleSpec = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
fg?: ColorValue // foreground: 256 index or RGB
|
|
11
|
+
bg?: ColorValue // background: 256 index or RGB
|
|
12
|
+
bold?: boolean
|
|
13
|
+
italic?: boolean
|
|
14
|
+
underline?: boolean
|
|
15
|
+
inverse?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a canonical key for a StyleSpec that is order-independent.
|
|
20
|
+
* This ensures {fg: 1, bg: 2} and {bg: 2, fg: 1} produce the same cache key.
|
|
21
|
+
*/
|
|
22
|
+
function styleKey(spec: StyleSpec): string {
|
|
23
|
+
const parts: string[] = []
|
|
24
|
+
// Use fixed property order for deterministic keys
|
|
25
|
+
if (spec.fg !== undefined) {
|
|
26
|
+
parts.push(typeof spec.fg === "number" ? `f${spec.fg}` : `f${spec.fg.r},${spec.fg.g},${spec.fg.b}`)
|
|
27
|
+
}
|
|
28
|
+
if (spec.bg !== undefined) {
|
|
29
|
+
parts.push(typeof spec.bg === "number" ? `b${spec.bg}` : `b${spec.bg.r},${spec.bg.g},${spec.bg.b}`)
|
|
30
|
+
}
|
|
31
|
+
if (spec.bold) parts.push("B")
|
|
32
|
+
if (spec.italic) parts.push("I")
|
|
33
|
+
if (spec.underline) parts.push("U")
|
|
34
|
+
if (spec.inverse) parts.push("V")
|
|
35
|
+
return parts.join("|")
|
|
16
36
|
}
|
|
17
37
|
|
|
18
38
|
/**
|
|
@@ -20,56 +40,57 @@ export type StyleSpec = {
|
|
|
20
40
|
* ANSI SGR escape strings. Id 0 always represents the default terminal style.
|
|
21
41
|
*/
|
|
22
42
|
export class Palette {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
private nextId = 1 // 0 = default terminal style
|
|
44
|
+
private idByKey = new Map<string, number>()
|
|
45
|
+
private sgrById = new Map<number, string>()
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
/** Get or create a numeric style id for a given style spec. */
|
|
48
|
+
id(spec?: StyleSpec): number {
|
|
49
|
+
if (!spec) return 0
|
|
50
|
+
const key = styleKey(spec)
|
|
51
|
+
if (key === "") return 0 // empty object = default style
|
|
52
|
+
let id = this.idByKey.get(key)
|
|
53
|
+
if (id) return id
|
|
54
|
+
id = this.nextId++
|
|
55
|
+
this.idByKey.set(key, id)
|
|
56
|
+
this.sgrById.set(id, this.buildSGR(spec))
|
|
57
|
+
return id
|
|
58
|
+
}
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
/** Look up the ANSI SGR sequence for a style id. Id 0 resets attributes. */
|
|
61
|
+
sgr(id: number): string {
|
|
62
|
+
if (id === 0) return `${ESC}0m`
|
|
63
|
+
const s = this.sgrById.get(id)
|
|
64
|
+
return s ?? `${ESC}0m`
|
|
65
|
+
}
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Build an ANSI SGR sequence for a style spec.
|
|
69
|
+
* We always begin with reset (0) to avoid style drift across runs,
|
|
70
|
+
* then add only the requested attributes.
|
|
71
|
+
*/
|
|
72
|
+
private buildSGR(spec: StyleSpec): string {
|
|
73
|
+
const parts: string[] = []
|
|
74
|
+
parts.push("0") // reset, then set exact attrs to avoid drift
|
|
75
|
+
if (spec.bold) parts.push("1")
|
|
76
|
+
if (spec.italic) parts.push("3")
|
|
77
|
+
if (spec.underline) parts.push("4")
|
|
78
|
+
if (spec.inverse) parts.push("7")
|
|
58
79
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
const colorToCodes = (c: ColorValue | undefined, isBg: boolean): string | undefined => {
|
|
81
|
+
if (c == null) return undefined
|
|
82
|
+
if (typeof c === "number") return (isBg ? "48;5;" : "38;5;") + String(c | 0)
|
|
83
|
+
const clamp = (n: number) => (n < 0 ? 0 : n > 255 ? 255 : n | 0)
|
|
84
|
+
return `${isBg ? "48;2;" : "38;2;"}${clamp(c.r)};${clamp(c.g)};${clamp(c.b)}`
|
|
85
|
+
}
|
|
65
86
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
const fgc = colorToCodes(spec.fg, false)
|
|
88
|
+
const bgc = colorToCodes(spec.bg, true)
|
|
89
|
+
if (fgc) parts.push(fgc)
|
|
90
|
+
if (bgc) parts.push(bgc)
|
|
70
91
|
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
return `${CSI}${parts.join(";")}m`
|
|
93
|
+
}
|
|
73
94
|
}
|
|
74
95
|
|
|
75
96
|
/**
|
|
@@ -77,37 +98,37 @@ export class Palette {
|
|
|
77
98
|
* requested style, while delegating id/sgr generation to a shared Palette.
|
|
78
99
|
*/
|
|
79
100
|
export class ScopedPalette extends Palette {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
constructor(
|
|
102
|
+
private readonly base: Palette,
|
|
103
|
+
private readonly baseStyle?: StyleSpec,
|
|
104
|
+
) {
|
|
105
|
+
super()
|
|
106
|
+
}
|
|
86
107
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
override id(spec?: StyleSpec): number {
|
|
109
|
+
return this.base.id(mergeStyle(this.baseStyle, spec))
|
|
110
|
+
}
|
|
90
111
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
override sgr(id: number): string {
|
|
113
|
+
return this.base.sgr(id)
|
|
114
|
+
}
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
/** Merge two style specs (b overrides a). Returns undefined if nothing to set. */
|
|
97
118
|
export function mergeStyle(a?: StyleSpec, b?: StyleSpec): StyleSpec | undefined {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
if (!a && !b) return undefined
|
|
120
|
+
const merged: StyleSpec = {}
|
|
121
|
+
const fg = b?.fg ?? a?.fg
|
|
122
|
+
const bg = b?.bg ?? a?.bg
|
|
123
|
+
const bold = b?.bold ?? a?.bold
|
|
124
|
+
const italic = b?.italic ?? a?.italic
|
|
125
|
+
const underline = b?.underline ?? a?.underline
|
|
126
|
+
const inverse = b?.inverse ?? a?.inverse
|
|
127
|
+
if (fg !== undefined) merged.fg = fg
|
|
128
|
+
if (bg !== undefined) merged.bg = bg
|
|
129
|
+
if (bold !== undefined) merged.bold = bold
|
|
130
|
+
if (italic !== undefined) merged.italic = italic
|
|
131
|
+
if (underline !== undefined) merged.underline = underline
|
|
132
|
+
if (inverse !== undefined) merged.inverse = inverse
|
|
133
|
+
return Object.keys(merged).length > 0 ? merged : undefined
|
|
113
134
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/* segmenter.ts — Cached grapheme segmenter for text processing */
|
|
2
|
+
|
|
3
|
+
export type GraphemeSegmenter = {
|
|
4
|
+
segment(input: string): Iterable<{ segment: string }>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Cached segmenter instance (created once, reused for all calls)
|
|
8
|
+
let cachedSegmenter: GraphemeSegmenter | null | undefined = undefined
|
|
9
|
+
|
|
10
|
+
/** Get or create a cached grapheme segmenter. Returns null if Intl.Segmenter unavailable. */
|
|
11
|
+
export function getGraphemeSegmenter(): GraphemeSegmenter | null {
|
|
12
|
+
if (cachedSegmenter !== undefined) return cachedSegmenter
|
|
13
|
+
|
|
14
|
+
const SegCtor: { new (...args: any[]): GraphemeSegmenter } | undefined = (globalThis as any).Intl?.Segmenter
|
|
15
|
+
if (typeof SegCtor === "function") {
|
|
16
|
+
try {
|
|
17
|
+
cachedSegmenter = new (SegCtor as new (...args: any[]) => GraphemeSegmenter)(undefined, {
|
|
18
|
+
granularity: "grapheme",
|
|
19
|
+
}) as GraphemeSegmenter
|
|
20
|
+
return cachedSegmenter
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore and fall back
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
cachedSegmenter = null
|
|
26
|
+
return null
|
|
27
|
+
}
|
package/src/render/surface.ts
CHANGED
|
@@ -1,238 +1,152 @@
|
|
|
1
1
|
import { CellBuffer, type Wcwidth, defaultWcwidth } from "./buffer.js"
|
|
2
2
|
import { Palette, ScopedPalette, type ColorValue, type StyleSpec, mergeStyle } from "./palette.js"
|
|
3
|
+
import { Colors, parseColor, BASE_NAMES, type Color, type HexColor, type BaseColorName } from "../colors.js"
|
|
4
|
+
import { emitRow } from "../output.js"
|
|
3
5
|
|
|
4
6
|
// Re-export palette types for other modules
|
|
5
7
|
export type { ColorValue, StyleSpec }
|
|
6
8
|
export { Palette, mergeStyle }
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
export const BASE_NAMES = {
|
|
12
|
-
black: 0,
|
|
13
|
-
red: 1,
|
|
14
|
-
green: 2,
|
|
15
|
-
yellow: 3,
|
|
16
|
-
blue: 4,
|
|
17
|
-
magenta: 5,
|
|
18
|
-
cyan: 6,
|
|
19
|
-
white: 7,
|
|
20
|
-
brightBlack: 8,
|
|
21
|
-
brightRed: 9,
|
|
22
|
-
brightGreen: 10,
|
|
23
|
-
brightYellow: 11,
|
|
24
|
-
brightBlue: 12,
|
|
25
|
-
brightMagenta: 13,
|
|
26
|
-
brightCyan: 14,
|
|
27
|
-
brightWhite: 15,
|
|
28
|
-
} as const
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Parse flexible color inputs into a ColorValue.
|
|
32
|
-
* Supports: number (0..255), {r,g,b}, "#rrggbb", base names, and gray0..gray23.
|
|
33
|
-
*/
|
|
34
|
-
export function parseColor(c: ColorLike): ColorValue {
|
|
35
|
-
if (typeof c === "number") return c
|
|
36
|
-
if (typeof c === "object") return { r: c.r | 0, g: c.g | 0, b: c.b | 0 }
|
|
37
|
-
const s = String(c).trim()
|
|
38
|
-
|
|
39
|
-
if (s.startsWith("#")) {
|
|
40
|
-
const hex = s.slice(1)
|
|
41
|
-
if (hex.length === 6) {
|
|
42
|
-
const r = parseInt(hex.slice(0, 2), 16)
|
|
43
|
-
const g = parseInt(hex.slice(2, 4), 16)
|
|
44
|
-
const b = parseInt(hex.slice(4, 6), 16)
|
|
45
|
-
return { r, g, b }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (s in BASE_NAMES) return BASE_NAMES[s as keyof typeof BASE_NAMES]
|
|
50
|
-
|
|
51
|
-
const m = /^(?:gray|grey)(\d{1,2})$/.exec(s)
|
|
52
|
-
if (m?.[1]) {
|
|
53
|
-
const n = Math.max(0, Math.min(23, parseInt(m[1], 10)))
|
|
54
|
-
return 232 + n
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return 7 // default to white if unrecognized
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
type ColorsApi = typeof BASE_NAMES & {
|
|
61
|
-
rgb(r: number, g: number, b: number): ColorValue
|
|
62
|
-
hex(hex: string): ColorValue
|
|
63
|
-
gray(level: number): number
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const Colors: ColorsApi = Object.assign(
|
|
67
|
-
{
|
|
68
|
-
rgb(r: number, g: number, b: number): ColorValue {
|
|
69
|
-
return { r, g, b }
|
|
70
|
-
},
|
|
71
|
-
hex(hex: string): ColorValue {
|
|
72
|
-
return parseColor(hex)
|
|
73
|
-
},
|
|
74
|
-
gray(level: number): number {
|
|
75
|
-
const n = Math.max(0, Math.min(23, level | 0))
|
|
76
|
-
return 232 + n
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
BASE_NAMES,
|
|
80
|
-
) as ColorsApi
|
|
10
|
+
// Re-export color utilities from colors.ts
|
|
11
|
+
export { Colors, parseColor, BASE_NAMES }
|
|
12
|
+
export type { Color, HexColor, BaseColorName }
|
|
81
13
|
|
|
82
14
|
export function derivePalette(p: Palette, base?: StyleSpec): Palette {
|
|
83
|
-
|
|
15
|
+
return new ScopedPalette(p, base)
|
|
84
16
|
}
|
|
85
17
|
|
|
86
18
|
export class Surface {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
line += palette.sgr(styleId)
|
|
221
|
-
currentStyle = styleId
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const cp = g[i]
|
|
225
|
-
line += cp === 32 ? " " : String.fromCodePoint(cp)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// reset style at end of line
|
|
229
|
-
if (currentStyle !== 0) {
|
|
230
|
-
line += palette.sgr(0)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
lines.push(line)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return lines
|
|
237
|
-
}
|
|
19
|
+
readonly w: number
|
|
20
|
+
readonly h: number
|
|
21
|
+
private B: CellBuffer
|
|
22
|
+
private palette: Palette
|
|
23
|
+
|
|
24
|
+
/** Access the underlying CellBuffer for diffing or inspection. */
|
|
25
|
+
get buffer(): CellBuffer {
|
|
26
|
+
return this.B
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor(width: number, height: number, opts?: { palette?: Palette; wcwidth?: Wcwidth }) {
|
|
30
|
+
this.w = Math.max(1, width | 0)
|
|
31
|
+
this.h = Math.max(1, height | 0)
|
|
32
|
+
|
|
33
|
+
const wc = opts?.wcwidth ?? defaultWcwidth
|
|
34
|
+
this.B = new CellBuffer(this.w, this.h, wc)
|
|
35
|
+
this.palette = opts?.palette ?? new Palette()
|
|
36
|
+
|
|
37
|
+
this.B.clear(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Clear the back buffer (B) to spaces in the given style. */
|
|
41
|
+
clear(styleId = 0): void {
|
|
42
|
+
this.B.clear(styleId)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Draw a single code point at (x,y). If width=2, marks the following cell as continuation (w=0). */
|
|
46
|
+
drawCP(x: number, y: number, cp: number, styleId = 0): void {
|
|
47
|
+
this.B.drawCP(x, y, cp, styleId)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Draw plain text (no wrapping) clipped to width. Uses Intl.Segmenter if available. */
|
|
51
|
+
drawText(x: number, y: number, text: string, styleId = 0, maxWidth?: number): void {
|
|
52
|
+
this.B.drawText(x, y, text, styleId, maxWidth)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Fill a rectangle with a code point + style. */
|
|
56
|
+
fillRect(x: number, y: number, w: number, h: number, cp = 32, styleId = 0): void {
|
|
57
|
+
this.B.fillRect(x, y, w, h, cp, styleId)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- Clipping helpers (forwarded to back buffer) ---
|
|
61
|
+
|
|
62
|
+
pushClip(x: number, y: number, w: number, h: number): void {
|
|
63
|
+
this.B.pushClip(x, y, w, h)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
popClip(): void {
|
|
67
|
+
this.B.popClip()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
withClip(x: number, y: number, w: number, h: number, fn: () => void): void {
|
|
71
|
+
this.B.withClip(x, y, w, h, fn)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- Offset helpers (forwarded to back buffer) ---
|
|
75
|
+
|
|
76
|
+
pushOffset(dx: number, dy: number): void {
|
|
77
|
+
this.B.pushOffset(dx, dy)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
popOffset(): void {
|
|
81
|
+
this.B.popOffset()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
withOffset(dx: number, dy: number, fn: () => void): void {
|
|
85
|
+
this.B.withOffset(dx, dy, fn)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Convert surface to plain text (for testing). */
|
|
89
|
+
toString(): string {
|
|
90
|
+
const lines: string[] = []
|
|
91
|
+
for (let y = 0; y < this.h; y++) {
|
|
92
|
+
const row = y * this.w
|
|
93
|
+
let line = ""
|
|
94
|
+
for (let x = 0; x < this.w; x++) {
|
|
95
|
+
const i = row + x
|
|
96
|
+
const cp = this.B.g[i]
|
|
97
|
+
const w = this.B.cw[i]
|
|
98
|
+
if (w !== 0) line += cp === 32 ? " " : String.fromCodePoint(cp)
|
|
99
|
+
}
|
|
100
|
+
// trim right spaces
|
|
101
|
+
line = line.replace(/\s+$/, "")
|
|
102
|
+
lines.push(line)
|
|
103
|
+
}
|
|
104
|
+
return lines.join("\n")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Compute tight bounding box of content in back buffer: any non-space glyph or non-default style. */
|
|
108
|
+
contentBounds(): { x: number; y: number; w: number; h: number } {
|
|
109
|
+
const W = this.w
|
|
110
|
+
const H = this.h
|
|
111
|
+
const g = this.B.g
|
|
112
|
+
const s = this.B.s
|
|
113
|
+
const cw = this.B.cw
|
|
114
|
+
let minX = W,
|
|
115
|
+
minY = H,
|
|
116
|
+
maxX = -1,
|
|
117
|
+
maxY = -1
|
|
118
|
+
for (let y = 0; y < H; y++) {
|
|
119
|
+
const row = y * W
|
|
120
|
+
for (let x = 0; x < W; x++) {
|
|
121
|
+
const i = row + x
|
|
122
|
+
if (cw[i] === 0) continue
|
|
123
|
+
if (g[i] !== 32 || s[i] !== 0) {
|
|
124
|
+
if (x < minX) minX = x
|
|
125
|
+
if (y < minY) minY = y
|
|
126
|
+
if (x > maxX) maxX = x
|
|
127
|
+
if (y > maxY) maxY = y
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (maxX < minX || maxY < minY) return { x: 0, y: 0, w: 0, h: 0 }
|
|
132
|
+
return { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Build styled text lines for a given bounds region of the back buffer (without trailing newlines). */
|
|
136
|
+
buildStyledLines(bounds: { x: number; y: number; w: number; h: number }, palette: Palette = this.palette): string[] {
|
|
137
|
+
const W = this.w
|
|
138
|
+
const lines: string[] = []
|
|
139
|
+
const { x: bx, y: by, w: bw, h: bh } = bounds
|
|
140
|
+
const endY = Math.min(by + bh, this.h)
|
|
141
|
+
const endX = Math.min(bx + bw, W)
|
|
142
|
+
const startX = Math.max(0, bx)
|
|
143
|
+
|
|
144
|
+
for (let y = Math.max(0, by); y < endY; y++) {
|
|
145
|
+
const { output, lastStyle } = emitRow(this.B, palette, y, W, startX, endX)
|
|
146
|
+
const line = lastStyle !== 0 ? output + palette.sgr(0) : output
|
|
147
|
+
lines.push(line)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return lines
|
|
151
|
+
}
|
|
238
152
|
}
|