@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.5

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.
Files changed (207) hide show
  1. package/README.md +23 -13
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-D5HUf3Fj.d.ts +452 -0
  4. package/dist/ansi.d.ts +137 -143
  5. package/dist/ansi.js +87 -2
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
  11. package/dist/color-policy-SVj1pYTA.js +560 -0
  12. package/dist/color-profile-DHhQHY55.js +36 -0
  13. package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-Bh4yGP6w.d.ts +186 -0
  18. package/dist/detect-BuTXtY6e.js +373 -0
  19. package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  20. package/dist/env-YVw64yZS.js +9 -0
  21. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  22. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  23. package/dist/index-Bmc2tRPk.d.ts +21 -0
  24. package/dist/index.d.ts +235 -754
  25. package/dist/index.js +1362 -3001
  26. package/dist/osc-CCH7xDoS.js +71 -0
  27. package/dist/osc-Cn0fw77g.d.ts +23 -0
  28. package/dist/paint-C19minOS.d.ts +81 -0
  29. package/dist/query-vaIeGOkH.d.ts +152 -0
  30. package/dist/router.d.ts +392 -0
  31. package/dist/router.js +709 -0
  32. package/dist/sample-Cqw1bjUL.js +445 -0
  33. package/dist/screen-BOh__-65.js +324 -0
  34. package/dist/screen-BReKIheE.d.ts +40 -0
  35. package/dist/screen.d.ts +5 -0
  36. package/dist/screen.js +5 -0
  37. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  38. package/dist/serialize-BTkAZgw1.js +79 -0
  39. package/dist/session-BJmzX2NJ.js +664 -0
  40. package/dist/sgr-BhwaWAJB.js +246 -0
  41. package/dist/store-CgrG9K4y.d.ts +72 -0
  42. package/dist/string-width-CijQwpIk.js +69 -0
  43. package/dist/strip-BvU4toXG.js +6 -0
  44. package/dist/terminal.d.ts +118 -0
  45. package/dist/terminal.js +2 -0
  46. package/dist/tokenize-AjqbvtiT.js +1242 -0
  47. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  48. package/dist/truncate-D31fhU6i.js +562 -0
  49. package/dist/use-focus-C2sciZOo.js +1335 -0
  50. package/package.json +43 -17
  51. package/src/ansi/chalk.ts +12 -53
  52. package/src/ansi/cursor.ts +2 -4
  53. package/src/ansi/east-asian-width.ts +44 -0
  54. package/src/ansi/escapes.ts +18 -26
  55. package/src/ansi/graphemes.ts +8 -0
  56. package/src/ansi/hyperlink.ts +44 -0
  57. package/src/ansi/index.ts +14 -14
  58. package/src/ansi/osc.ts +77 -0
  59. package/src/ansi/sgr.ts +2 -5
  60. package/src/ansi/slice.ts +1 -1
  61. package/src/ansi/string-width.ts +91 -206
  62. package/src/ansi/strip.ts +13 -33
  63. package/src/ansi/tokenize.ts +96 -169
  64. package/src/ansi/truncate.ts +5 -3
  65. package/src/ansi/wrap.ts +13 -14
  66. package/src/ansi-tokenizer.ts +1 -1
  67. package/src/capabilities/color-policy.ts +34 -0
  68. package/src/capabilities/detect.ts +594 -0
  69. package/src/capabilities/index.ts +37 -0
  70. package/src/capabilities/query.ts +657 -0
  71. package/src/capabilities/store.ts +379 -0
  72. package/src/color/index.ts +3 -0
  73. package/src/color/paint.ts +169 -0
  74. package/src/color/palette.ts +48 -0
  75. package/src/color/sample.ts +323 -0
  76. package/src/color.ts +1 -0
  77. package/src/components/AnsiText.tsx +42 -0
  78. package/src/components/App.tsx +109 -22
  79. package/src/components/BackgroundContext.ts +2 -3
  80. package/src/components/Box.tsx +43 -60
  81. package/src/components/CursorContext.ts +1 -1
  82. package/src/components/ErrorBoundary.tsx +1 -1
  83. package/src/components/ErrorOverview.tsx +8 -7
  84. package/src/components/Hyperlink.tsx +56 -0
  85. package/src/components/Spacer.tsx +1 -1
  86. package/src/components/Static.tsx +1 -1
  87. package/src/components/StderrContext.ts +0 -2
  88. package/src/components/StdinContext.ts +0 -1
  89. package/src/components/StdoutContext.ts +1 -3
  90. package/src/components/TerminalOscContext.ts +25 -0
  91. package/src/components/Text.tsx +23 -46
  92. package/src/components/Transform.tsx +2 -2
  93. package/src/cursor-position.ts +1 -1
  94. package/src/devtools.ts +84 -24
  95. package/src/dom.ts +11 -8
  96. package/src/env.ts +12 -0
  97. package/src/get-max-width.ts +1 -1
  98. package/src/global.d.ts +6 -4
  99. package/src/{boxes.ts → glyphs.ts} +17 -18
  100. package/src/hooks/use-animation.ts +1 -1
  101. package/src/hooks/use-app.ts +1 -1
  102. package/src/hooks/use-box-metrics.ts +1 -1
  103. package/src/hooks/use-capabilities.ts +73 -0
  104. package/src/hooks/use-cursor.ts +3 -3
  105. package/src/hooks/use-focus-manager.ts +1 -1
  106. package/src/hooks/use-focus.ts +5 -6
  107. package/src/hooks/use-input.ts +4 -4
  108. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  109. package/src/hooks/use-paste.ts +2 -2
  110. package/src/hooks/use-stderr.ts +1 -1
  111. package/src/hooks/use-stdin.ts +1 -1
  112. package/src/hooks/use-stdout.ts +1 -1
  113. package/src/hooks/use-terminal-osc.ts +59 -0
  114. package/src/hooks/use-window-size.ts +2 -2
  115. package/src/index.ts +88 -44
  116. package/src/ink.tsx +620 -745
  117. package/src/input-parser.ts +1 -1
  118. package/src/instances.ts +1 -1
  119. package/src/kitty-keyboard.ts +128 -0
  120. package/src/measure-element.ts +1 -1
  121. package/src/measure-text.ts +2 -2
  122. package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
  123. package/src/parse-keypress.ts +4 -5
  124. package/src/parse-stack-line.ts +0 -1
  125. package/src/patch-console.ts +44 -0
  126. package/src/reconciler.ts +36 -62
  127. package/src/render-background.ts +37 -16
  128. package/src/render-border.ts +97 -64
  129. package/src/render-frame.ts +83 -0
  130. package/src/render-to-string.ts +24 -9
  131. package/src/render.ts +24 -16
  132. package/src/router/components.tsx +343 -0
  133. package/src/router/context.ts +41 -0
  134. package/src/router/history.ts +194 -0
  135. package/src/router/hooks.tsx +391 -0
  136. package/src/router/index.ts +34 -0
  137. package/src/router/matcher.ts +571 -0
  138. package/src/sanitize-ansi.ts +1 -1
  139. package/src/screen/ansi.ts +184 -0
  140. package/src/screen/canvas.ts +137 -0
  141. package/src/screen/cell.ts +138 -0
  142. package/src/screen/color-profile.ts +47 -0
  143. package/src/screen/geometry.ts +9 -0
  144. package/src/screen/index.ts +6 -0
  145. package/src/screen/screen.ts +272 -0
  146. package/src/screen/serialize.ts +129 -0
  147. package/src/screen.ts +1 -0
  148. package/src/semantic-text-style.ts +118 -0
  149. package/src/signal-exit.ts +4 -5
  150. package/src/squash-text-nodes.ts +4 -7
  151. package/src/stream.ts +2 -4
  152. package/src/structured-text.ts +321 -0
  153. package/src/styles.ts +16 -17
  154. package/src/terminal/index.ts +2 -0
  155. package/src/terminal/inline-presenter.ts +120 -0
  156. package/src/terminal/input.ts +86 -0
  157. package/src/terminal/render-scheduler.ts +37 -0
  158. package/src/terminal/screen-presenter.ts +188 -0
  159. package/src/terminal/session.ts +407 -0
  160. package/src/terminal-size.ts +9 -8
  161. package/src/terminal.ts +1 -0
  162. package/src/testing/browser.ts +588 -0
  163. package/src/testing/emulators.ts +205 -0
  164. package/src/testing/explorer-app/index.html +12 -0
  165. package/src/testing/explorer-app/main.ts +381 -0
  166. package/src/testing/explorer-app/style.css +194 -0
  167. package/src/testing/explorer-app/tsconfig.json +15 -0
  168. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  169. package/src/testing/index.ts +26 -0
  170. package/src/testing/keys.ts +56 -0
  171. package/src/testing/live.ts +85 -0
  172. package/src/testing/matchers.ts +70 -0
  173. package/src/testing/public.ts +94 -0
  174. package/src/testing/terminal.ts +349 -0
  175. package/src/testing/vitest.ts +157 -0
  176. package/src/transform-adapter.ts +14 -0
  177. package/src/types.ts +1 -6
  178. package/src/utils.ts +2 -2
  179. package/src/wrap-text.ts +4 -4
  180. package/src/yoga/config.ts +2 -2
  181. package/src/yoga/core/absoluteLayout.ts +15 -15
  182. package/src/yoga/core/baseline.ts +3 -3
  183. package/src/yoga/core/cache.ts +4 -4
  184. package/src/yoga/core/calculateLayout.ts +33 -27
  185. package/src/yoga/core/config.ts +1 -1
  186. package/src/yoga/core/flexLine.ts +3 -3
  187. package/src/yoga/core/helpers.ts +3 -3
  188. package/src/yoga/core/layoutResults.ts +4 -4
  189. package/src/yoga/core/node.ts +16 -16
  190. package/src/yoga/core/pixelGrid.ts +4 -4
  191. package/src/yoga/core/style.ts +11 -11
  192. package/src/yoga/core/types.ts +2 -2
  193. package/src/yoga/index.ts +6 -6
  194. package/src/yoga/node.ts +8 -8
  195. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  196. package/dist/truncate-CBiyyZzw.js +0 -2156
  197. package/src/ansi/supports-color.ts +0 -213
  198. package/src/ansi/widest-line.ts +0 -12
  199. package/src/auto-bind.ts +0 -41
  200. package/src/colorize.ts +0 -60
  201. package/src/devtools-window-polyfill.ts +0 -73
  202. package/src/indent-string.ts +0 -16
  203. package/src/is-in-ci.ts +0 -7
  204. package/src/log-update.ts +0 -370
  205. package/src/output.ts +0 -308
  206. package/src/renderer.ts +0 -73
  207. package/src/write-synchronized.ts +0 -9
