@greppleai/grip-tui 0.0.0-stage → 0.0.1

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 (158) hide show
  1. package/README.md +818 -2
  2. package/dist/autocomplete.d.ts +48 -0
  3. package/dist/autocomplete.d.ts.map +1 -0
  4. package/dist/autocomplete.js +566 -0
  5. package/dist/autocomplete.js.map +1 -0
  6. package/dist/components/alt-screen-flash.d.ts +13 -0
  7. package/dist/components/alt-screen-flash.d.ts.map +1 -0
  8. package/dist/components/alt-screen-flash.js +37 -0
  9. package/dist/components/alt-screen-flash.js.map +1 -0
  10. package/dist/components/box.d.ts +22 -0
  11. package/dist/components/box.d.ts.map +1 -0
  12. package/dist/components/box.js +104 -0
  13. package/dist/components/box.js.map +1 -0
  14. package/dist/components/cancellable-loader.d.ts +22 -0
  15. package/dist/components/cancellable-loader.d.ts.map +1 -0
  16. package/dist/components/cancellable-loader.js +35 -0
  17. package/dist/components/cancellable-loader.js.map +1 -0
  18. package/dist/components/editor.d.ts +292 -0
  19. package/dist/components/editor.d.ts.map +1 -0
  20. package/dist/components/editor.js +1887 -0
  21. package/dist/components/editor.js.map +1 -0
  22. package/dist/components/h-stack.d.ts +7 -0
  23. package/dist/components/h-stack.d.ts.map +1 -0
  24. package/dist/components/h-stack.js +43 -0
  25. package/dist/components/h-stack.js.map +1 -0
  26. package/dist/components/image.d.ts +32 -0
  27. package/dist/components/image.d.ts.map +1 -0
  28. package/dist/components/image.js +76 -0
  29. package/dist/components/image.js.map +1 -0
  30. package/dist/components/input.d.ts +46 -0
  31. package/dist/components/input.d.ts.map +1 -0
  32. package/dist/components/input.js +352 -0
  33. package/dist/components/input.js.map +1 -0
  34. package/dist/components/loader.d.ts +32 -0
  35. package/dist/components/loader.d.ts.map +1 -0
  36. package/dist/components/loader.js +80 -0
  37. package/dist/components/loader.js.map +1 -0
  38. package/dist/components/markdown.d.ts +132 -0
  39. package/dist/components/markdown.d.ts.map +1 -0
  40. package/dist/components/markdown.js +717 -0
  41. package/dist/components/markdown.js.map +1 -0
  42. package/dist/components/scroll-view.d.ts +53 -0
  43. package/dist/components/scroll-view.d.ts.map +1 -0
  44. package/dist/components/scroll-view.js +173 -0
  45. package/dist/components/scroll-view.js.map +1 -0
  46. package/dist/components/select-list.d.ts +50 -0
  47. package/dist/components/select-list.d.ts.map +1 -0
  48. package/dist/components/select-list.js +159 -0
  49. package/dist/components/select-list.js.map +1 -0
  50. package/dist/components/settings-list.d.ts +52 -0
  51. package/dist/components/settings-list.d.ts.map +1 -0
  52. package/dist/components/settings-list.js +187 -0
  53. package/dist/components/settings-list.js.map +1 -0
  54. package/dist/components/spacer.d.ts +12 -0
  55. package/dist/components/spacer.d.ts.map +1 -0
  56. package/dist/components/spacer.js +23 -0
  57. package/dist/components/spacer.js.map +1 -0
  58. package/dist/components/stack.d.ts +32 -0
  59. package/dist/components/stack.d.ts.map +1 -0
  60. package/dist/components/stack.js +122 -0
  61. package/dist/components/stack.js.map +1 -0
  62. package/dist/components/text.d.ts +19 -0
  63. package/dist/components/text.d.ts.map +1 -0
  64. package/dist/components/text.js +89 -0
  65. package/dist/components/text.js.map +1 -0
  66. package/dist/components/truncated-text.d.ts +13 -0
  67. package/dist/components/truncated-text.d.ts.map +1 -0
  68. package/dist/components/truncated-text.js +51 -0
  69. package/dist/components/truncated-text.js.map +1 -0
  70. package/dist/components/v-stack.d.ts +8 -0
  71. package/dist/components/v-stack.d.ts.map +1 -0
  72. package/dist/components/v-stack.js +27 -0
  73. package/dist/components/v-stack.js.map +1 -0
  74. package/dist/editor-component.d.ts +39 -0
  75. package/dist/editor-component.d.ts.map +1 -0
  76. package/dist/editor-component.js +2 -0
  77. package/dist/editor-component.js.map +1 -0
  78. package/dist/fuzzy.d.ts +16 -0
  79. package/dist/fuzzy.d.ts.map +1 -0
  80. package/dist/fuzzy.js +101 -0
  81. package/dist/fuzzy.js.map +1 -0
  82. package/dist/index.d.ts +30 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +41 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/keybindings.d.ts +243 -0
  87. package/dist/keybindings.d.ts.map +1 -0
  88. package/dist/keybindings.js +209 -0
  89. package/dist/keybindings.js.map +1 -0
  90. package/dist/keys.d.ts +184 -0
  91. package/dist/keys.d.ts.map +1 -0
  92. package/dist/keys.js +1053 -0
  93. package/dist/keys.js.map +1 -0
  94. package/dist/kill-ring.d.ts +30 -0
  95. package/dist/kill-ring.d.ts.map +1 -0
  96. package/dist/kill-ring.js +37 -0
  97. package/dist/kill-ring.js.map +1 -0
  98. package/dist/latex.d.ts +10 -0
  99. package/dist/latex.d.ts.map +1 -0
  100. package/dist/latex.js +1406 -0
  101. package/dist/latex.js.map +1 -0
  102. package/dist/layout-node.d.ts +43 -0
  103. package/dist/layout-node.d.ts.map +1 -0
  104. package/dist/layout-node.js +6 -0
  105. package/dist/layout-node.js.map +1 -0
  106. package/dist/layout.d.ts +40 -0
  107. package/dist/layout.d.ts.map +1 -0
  108. package/dist/layout.js +386 -0
  109. package/dist/layout.js.map +1 -0
  110. package/dist/native-modifiers.d.ts +3 -0
  111. package/dist/native-modifiers.d.ts.map +1 -0
  112. package/dist/native-modifiers.js +53 -0
  113. package/dist/native-modifiers.js.map +1 -0
  114. package/dist/stdin-buffer.d.ts +58 -0
  115. package/dist/stdin-buffer.d.ts.map +1 -0
  116. package/dist/stdin-buffer.js +318 -0
  117. package/dist/stdin-buffer.js.map +1 -0
  118. package/dist/terminal-control.d.ts +5 -0
  119. package/dist/terminal-control.d.ts.map +1 -0
  120. package/dist/terminal-control.js +26 -0
  121. package/dist/terminal-control.js.map +1 -0
  122. package/dist/terminal-image.d.ts +112 -0
  123. package/dist/terminal-image.d.ts.map +1 -0
  124. package/dist/terminal-image.js +508 -0
  125. package/dist/terminal-image.js.map +1 -0
  126. package/dist/terminal.d.ts +117 -0
  127. package/dist/terminal.d.ts.map +1 -0
  128. package/dist/terminal.js +490 -0
  129. package/dist/terminal.js.map +1 -0
  130. package/dist/tui-alt-screen.d.ts +114 -0
  131. package/dist/tui-alt-screen.d.ts.map +1 -0
  132. package/dist/tui-alt-screen.js +950 -0
  133. package/dist/tui-alt-screen.js.map +1 -0
  134. package/dist/tui-main-screen.d.ts +52 -0
  135. package/dist/tui-main-screen.d.ts.map +1 -0
  136. package/dist/tui-main-screen.js +511 -0
  137. package/dist/tui-main-screen.js.map +1 -0
  138. package/dist/tui.d.ts +295 -0
  139. package/dist/tui.d.ts.map +1 -0
  140. package/dist/tui.js +868 -0
  141. package/dist/tui.js.map +1 -0
  142. package/dist/undo-stack.d.ts +17 -0
  143. package/dist/undo-stack.d.ts.map +1 -0
  144. package/dist/undo-stack.js +25 -0
  145. package/dist/undo-stack.js.map +1 -0
  146. package/dist/utils.d.ts +96 -0
  147. package/dist/utils.d.ts.map +1 -0
  148. package/dist/utils.js +1106 -0
  149. package/dist/utils.js.map +1 -0
  150. package/dist/word-navigation.d.ts +25 -0
  151. package/dist/word-navigation.d.ts.map +1 -0
  152. package/dist/word-navigation.js +94 -0
  153. package/dist/word-navigation.js.map +1 -0
  154. package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  155. package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  156. package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  157. package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  158. package/package.json +47 -5