@@ -1,236 +1,121 @@
1
- import { eastAsianWidth } from "./east-asian-width.ts";
2
- // Visual width of a string: how many terminal columns it occupies.
3
- // Ported from `string-width` (MIT, Sindre Sorhus).
4
- //
5
- // Logic:
6
- // - Segment graphemes to match how terminals render clusters.
7
- // - Width rules:
8
- // 1. Skip non-printing clusters (Default_Ignorable, Control, pure
9
- // nonspacing/enclosing Mark, lone Surrogates). Tabs are ignored by design.
10
- // 2. RGI emoji clusters (\p{RGI_Emoji}) are double-width.
11
- // 3. Minimally-qualified/unqualified emoji clusters (ZWJ sequences with 2+
12
- // Extended_Pictographic, or keycap sequences) are double-width.
13
- // 4. Hangul jamo collapse each standard modern Hangul L+V or L+V+T syllable
14
- // piece to width 2. Unmatched repeated leading/vowel/trailing jamo stay
15
- // additive because that matches how the terminals we target render them.
16
- // 5. Otherwise use East Asian Width of the cluster's first visible code
17
- // point, and add widths for trailing spacing marks and Halfwidth/Fullwidth
18
- // Forms within the same cluster (e.g., dakuten/handakuten).
19
- import { C1_CSI, ESC } from "./escapes.ts";
20
- import { stripAnsi } from "./strip.ts";
21
-
22
- export type StringWidthOptions = {
23
- readonly ambiguousIsNarrow?: boolean;
24
- readonly countAnsiEscapeCodes?: boolean;
25
- };
26
-
27
- type EastAsianWidthOptions = {
28
- readonly ambiguousAsWide: boolean;
29
- };
30
-
31
- const segmenter = new Intl.Segmenter();
32
-
33
- // Whole-cluster zero-width
34
- const zeroWidthClusterRegex =
35
- /^(?:\p{Default_Ignorable_Code_Point}|\p{Control}|\p{Format}|\p{Nonspacing_Mark}|\p{Enclosing_Mark}|\p{Surrogate})+$/v;
1
+ // Derived from `fast-string-truncated-width` (MIT, Fabio Spampinato),
2
+ // reduced to plain width measurement: Sigil truncates via `truncate.ts`, and
3
+ // no caller customizes per-class widths, so those are constants here.
4
+ // Width tables live in east-asian-width.ts, the single width-data module.
36
5
 