@@ -0,0 +1,717 @@
1
+ import { Marked, Tokenizer } from "marked";
2
+ import { renderLatex } from "../latex.js";
3
+ import { getCapabilities, hyperlink, isImageLine } from "../terminal-image.js";
4
+ import { applyBackgroundToLine, visibleWidth, wrapTextWithAnsi } from "../utils.js";
5
+ const STRICT_STRIKETHROUGH_REGEX = /^(~~)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/;
6
+ const HTML_COMMENT_REGEX = /<!--[\s\S]*?-->/g;
7
+ function stripHtmlComments(html) {
8
+ return html.replace(HTML_COMMENT_REGEX, "");
9
+ }
10
+ class StrictStrikethroughTokenizer extends Tokenizer {
11
+ del(src) {
12
+ const match = STRICT_STRIKETHROUGH_REGEX.exec(src);
13
+ if (!match) {
14
+ return undefined;
15
+ }
16
+ const text = match[2];
17
+ return {
18
+ type: "del",
19
+ raw: match[0],
20
+ text,
21
+ tokens: this.lexer.inlineTokens(text),
22
+ };
23
+ }
24
+ }
25
+ function isEscaped(source, index) {
26
+ let backslashes = 0;
27
+ for (let position = index - 1; position >= 0 && source[position] === "\\"; position--) {
28
+ backslashes++;
29
+ }
30
+ return backslashes % 2 === 1;
31
+ }
32
+ function findClosingDelimiter(source, closing, start) {
33
+ let index = source.indexOf(closing, start);
34
+ while (index >= 0 && isEscaped(source, index)) {
35
+ index = source.indexOf(closing, index + closing.length);
36
+ }
37
+ return index;
38
+ }
39
+ function looksLikePendingDollarMath(source) {
40
+ return /\\[A-Za-z]+|[_^=+*/<>()[\]|±≤≥≠≈∈→⇒∞∫∑√-]/.test(source);
41
+ }
42
+ function looksLikeShellVariableChain(content, following) {
43
+ return /^[A-Z_][A-Z0-9_]*(?:[/:.-][A-Za-z0-9_./:-]*)*$/.test(content) && /^[A-Za-z_][A-Za-z0-9_]*/.test(following);
44
+ }
45
+ function tokenizeInlineLatex(source) {
46
+ let opening = "";
47
+ let closing = "";
48
+ if (source.startsWith("$$")) {
49
+ opening = "$$";
50
+ closing = "$$";
51
+ }
52
+ else if (source.startsWith("\\(")) {
53
+ opening = "\\(";
54
+ closing = "\\)";
55
+ }
56
+ else if (source.startsWith("\\[")) {
57
+ opening = "\\[";
58
+ closing = "\\]";
59
+ }
60
+ else if (source.startsWith("$") && !/^\$\s/.test(source)) {
61
+ opening = "$";
62
+ closing = "$";
63
+ }
64
+ else {
65
+ return undefined;
66
+ }
67
+ const closingIndex = findClosingDelimiter(source, closing, opening.length);
68
+ if (closingIndex >= 0 &&
69
+ opening === "$" &&
70
+ (/\s$/.test(source.slice(opening.length, closingIndex)) ||
71
+ /^\d/.test(source.slice(closingIndex + 1)) ||
72
+ looksLikeShellVariableChain(source.slice(opening.length, closingIndex), source.slice(closingIndex + closing.length)) ||
73
+ source.slice(opening.length, closingIndex).includes("`"))) {
74
+ return undefined;
75
+ }
76
+ if (closingIndex < 0) {
77
+ const pendingSource = source.slice(opening.length);
78
+ if (opening.startsWith("\\") || looksLikePendingDollarMath(pendingSource)) {
79
+ return { type: "latex", raw: source, text: pendingSource, pending: true };
80
+ }
81
+ return undefined;
82
+ }
83
+ const text = source.slice(opening.length, closingIndex);
84
+ if (!text || text.includes("\n")) {
85
+ return undefined;
86
+ }
87
+ const raw = source.slice(0, closingIndex + closing.length);
88
+ return { type: "latex", raw, text };
89
+ }
90
+ function tokenizeBlockLatex(source) {
91
+ const dollarMatch = /^ {0,3}\$\$[ \t]*(?:\n)?([\s\S]*?)\$\$[ \t]*(?:\n|$)/.exec(source);
92
+ if (dollarMatch?.[1]) {
93
+ return { type: "latexBlock", raw: dollarMatch[0], text: dollarMatch[1].trim() };
94
+ }
95
+ const bracketMatch = /^ {0,3}\\\[[ \t]*(?:\n)?([\s\S]*?)\\\][ \t]*(?:\n|$)/.exec(source);
96
+ if (bracketMatch?.[1]) {
97
+ return { type: "latexBlock", raw: bracketMatch[0], text: bracketMatch[1].trim() };
98
+ }
99
+ const pendingBracket = /^ {0,3}\\\[[ \t]*(?:\n)?([\s\S]*)$/.exec(source);
100
+ if (pendingBracket) {
101
+ return { type: "latexBlock", raw: pendingBracket[0], text: pendingBracket[1], pending: true };
102
+ }
103
+ const pendingDollar = /^ {0,3}\$\$[ \t]*(?:\n)?([\s\S]*)$/.exec(source);
104
+ if (pendingDollar?.[1] && looksLikePendingDollarMath(pendingDollar[1])) {
105
+ return { type: "latexBlock", raw: pendingDollar[0], text: pendingDollar[1], pending: true };
106
+ }
107
+ return undefined;
108
+ }
109
+ const LATEX_MARKDOWN_EXTENSIONS = [
110
+ {
111
+ name: "latexBlock",
112
+ level: "block",
113
+ start(source) {
114
+ const match = /(?:^|\n) {0,3}(?:\$\$|\\\[)/.exec(source);
115
+ return match ? match.index + (match[0].startsWith("\n") ? 1 : 0) : undefined;
116
+ },
117
+ tokenizer: tokenizeBlockLatex,
118
+ },
119
+ {
120
+ name: "latex",
121
+ level: "inline",
122
+ start(source) {
123
+ const indices = [source.indexOf("$"), source.indexOf("\\("), source.indexOf("\\[")].filter((index) => index >= 0);
124
+ return indices.length > 0 ? Math.min(...indices) : undefined;
125
+ },
126
+ tokenizer: tokenizeInlineLatex,
127
+ },
128
+ ];
129
+ const markdownParser = new Marked();
130
+ markdownParser.setOptions({
131
+ tokenizer: new StrictStrikethroughTokenizer(),
132
+ });
133
+ markdownParser.use({ extensions: [...LATEX_MARKDOWN_EXTENSIONS] });
134
+ export class Markdown {
135
+ text;
136
+ paddingX; // Left/right padding
137
+ paddingY; // Top/bottom padding
138
+ defaultTextStyle;
139
+ theme;
140
+ options;
141
+ defaultStylePrefix;
142
+ // Cache for rendered output
143
+ cachedText;
144
+ cachedWidth;
145
+ cachedLines;
146
+ constructor(text, paddingX, paddingY, theme, defaultTextStyle, options) {
147
+ this.text = text;
148
+ this.paddingX = paddingX;
149
+ this.paddingY = paddingY;
150
+ this.theme = theme;
151
+ this.defaultTextStyle = defaultTextStyle;
152
+ this.options = options ? { ...options } : {};
153
+ }
154
+ setText(text) {
155
+ this.text = text;
156
+ this.invalidate();
157
+ }
158
+ invalidate() {
159
+ this.cachedText = undefined;
160
+ this.cachedWidth = undefined;
161
+ this.cachedLines = undefined;
162
+ }
163
+ renderMarkdownTokens(text, contentWidth) {
164
+ const tokens = markdownParser.lexer(text);
165
+ const renderedLines = [];
166
+ for (let index = 0; index < tokens.length; index++) {
167
+ const tokenLines = this.renderToken(tokens[index], contentWidth, tokens[index + 1]?.type);
168
+ for (const tokenLine of tokenLines)
169
+ renderedLines.push(tokenLine);
170
+ }
171
+ return renderedLines;
172
+ }
173
+ wrapRenderedLines(renderedLines, contentWidth) {
174
+ const wrappedLines = [];
175
+ for (const line of renderedLines) {
176
+ if (isImageLine(line)) {
177
+ wrappedLines.push(line);
178
+ continue;
179
+ }
180
+ for (const wrappedLine of wrapTextWithAnsi(line, contentWidth))
181
+ wrappedLines.push(wrappedLine);
182
+ }
183
+ return wrappedLines;
184
+ }
185
+ padRenderedLines(lines, width, paddingX) {
186
+ const margin = " ".repeat(paddingX);
187
+ const bgFn = this.defaultTextStyle?.bgColor;
188
+ const contentLines = [];
189
+ for (const line of lines) {
190
+ if (isImageLine(line)) {
191
+ contentLines.push(line);
192
+ continue;
193
+ }
194
+ const lineWithMargins = margin + line + margin;
195
+ if (bgFn) {
196
+ contentLines.push(applyBackgroundToLine(lineWithMargins, width, bgFn));
197
+ continue;
198
+ }
199
+ const paddingNeeded = Math.max(0, width - visibleWidth(lineWithMargins));
200
+ contentLines.push(lineWithMargins + " ".repeat(paddingNeeded));
201
+ }
202
+ return contentLines;
203
+ }
204
+ createVerticalPadding(width) {
205
+ const bgFn = this.defaultTextStyle?.bgColor;
206
+ const emptyLine = " ".repeat(width);
207
+ const emptyLines = [];
208
+ for (let index = 0; index < this.paddingY; index++) {
209
+ emptyLines.push(bgFn ? applyBackgroundToLine(emptyLine, width, bgFn) : emptyLine);
210
+ }
211
+ return emptyLines;
212
+ }
213
+ cacheRenderedLines(width, lines) {
214
+ this.cachedText = this.text;
215
+ this.cachedWidth = width;
216
+ this.cachedLines = lines;
217
+ }
218
+ render(width) {
219
+ if (this.cachedLines && this.cachedText === this.text && this.cachedWidth === width) {
220
+ return this.cachedLines;
221
+ }
222
+ const effectivePaddingX = Math.min(this.paddingX, Math.floor(Math.max(0, width - 1) / 2));
223
+ const contentWidth = Math.max(1, width - effectivePaddingX * 2);
224
+ const text = this.options.transform?.(this.text, contentWidth) ?? this.text;
225
+ if (!text || text.trim() === "") {
226
+ const result = [];
227
+ this.cacheRenderedLines(width, result);
228
+ return result;
229
+ }
230
+ const normalizedText = text.replace(/\t/g, " ");
231
+ const renderedLines = this.renderMarkdownTokens(normalizedText, contentWidth);
232
+ const wrappedLines = this.wrapRenderedLines(renderedLines, contentWidth);
233
+ const contentLines = this.padRenderedLines(wrappedLines, width, effectivePaddingX);
234
+ const verticalPadding = this.createVerticalPadding(width);
235
+ const result = verticalPadding.concat(contentLines, verticalPadding);
236
+ this.cacheRenderedLines(width, result);
237
+ return result.length > 0 ? result : [""];
238
+ }
239
+ /**
240
+ * Apply default text style to a string.
241
+ * This is the base styling applied to all text content.
242
+ * NOTE: Background color is NOT applied here - it's applied at the padding stage
243
+ * to ensure it extends to the full line width.
244
+ */
245
+ applyDefaultStyle(text) {
246
+ if (!this.defaultTextStyle) {
247
+ return text;
248
+ }
249
+ let styled = text;
250
+ // Apply foreground color (NOT background - that's applied at padding stage)
251
+ if (this.defaultTextStyle.color) {
252
+ styled = this.defaultTextStyle.color(styled);
253
+ }
254
+ // Apply text decorations using this.theme
255
+ if (this.defaultTextStyle.bold) {
256
+ styled = this.theme.bold(styled);
257
+ }
258
+ if (this.defaultTextStyle.italic) {
259
+ styled = this.theme.italic(styled);
260
+ }
261
+ if (this.defaultTextStyle.strikethrough) {
262
+ styled = this.theme.strikethrough(styled);
263
+ }
264
+ if (this.defaultTextStyle.underline) {
265
+ styled = this.theme.underline(styled);
266
+ }
267
+ return styled;
268
+ }
269
+ getDefaultStylePrefix() {
270
+ if (!this.defaultTextStyle) {
271
+ return "";
272
+ }
273
+ if (this.defaultStylePrefix !== undefined) {
274
+ return this.defaultStylePrefix;
275
+ }
276
+ const sentinel = "\u0000";
277
+ let styled = sentinel;
278
+ if (this.defaultTextStyle.color) {
279
+ styled = this.defaultTextStyle.color(styled);
280
+ }
281
+ if (this.defaultTextStyle.bold) {
282
+ styled = this.theme.bold(styled);
283
+ }
284
+ if (this.defaultTextStyle.italic) {
285
+ styled = this.theme.italic(styled);
286
+ }
287
+ if (this.defaultTextStyle.strikethrough) {
288
+ styled = this.theme.strikethrough(styled);
289
+ }
290
+ if (this.defaultTextStyle.underline) {
291
+ styled = this.theme.underline(styled);
292
+ }
293
+ const sentinelIndex = styled.indexOf(sentinel);
294
+ this.defaultStylePrefix = sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : "";
295
+ return this.defaultStylePrefix;
296
+ }
297
+ getStylePrefix(styleFn) {
298
+ const sentinel = "\u0000";
299
+ const styled = styleFn(sentinel);
300
+ const sentinelIndex = styled.indexOf(sentinel);
301
+ return sentinelIndex >= 0 ? styled.slice(0, sentinelIndex) : "";
302
+ }
303
+ getDefaultInlineStyleContext() {
304
+ return {
305
+ applyText: (text) => this.applyDefaultStyle(text),
306
+ stylePrefix: this.getDefaultStylePrefix(),
307
+ };
308
+ }
309
+ shouldAppendBlockSpacing(nextTokenType) {
310
+ return Boolean(nextTokenType && nextTokenType !== "space");
311
+ }
312
+ renderHeadingToken(token, nextTokenType) {
313
+ const headingPrefix = `${"#".repeat(token.depth)} `;
314
+ const headingStyle = token.depth === 1
315
+ ? (text) => this.theme.heading(this.theme.bold(this.theme.underline(text)))
316
+ : (text) => this.theme.heading(this.theme.bold(text));
317
+ const styleContext = {
318
+ applyText: headingStyle,
319
+ stylePrefix: this.getStylePrefix(headingStyle),
320
+ };
321
+ const headingText = this.renderInlineTokens(token.tokens || [], styleContext);
322
+ const styledHeading = token.depth >= 3 ? headingStyle(headingPrefix) + headingText : headingText;
323
+ return this.shouldAppendBlockSpacing(nextTokenType) ? [styledHeading, ""] : [styledHeading];
324
+ }
325
+ renderParagraphToken(token, nextTokenType, styleContext) {
326
+ const lines = [this.renderInlineTokens(token.tokens || [], styleContext)];
327
+ if (nextTokenType && nextTokenType !== "list" && nextTokenType !== "space")
328
+ lines.push("");
329
+ return lines;
330
+ }
331
+ renderLatexBlockToken(token, width, nextTokenType) {
332
+ const candidate = !token.pending && this.options.renderLatex !== false ? renderLatex(token.text, { display: true }) : undefined;
333
+ const rendered = candidate?.split("\n").every((line) => visibleWidth(line) <= width)
334
+ ? candidate
335
+ : token.raw.trim();
336
+ const lines = rendered.split("\n").map((line) => this.applyDefaultStyle(line));
337
+ if (this.shouldAppendBlockSpacing(nextTokenType))
338
+ lines.push("");
339
+ return lines;
340
+ }
341
+ renderCodeToken(token, nextTokenType) {
342
+ const indent = this.theme.codeBlockIndent ?? " ";
343
+ const lines = [this.theme.codeBlockBorder(`\`\`\`${token.lang || ""}`)];
344
+ if (this.theme.highlightCode) {
345
+ for (const highlightedLine of this.theme.highlightCode(token.text, token.lang)) {
346
+ lines.push(`${indent}${highlightedLine}`);
347
+ }
348
+ }
349
+ else {
350
+ for (const codeLine of token.text.split("\n")) {
351
+ lines.push(`${indent}${this.theme.codeBlock(codeLine)}`);
352
+ }
353
+ }
354
+ lines.push(this.theme.codeBlockBorder("```"));
355
+ if (this.shouldAppendBlockSpacing(nextTokenType))
356
+ lines.push("");
357
+ return lines;
358
+ }
359
+ renderBlockquoteToken(token, width, nextTokenType) {
360
+ const quoteStyle = (text) => this.theme.quote(this.theme.italic(text));
361
+ const quoteStylePrefix = this.getStylePrefix(quoteStyle);
362
+ const quoteContentWidth = Math.max(1, width - 2);
363
+ const quoteStyleContext = {
364
+ applyText: (text) => text,
365
+ stylePrefix: quoteStylePrefix,
366
+ };
367
+ const renderedQuoteLines = [];
368
+ const quoteTokens = token.tokens || [];
369
+ for (let index = 0; index < quoteTokens.length; index++) {
370
+ renderedQuoteLines.push(...this.renderToken(quoteTokens[index], quoteContentWidth, quoteTokens[index + 1]?.type, quoteStyleContext));
371
+ }
372
+ while (renderedQuoteLines.length > 0 && renderedQuoteLines[renderedQuoteLines.length - 1] === "") {
373
+ renderedQuoteLines.pop();
374
+ }
375
+ const lines = [];
376
+ for (const quoteLine of renderedQuoteLines) {
377
+ const lineWithReappliedStyle = quoteStylePrefix
378
+ ? quoteLine.replace(/\x1b\[0m/g, `\x1b[0m${quoteStylePrefix}`)
379
+ : quoteLine;
380
+ for (const wrappedLine of wrapTextWithAnsi(quoteStyle(lineWithReappliedStyle), quoteContentWidth)) {
381
+ lines.push(this.theme.quoteBorder("│ ") + wrappedLine);
382
+ }
383
+ }
384
+ if (this.shouldAppendBlockSpacing(nextTokenType))
385
+ lines.push("");
386
+ return lines;
387
+ }
388
+ renderHorizontalRule(width, nextTokenType) {
389
+ const lines = [this.theme.hr("─".repeat(Math.min(width, 80)))];
390
+ if (this.shouldAppendBlockSpacing(nextTokenType))
391
+ lines.push("");
392
+ return lines;
393
+ }
394
+ renderHtmlBlock(token) {
395
+ if (!("raw" in token) || typeof token.raw !== "string")
396
+ return [];
397
+ const visibleHtml = stripHtmlComments(token.raw).trim();
398
+ return visibleHtml ? [this.applyDefaultStyle(visibleHtml)] : [];
399
+ }
400
+ renderFallbackToken(token) {
401
+ return "text" in token && typeof token.text === "string" ? [token.text] : [];
402
+ }
403
+ renderToken(token, width, nextTokenType, styleContext) {
404
+ switch (token.type) {
405
+ case "heading":
406
+ return this.renderHeadingToken(token, nextTokenType);
407
+ case "paragraph":
408
+ return this.renderParagraphToken(token, nextTokenType, styleContext);
409
+ case "text":
410
+ return [this.renderInlineTokens([token], styleContext)];
411
+ case "latexBlock":
412
+ return this.renderLatexBlockToken(token, width, nextTokenType);
413
+ case "code":
414
+ return this.renderCodeToken(token, nextTokenType);
415
+ case "list":
416
+ return this.renderList(token, 0, width, styleContext);
417
+ case "table":
418
+ return this.renderTable(token, width, nextTokenType, styleContext);
419
+ case "blockquote":
420
+ return this.renderBlockquoteToken(token, width, nextTokenType);
421
+ case "hr":
422
+ return this.renderHorizontalRule(width, nextTokenType);
423
+ case "html":
424
+ return this.renderHtmlBlock(token);
425
+ case "space":
426
+ return [""];
427
+ default:
428
+ return this.renderFallbackToken(token);
429
+ }
430
+ }
431
+ applyInlineText(text, styleContext) {
432
+ return text
433
+ .split("\n")
434
+ .map((segment) => styleContext.applyText(segment))
435
+ .join("\n");
436
+ }
437
+ renderInlineLinkToken(token, styleContext) {
438
+ const linkText = this.renderInlineTokens(token.tokens || [], styleContext);
439
+ const styledLink = this.theme.link(this.theme.underline(linkText));
440
+ if (getCapabilities().hyperlinks) {
441
+ // OSC 8: render as a clickable hyperlink. The URL is not printed inline,
442
+ // so we always show only the link text regardless of whether it matches href.
443
+ return hyperlink(styledLink, token.href) + styleContext.stylePrefix;
444
+ }
445
+ // Fallback: print URL in parentheses when text differs from href.
446
+ // Compare raw token.text (not styled) against href for the equality check.
447
+ // For mailto: links strip the prefix (autolinked emails use text="foo@bar.com"
448
+ // but href="mailto:foo@bar.com").
449
+ const hrefForComparison = token.href.startsWith("mailto:") ? token.href.slice(7) : token.href;
450
+ if (token.text === token.href || token.text === hrefForComparison) {
451
+ return styledLink + styleContext.stylePrefix;
452
+ }
453
+ return styledLink + this.theme.linkUrl(` (${token.href})`) + styleContext.stylePrefix;
454
+ }
455
+ renderInlineLatexToken(token, styleContext) {
456
+ const rendered = !token.pending && this.options.renderLatex !== false ? (renderLatex(token.text) ?? token.raw) : token.raw;
457
+ return this.applyInlineText(rendered, styleContext);
458
+ }
459
+ renderInlineTextToken(token, styleContext) {
460
+ // Text tokens in list items can have nested tokens for inline formatting.
461
+ return token.tokens?.length
462
+ ? this.renderInlineTokens(token.tokens, styleContext)
463
+ : this.applyInlineText(token.text, styleContext);
464
+ }
465
+ renderInlineHtmlToken(token, styleContext) {
466
+ if (!("raw" in token) || typeof token.raw !== "string")
467
+ return "";
468
+ const visibleHtml = stripHtmlComments(token.raw);
469
+ return visibleHtml ? this.applyInlineText(visibleHtml, styleContext) : "";
470
+ }
471
+ renderInlineFallbackToken(token, styleContext) {
472
+ if (!("text" in token) || typeof token.text !== "string")
473
+ return "";
474
+ return this.applyInlineText(token.text, styleContext);
475
+ }
476
+ renderInlineToken(token, styleContext) {
477
+ const { stylePrefix } = styleContext;
478
+ switch (token.type) {
479
+ case "latex":
480
+ return this.renderInlineLatexToken(token, styleContext);
481
+ case "escape":
482
+ return this.applyInlineText(this.options.preserveBackslashEscapes ? token.raw : token.text, styleContext);
483
+ case "text":
484
+ return this.renderInlineTextToken(token, styleContext);
485
+ case "paragraph":
486
+ return this.renderInlineTokens(token.tokens || [], styleContext);
487
+ case "strong":
488
+ return this.theme.bold(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;
489
+ case "em":
490
+ return this.theme.italic(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;
491
+ case "codespan":
492
+ return this.theme.code(token.text) + stylePrefix;
493
+ case "link":
494
+ return this.renderInlineLinkToken(token, styleContext);
495
+ case "br":
496
+ return "\n";
497
+ case "del":
498
+ return this.theme.strikethrough(this.renderInlineTokens(token.tokens || [], styleContext)) + stylePrefix;
499
+ case "html":
500
+ return this.renderInlineHtmlToken(token, styleContext);
501
+ default:
502
+ return this.renderInlineFallbackToken(token, styleContext);
503
+ }
504
+ }
505
+ renderInlineTokens(tokens, styleContext) {
506
+ const resolvedStyleContext = styleContext ?? this.getDefaultInlineStyleContext();
507
+ let result = "";
508
+ for (const token of tokens) {
509
+ result += this.renderInlineToken(token, resolvedStyleContext);
510
+ }
511
+ while (resolvedStyleContext.stylePrefix && result.endsWith(resolvedStyleContext.stylePrefix)) {
512
+ result = result.slice(0, -resolvedStyleContext.stylePrefix.length);
513
+ }
514
+ return result;
515
+ }
516
+ getOrderedListMarker(item) {
517
+ const match = /^(?: {0,3})(\d{1,9}[.)])[ \t]+/.exec(item.raw);
518
+ return match ? `${match[1]} ` : undefined;
519
+ }
520
+ getListItemMarker(token, item, index, startNumber) {
521
+ if (!token.ordered)
522
+ return "- ";
523
+ if (this.options.preserveOrderedListMarkers) {
524
+ return this.getOrderedListMarker(item) ?? `${startNumber + index}. `;
525
+ }
526
+ return `${startNumber + index}. `;
527
+ }
528
+ renderListItem(token, item, index, startNumber, depth, width, styleContext) {
529
+ const indent = " ".repeat(depth);
530
+ const bullet = this.getListItemMarker(token, item, index, startNumber);
531
+ const taskMarker = item.task ? `[${item.checked ? "x" : " "}] ` : "";
532
+ const marker = bullet + taskMarker;
533
+ const firstPrefix = indent + this.theme.listBullet(marker);
534
+ const continuationPrefix = indent + " ".repeat(visibleWidth(marker));
535
+ const itemWidth = Math.max(1, width - visibleWidth(firstPrefix));
536
+ const lines = [];
537
+ let renderedAnyLine = false;
538
+ for (const itemToken of item.tokens) {
539
+ if (itemToken.type === "list") {
540
+ lines.push(...this.renderList(itemToken, depth + 1, width, styleContext));
541
+ renderedAnyLine = true;
542
+ continue;
543
+ }
544
+ const itemLines = this.renderToken(itemToken, itemWidth, undefined, styleContext);
545
+ const wrappedItemLines = itemLines.flatMap((line) => wrapTextWithAnsi(line, itemWidth));
546
+ for (const wrappedLine of wrappedItemLines) {
547
+ const linePrefix = renderedAnyLine ? continuationPrefix : firstPrefix;
548
+ lines.push(linePrefix + wrappedLine);
549
+ renderedAnyLine = true;
550
+ }
551
+ }
552
+ if (!renderedAnyLine)
553
+ lines.push(firstPrefix);
554
+ return lines;
555
+ }
556
+ /**
557
+ * Render a list with proper nesting support
558
+ */
559
+ renderList(token, depth, width, styleContext) {
560
+ const lines = [];
561
+ const startNumber = typeof token.start === "number" ? token.start : 1;
562
+ for (let index = 0; index < token.items.length; index++) {
563
+ lines.push(...this.renderListItem(token, token.items[index], index, startNumber, depth, width, styleContext));
564
+ }
565
+ return lines;
566
+ }
567
+ /**
568
+ * Get the visible width of the longest word in a string.
569
+ */
570
+ getLongestWordWidth(text, maxWidth) {
571
+ const words = text.split(/\s+/).filter((word) => word.length > 0);
572
+ let longest = 0;
573
+ for (const word of words) {
574
+ longest = Math.max(longest, visibleWidth(word));
575
+ }
576
+ if (maxWidth === undefined) {
577
+ return longest;
578
+ }
579
+ return Math.min(longest, maxWidth);
580
+ }
581
+ /**
582
+ * Wrap a table cell to fit into a column.
583
+ *
584
+ * Delegates to wrapTextWithAnsi() so ANSI codes + long tokens are handled
585
+ * consistently with the rest of the renderer.
586
+ */
587
+ wrapCellText(text, maxWidth) {
588
+ return wrapTextWithAnsi(text, Math.max(1, maxWidth));
589
+ }
590
+ /**
591
+ * Render a table with width-aware cell wrapping.
592
+ * Cells that don't fit are wrapped to multiple lines.
593
+ */
594
+ measureTableColumns(token, styleContext) {
595
+ const naturalWidths = [];
596
+ const minimumWidths = [];
597
+ const maxUnbrokenWordWidth = 30;
598
+ for (let column = 0; column < token.header.length; column++) {
599
+ const headerText = this.renderInlineTokens(token.header[column].tokens || [], styleContext);
600
+ naturalWidths[column] = visibleWidth(headerText);
601
+ minimumWidths[column] = Math.max(1, this.getLongestWordWidth(headerText, maxUnbrokenWordWidth));
602
+ }
603
+ for (const row of token.rows) {
604
+ for (let column = 0; column < row.length; column++) {
605
+ const cellText = this.renderInlineTokens(row[column].tokens || [], styleContext);
606
+ naturalWidths[column] = Math.max(naturalWidths[column] || 0, visibleWidth(cellText));
607
+ minimumWidths[column] = Math.max(minimumWidths[column] || 1, this.getLongestWordWidth(cellText, maxUnbrokenWordWidth));
608
+ }
609
+ }
610
+ return { naturalWidths, minimumWidths };
611
+ }
612
+ fitMinimumTableColumnWidths(minimumWidths, availableForCells) {
613
+ if (minimumWidths.reduce((total, width) => total + width, 0) <= availableForCells)
614
+ return minimumWidths;
615
+ const fittedWidths = new Array(minimumWidths.length).fill(1);
616
+ const remaining = availableForCells - minimumWidths.length;
617
+ if (remaining <= 0)
618
+ return fittedWidths;
619
+ const totalWeight = minimumWidths.reduce((total, width) => total + Math.max(0, width - 1), 0);
620
+ const growth = minimumWidths.map((width) => {
621
+ const weight = Math.max(0, width - 1);
622
+ return totalWeight > 0 ? Math.floor((weight / totalWeight) * remaining) : 0;
623
+ });
624
+ for (let column = 0; column < minimumWidths.length; column++)
625
+ fittedWidths[column] += growth[column] ?? 0;
626
+ const allocated = growth.reduce((total, width) => total + width, 0);
627
+ let leftover = remaining - allocated;
628
+ for (let column = 0; leftover > 0 && column < minimumWidths.length; column++) {
629
+ fittedWidths[column]++;
630
+ leftover--;
631
+ }
632
+ return fittedWidths;
633
+ }
634
+ distributeRemainingTableWidth(columnWidths, naturalWidths, remainingWidth) {
635
+ let remaining = remainingWidth;
636
+ while (remaining > 0) {
637
+ let grew = false;
638
+ for (let column = 0; column < columnWidths.length && remaining > 0; column++) {
639
+ if (columnWidths[column] >= naturalWidths[column])
640
+ continue;
641
+ columnWidths[column]++;
642
+ remaining--;
643
+ grew = true;
644
+ }
645
+ if (!grew)
646
+ break;
647
+ }
648
+ }
649
+ calculateTableColumnWidths(naturalWidths, minimumWidths, availableForCells, borderOverhead) {
650
+ const fittedMinimumWidths = this.fitMinimumTableColumnWidths(minimumWidths, availableForCells);
651
+ const totalNaturalWidth = naturalWidths.reduce((total, width) => total + width, 0) + borderOverhead;
652
+ if (totalNaturalWidth <= availableForCells + borderOverhead) {
653
+ return naturalWidths.map((width, column) => Math.max(width, fittedMinimumWidths[column]));
654
+ }
655
+ const minimumCellsWidth = fittedMinimumWidths.reduce((total, width) => total + width, 0);
656
+ const totalGrowPotential = naturalWidths.reduce((total, width, column) => total + Math.max(0, width - fittedMinimumWidths[column]), 0);
657
+ const extraWidth = Math.max(0, availableForCells - minimumCellsWidth);
658
+ const columnWidths = fittedMinimumWidths.map((minimumWidth, column) => {
659
+ const growthPotential = Math.max(0, naturalWidths[column] - minimumWidth);
660
+ const growth = totalGrowPotential > 0 ? Math.floor((growthPotential / totalGrowPotential) * extraWidth) : 0;
661
+ return minimumWidth + growth;
662
+ });
663
+ const allocated = columnWidths.reduce((total, width) => total + width, 0);
664
+ this.distributeRemainingTableWidth(columnWidths, naturalWidths, availableForCells - allocated);
665
+ return columnWidths;
666
+ }
667
+ renderTableBorder(columnWidths, left, junction, right) {
668
+ return `${left}─${columnWidths.map((width) => "─".repeat(width)).join(`─${junction}─`)}─${right}`;
669
+ }
670
+ renderTableCellLines(cells, columnWidths, styleContext) {
671
+ return cells.map((cell, column) => {
672
+ const text = this.renderInlineTokens(cell.tokens || [], styleContext);
673
+ return this.wrapCellText(text, columnWidths[column]);
674
+ });
675
+ }
676
+ // grip-ignore noExcessiveCollectionIterations: Every rendered table line must format each column once, so this rectangular traversal is linear in the emitted cell slots. @class:229-925
677
+ appendTableRow(lines, cellLines, columnWidths, bold) {
678
+ const lineCount = Math.max(...cellLines.map((cell) => cell.length));
679
+ for (let lineIndex = 0; lineIndex < lineCount; lineIndex++) {
680
+ const rowParts = cellLines.map((columnLines, column) => {
681
+ const text = columnLines[lineIndex] || "";
682
+ const padded = text + " ".repeat(Math.max(0, columnWidths[column] - visibleWidth(text)));
683
+ return bold ? this.theme.bold(padded) : padded;
684
+ });
685
+ lines.push(`│ ${rowParts.join(" │ ")} │`);
686
+ }
687
+ }
688
+ renderTable(token, availableWidth, nextTokenType, styleContext) {
689
+ const columnCount = token.header.length;
690
+ if (columnCount === 0)
691
+ return [];
692
+ const borderOverhead = 3 * columnCount + 1;
693
+ const availableForCells = availableWidth - borderOverhead;
694
+ if (availableForCells < columnCount) {
695
+ const fallbackLines = token.raw ? wrapTextWithAnsi(token.raw, availableWidth) : [];
696
+ if (nextTokenType && nextTokenType !== "space")
697
+ fallbackLines.push("");
698
+ return fallbackLines;
699
+ }
700
+ const measurements = this.measureTableColumns(token, styleContext);
701
+ const columnWidths = this.calculateTableColumnWidths(measurements.naturalWidths, measurements.minimumWidths, availableForCells, borderOverhead);
702
+ const lines = [this.renderTableBorder(columnWidths, "┌", "┬", "┐")];
703
+ this.appendTableRow(lines, this.renderTableCellLines(token.header, columnWidths, styleContext), columnWidths, true);
704
+ const separator = this.renderTableBorder(columnWidths, "├", "┼", "┤");
705
+ lines.push(separator);
706
+ for (let rowIndex = 0; rowIndex < token.rows.length; rowIndex++) {
707
+ this.appendTableRow(lines, this.renderTableCellLines(token.rows[rowIndex], columnWidths, styleContext), columnWidths, false);
708
+ if (rowIndex < token.rows.length - 1)
709
+ lines.push(separator);
710
+ }
711
+ lines.push(this.renderTableBorder(columnWidths, "└", "┴", "┘"));
712
+ if (nextTokenType && nextTokenType !== "space")
713
+ lines.push("");
714
+ return lines;
715
+ }
716
+ }
717
+ //# sourceMappingURL=markdown.js.map