37
- // Pick the base scalar if the cluster starts with Prepend/Format/Marks
38
- const leadingNonPrintingRegex =
39
- /^[\p{Default_Ignorable_Code_Point}\p{Control}\p{Format}\p{Nonspacing_Mark}\p{Enclosing_Mark}\p{Surrogate}]+/v;
40
- const spacingMarkRegex = /\p{Spacing_Mark}/v;
6
+ import { isFullWidth, isWideNotCJKTNotEmoji } from "#/ansi/east-asian-width.ts";
41
7
 
42
- // RGI emoji sequences
43
- const rgiEmojiRegex = /^\p{RGI_Emoji}$/v;
44
-
45
- // Detect minimally-qualified/unqualified emoji sequences (missing VS16 but
46
- // still rendering as double-width)
47
- const unqualifiedKeycapRegex = /^[\d#*]\u20E3$/;
48
- const extendedPictographicRegex = /\p{Extended_Pictographic}/gu;
49
-
50
- function isDoubleWidthNonRgiEmojiSequence(segment: string): boolean {
51
- // Real emoji clusters are < 30 chars; guard against pathological input
52
- if (segment.length > 50) {
53
- return false;
54
- }
55
-
56
- if (unqualifiedKeycapRegex.test(segment)) {
57
- return true;
58
- }
8
+ const getCodePointsLength = (input: string): number => {
9
+ let length = 0;
59
10
 
60
- // ZWJ sequences with 2+ Extended_Pictographic
61
- if (segment.includes("\u200D")) {
62
- const pictographics = segment.match(extendedPictographicRegex);
63
- return pictographics !== null && pictographics.length >= 2;
11
+ for (const _ of input) {
12
+ length += 1;
64
13
  }
65
14
 
66
- return false;
67
- }
68
-
69
- function baseVisible(segment: string): string {
70
- return segment.replace(leadingNonPrintingRegex, "");
71
- }
72
-
73
- function isZeroWidthCluster(segment: string): boolean {
74
- return zeroWidthClusterRegex.test(segment);
75
- }
76
-
77
- function isHangulLeadingJamo(codePoint: number | undefined): boolean {
78
- return (
79
- codePoint !== undefined &&
80
- ((codePoint >= 0x11_00 && codePoint <= 0x11_5f) ||
81
- (codePoint >= 0xa9_60 && codePoint <= 0xa9_7c))
82
- );
83
- }
84
-
85
- function isHangulVowelJamo(codePoint: number | undefined): boolean {
86
- return (
87
- codePoint !== undefined &&
88
- ((codePoint >= 0x11_60 && codePoint <= 0x11_a7) ||
89
- (codePoint >= 0xd7_b0 && codePoint <= 0xd7_c6))
90
- );
91
- }
92
-
93
- function isHangulTrailingJamo(codePoint: number | undefined): boolean {
94
- return (
95
- codePoint !== undefined &&
96
- ((codePoint >= 0x11_a8 && codePoint <= 0x11_ff) ||
97
- (codePoint >= 0xd7_cb && codePoint <= 0xd7_fb))
98
- );
99
- }
100
-
101
- function isHangulJamo(codePoint: number): boolean {
102
- return (
103
- isHangulLeadingJamo(codePoint) ||
104
- isHangulVowelJamo(codePoint) ||
105
- isHangulTrailingJamo(codePoint)
106
- );
107
- }
108
-
109
- function hangulClusterWidth(
110
- visibleSegment: string,
111
- eastAsianWidthOptions: EastAsianWidthOptions,
112
- ): number | undefined {
113
- const codePoints: number[] = [];
114
-
115
- for (const character of visibleSegment) {
116
- if (zeroWidthClusterRegex.test(character)) {
117
- continue;
118
- }
15
+ return length;
16
+ };
119
17
 
120
- codePoints.push(character.codePointAt(0)!);
121
- }
18
+ // Unlike the original port, CSI parameters also allow `:` so ITU T.416
19
+ // colon-parameter SGR sequences (which Sigil preserves) measure as width 0.
20
+ const ANSI_RE =
21
+ /[\u001b\u009b][[()#;?]*[0-9;:]*[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
22
+ const CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
23
+ const CJKT_WIDE_RE =
24
+ /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/uy;
25
+ const TAB_RE = /\t{1,1000}/y;
26
+ const EMOJI_RE =
27
+ /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/uy;
28
+ const LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
29
+ const MODIFIER_RE = /\p{M}+/gu;
30
+
31
+ const ANSI_WIDTH = 0;
32
+ const CONTROL_WIDTH = 0;
33
+ const TAB_WIDTH = 8;
34
+ const EMOJI_WIDTH = 2;
35
+ const FULL_WIDTH_WIDTH = 2;
36
+ const REGULAR_WIDTH = 1;
37
+ const WIDE_WIDTH = 2;
38
+
39
+ const PARSE_BLOCKS: [RegExp, number][] = [
40
+ [LATIN_RE, REGULAR_WIDTH],
41
+ [ANSI_RE, ANSI_WIDTH],
42
+ [CONTROL_RE, CONTROL_WIDTH],
43
+ [TAB_RE, TAB_WIDTH],
44
+ [EMOJI_RE, EMOJI_WIDTH],
45
+ [CJKT_WIDE_RE, WIDE_WIDTH],
46
+ ];
47
+
48
+ export const stringWidth = (input: string): number => {
49
+ let indexPrev = 0;
50
+ let index = 0;
51
+ const length = input.length;
52
+ let unmatchedStart = 0;
53
+ let unmatchedEnd = 0;
54
+ let width = 0;
122
55
 
123
- if (codePoints.length === 0) {
124
- return;
125
- }
56
+ outer: while (true) {
57
+ /* UNMATCHED */
126
58
 
127
- let width = 0;
59
+ if (unmatchedEnd > unmatchedStart || (index >= length && index > indexPrev)) {
60
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
128
61
 
129
- for (let index = 0; index < codePoints.length; index++) {
130
- const codePoint = codePoints[index]!;
131
- if (!isHangulJamo(codePoint)) {
132
- if (width === 0) {
133
- return;
134
- }
62
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
63
+ const codePoint = char.codePointAt(0) || 0;
135
64
 
136
- // Mixed cluster (e.g., L + precomposed syllable): use EAW for the
137
- // non-jamo remainder
138
- for (let remaining = index; remaining < codePoints.length; remaining++) {
139
- width += eastAsianWidth(codePoints[remaining]!, eastAsianWidthOptions);
65
+ width += isFullWidth(codePoint)
66
+ ? FULL_WIDTH_WIDTH
67
+ : isWideNotCJKTNotEmoji(codePoint)
68
+ ? WIDE_WIDTH
69
+ : REGULAR_WIDTH;
140
70
  }
141
71
 
142
- return width;
72
+ unmatchedStart = unmatchedEnd = 0;
143
73
  }
144
74
 
145
- // Modern Hangul L+V(+T) shapes as one syllable block. Unmatched jamo stay
146
- // additive: U+1100 U+1100 U+1161 => U+1100 + (U+1100 U+1161) => 2 + 2.
147
- if (isHangulLeadingJamo(codePoint) && isHangulVowelJamo(codePoints[index + 1])) {
148
- width += 2;
149
- index += isHangulTrailingJamo(codePoints[index + 2]) ? 2 : 1;
150
- continue;
151
- }
152
-
153
- width += eastAsianWidth(codePoint, eastAsianWidthOptions);
154
- }
75
+ /* EXITING */
155
76
 
156
- return width;
157
- }
158
-
159
- function trailingWidth(
160
- visibleSegment: string,
161
- eastAsianWidthOptions: EastAsianWidthOptions,
162
- ): number {
163
- let extra = 0;
164
- let first = true;
165
-
166
- for (const character of visibleSegment) {
167
- if (first) {
168
- first = false;
169
- continue;
77
+ if (index >= length) {
78
+ break;
170
79
  }
171
80
 
172
- if (spacingMarkRegex.test(character) || (character >= "\uFF00" && character <= "\uFFEF")) {
173
- extra += eastAsianWidth(character.codePointAt(0)!, eastAsianWidthOptions);
174
- }
175
- }
81
+ /* PARSE BLOCKS */
176
82
 
177
- return extra;
178
- }
83
+ for (let i = 0, l = PARSE_BLOCKS.length; i < l; i++) {
84
+ const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i]!;
179
85
 
180
- export function stringWidth(input: string, options: StringWidthOptions = {}): number {
181
- if (typeof input !== "string" || input.length === 0) {
182
- return 0;
183
- }
86
+ BLOCK_RE.lastIndex = index;
184
87
 
185
- const { ambiguousIsNarrow = true, countAnsiEscapeCodes = false } = options;
88
+ if (BLOCK_RE.test(input)) {
89
+ const lengthExtra =
90
+ BLOCK_RE === CJKT_WIDE_RE
91
+ ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex))
92
+ : BLOCK_RE === EMOJI_RE
93
+ ? 1
94
+ : BLOCK_RE.lastIndex - index;
186
95
 
187
- let string = input;
96
+ width += lengthExtra * BLOCK_WIDTH;
97
+ unmatchedStart = indexPrev;
98
+ unmatchedEnd = index;
99
+ index = indexPrev = BLOCK_RE.lastIndex;
188
100
 
189
- // Avoid calling stripAnsi when there are no ANSI escape sequences
190
- // (ESC = 0x1B, CSI = 0x9B)
191
- if (!countAnsiEscapeCodes && (string.includes(ESC) || string.includes(C1_CSI))) {
192
- string = stripAnsi(string);
193
- }
101
+ continue outer;
102
+ }
103
+ }
194
104
 
195
- if (string.length === 0) {
196
- return 0;
197
- }
105
+ /* UNMATCHED INDEX */
198
106
 
199
- // Fast path: printable ASCII (0x20–0x7E) needs no segmenter, regex, or EAW
200
- // lookup — width equals length.
201
- if (/^[ -~]*$/.test(string)) {
202
- return string.length;
107
+ index += 1;
203
108
  }
204
109
 
205
- let width = 0;
206
- const eastAsianWidthOptions: EastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
207
-
208
- for (const { segment } of segmenter.segment(string)) {
209
- // Zero-width / non-printing clusters
210
- if (isZeroWidthCluster(segment)) {
211
- continue;
212
- }
213
-
214
- // Emoji width logic
215
- if (rgiEmojiRegex.test(segment) || isDoubleWidthNonRgiEmojiSequence(segment)) {
216
- width += 2;
217
- continue;
218
- }
219
-
220
- const visibleSegment = baseVisible(segment);
221
- const hangulWidth = hangulClusterWidth(visibleSegment, eastAsianWidthOptions);
222
- if (hangulWidth !== undefined) {
223
- width += hangulWidth;
224
- continue;
225
- }
110
+ return width;
111
+ };
226
112
 
227
- // Everything else: EAW of the cluster's first visible scalar
228
- const codePoint = visibleSegment.codePointAt(0)!;
229
- width += eastAsianWidth(codePoint, eastAsianWidthOptions);
113
+ export const widestLine = (text: string): number => {
114
+ let lineWidth = 0;
230
115
 
231
- // Add width for trailing spacing marks and Halfwidth/Fullwidth Forms
232
- width += trailingWidth(visibleSegment, eastAsianWidthOptions);
116
+ for (const line of text.split("\n")) {
117
+ lineWidth = Math.max(lineWidth, stringWidth(line));
233
118
  }
234
119
 
235
- return width;
236
- }
120
+ return lineWidth;
121
+ };
package/src/ansi/strip.ts CHANGED
@@ -1,33 +1,13 @@
1
- // ANSI escape sequence matching and removal.
2
- // Ported from `ansi-regex` and `strip-ansi` (MIT, Sindre Sorhus).
3
- import { BEL, C1_CSI, C1_ST, ESC } from "./escapes.ts";
4
-
5
- export function ansiRegex({ onlyFirst = false }: { onlyFirst?: boolean } = {}): RegExp {
6
- // Valid string terminator sequences are BEL, ESC\, and C1 ST
7
- const st = `(?:${BEL}|${ESC}\\\\|${C1_ST})`;
8
-
9
- // OSC sequences only: ESC ] ... ST
10
- // The payload stops at the first terminator character rather than scanning
11
- // ahead for one, so an unterminated `ESC ]` cannot rescan the rest of the
12
- // input. Terminals likewise abort a control string on an unexpected ESC.
13
- const osc = `(?:${ESC}\\][^${BEL}${ESC}${C1_ST}]*${st})`;
14
-
15
- // CSI and related: ESC/C1, optional intermediates, optional params
16
- // (supports ; and :) then final byte
17
- const csi = `[${ESC}${C1_CSI}][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]`;
18
-
19
- return new RegExp(`${osc}|${csi}`, onlyFirst ? undefined : "g");
20
- }
21
-
22
- const regex = ansiRegex();
23
-
24
- export function stripAnsi(string: string): string {
25
- // Fast path: ANSI codes require ESC (7-bit) or CSI (8-bit) introducer
26
- if (!string.includes(ESC) && !string.includes(C1_CSI)) {
27
- return string;
28
- }
29
-
30
- // Even though the regex is global, we don't need to reset `.lastIndex`
31
- // because `.replace()` does it automatically.
32
- return string.replace(regex, "");
33
- }
1
+ import { stripVTControlCharacters } from "node:util";
2
+
3
+ // Node's stripVTControlCharacters only understands semicolon-separated CSI
4
+ // parameters, so an ITU T.416 colon-parameter SGR sequence like
5
+ // `\u001B[38:2::255:100:0m` — which Sigil deliberately preserves in output
6
+ // (see sanitize-ansi.ts) — would leave `:2::255:100:0m` behind. Remove those
7
+ // first, then let Node handle everything else.
8
+ // https://github.com/nodejs/node/pull/65379 fixes this upstream; this wrapper
9
+ // can go once the minimum supported Node version ships it.
10
+ const colonSgrRegex = /(?:\u001B\[|\u009B)[\d;]*:[\d;:]*m/g;
11
+
12
+ export const stripAnsi = (input: string): string =>
13
+ stripVTControlCharacters(input.replaceAll(colonSgrRegex, ""));