openhorse 0.2.21 → 0.2.22

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 (205) hide show
  1. package/dist/cli.js +25 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/index.d.ts.map +1 -1
  4. package/dist/commands/index.js +189 -49
  5. package/dist/commands/index.js.map +1 -1
  6. package/dist/commands/parser.d.ts.map +1 -1
  7. package/dist/commands/parser.js +8 -3
  8. package/dist/commands/parser.js.map +1 -1
  9. package/dist/commands/types.d.ts +2 -0
  10. package/dist/commands/types.d.ts.map +1 -1
  11. package/dist/commands/types.js.map +1 -1
  12. package/dist/core/cost-tracker.d.ts +45 -5
  13. package/dist/core/cost-tracker.d.ts.map +1 -1
  14. package/dist/core/cost-tracker.js +78 -13
  15. package/dist/core/cost-tracker.js.map +1 -1
  16. package/dist/framework/index.d.ts +1 -1
  17. package/dist/framework/index.d.ts.map +1 -1
  18. package/dist/framework/index.js.map +1 -1
  19. package/dist/framework/query.d.ts +25 -0
  20. package/dist/framework/query.d.ts.map +1 -1
  21. package/dist/framework/query.js +196 -49
  22. package/dist/framework/query.js.map +1 -1
  23. package/dist/framework/store.d.ts +6 -1
  24. package/dist/framework/store.d.ts.map +1 -1
  25. package/dist/framework/store.js +10 -1
  26. package/dist/framework/store.js.map +1 -1
  27. package/dist/harness/ledger.d.ts.map +1 -1
  28. package/dist/harness/ledger.js +6 -2
  29. package/dist/harness/ledger.js.map +1 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
  33. package/dist/ink-ui/components/StatusLine.js +0 -2
  34. package/dist/ink-ui/components/StatusLine.js.map +1 -1
  35. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  36. package/dist/ink-ui/screens/ReplScreen.js +12 -5
  37. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  38. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  39. package/dist/runtime/agent-runtime-controller.js +11 -1
  40. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  41. package/dist/runtime/chat-controller.d.ts.map +1 -1
  42. package/dist/runtime/chat-controller.js +229 -86
  43. package/dist/runtime/chat-controller.js.map +1 -1
  44. package/dist/runtime/rich-text/ansi-parser.d.ts +1 -1
  45. package/dist/runtime/rich-text/ansi-parser.d.ts.map +1 -1
  46. package/dist/runtime/rich-text/ansi-parser.js +93 -55
  47. package/dist/runtime/rich-text/ansi-parser.js.map +1 -1
  48. package/dist/runtime/rich-text/layout.d.ts +2 -11
  49. package/dist/runtime/rich-text/layout.d.ts.map +1 -1
  50. package/dist/runtime/rich-text/layout.js +280 -245
  51. package/dist/runtime/rich-text/layout.js.map +1 -1
  52. package/dist/runtime/rich-text/markdown-parser.js +31 -24
  53. package/dist/runtime/rich-text/markdown-parser.js.map +1 -1
  54. package/dist/runtime/rich-text/types.d.ts +1 -1
  55. package/dist/runtime/rich-text/types.d.ts.map +1 -1
  56. package/dist/runtime/subagents/production.d.ts.map +1 -1
  57. package/dist/runtime/subagents/production.js +28 -3
  58. package/dist/runtime/subagents/production.js.map +1 -1
  59. package/dist/runtime/subagents/result-parser.d.ts.map +1 -1
  60. package/dist/runtime/subagents/result-parser.js +54 -40
  61. package/dist/runtime/subagents/result-parser.js.map +1 -1
  62. package/dist/runtime/subagents/types.d.ts +2 -0
  63. package/dist/runtime/subagents/types.d.ts.map +1 -1
  64. package/dist/runtime/subagents/types.js +5 -1
  65. package/dist/runtime/subagents/types.js.map +1 -1
  66. package/dist/runtime/ui-events.d.ts +13 -0
  67. package/dist/runtime/ui-events.d.ts.map +1 -1
  68. package/dist/runtime/ui-events.js.map +1 -1
  69. package/dist/runtime/ui-view-model.d.ts +9 -0
  70. package/dist/runtime/ui-view-model.d.ts.map +1 -1
  71. package/dist/runtime/ui-view-model.js +59 -37
  72. package/dist/runtime/ui-view-model.js.map +1 -1
  73. package/dist/services/compact/auto-compact.d.ts +15 -2
  74. package/dist/services/compact/auto-compact.d.ts.map +1 -1
  75. package/dist/services/compact/auto-compact.js +56 -24
  76. package/dist/services/compact/auto-compact.js.map +1 -1
  77. package/dist/services/compact/compact.d.ts +2 -0
  78. package/dist/services/compact/compact.d.ts.map +1 -1
  79. package/dist/services/compact/compact.js +22 -5
  80. package/dist/services/compact/compact.js.map +1 -1
  81. package/dist/services/compact/coordinator.d.ts +26 -0
  82. package/dist/services/compact/coordinator.d.ts.map +1 -0
  83. package/dist/services/compact/coordinator.js +44 -0
  84. package/dist/services/compact/coordinator.js.map +1 -0
  85. package/dist/services/compact/index.d.ts +1 -0
  86. package/dist/services/compact/index.d.ts.map +1 -1
  87. package/dist/services/compact/index.js +3 -1
  88. package/dist/services/compact/index.js.map +1 -1
  89. package/dist/services/compact/summary-generator.d.ts +6 -2
  90. package/dist/services/compact/summary-generator.d.ts.map +1 -1
  91. package/dist/services/compact/summary-generator.js +73 -31
  92. package/dist/services/compact/summary-generator.js.map +1 -1
  93. package/dist/services/config-dir.d.ts +11 -1
  94. package/dist/services/config-dir.d.ts.map +1 -1
  95. package/dist/services/config-dir.js +19 -2
  96. package/dist/services/config-dir.js.map +1 -1
  97. package/dist/services/config.d.ts +4 -2
  98. package/dist/services/config.d.ts.map +1 -1
  99. package/dist/services/config.js +40 -0
  100. package/dist/services/config.js.map +1 -1
  101. package/dist/services/doctor.d.ts +2 -0
  102. package/dist/services/doctor.d.ts.map +1 -1
  103. package/dist/services/doctor.js +8 -1
  104. package/dist/services/doctor.js.map +1 -1
  105. package/dist/services/global-config.d.ts +10 -0
  106. package/dist/services/global-config.d.ts.map +1 -1
  107. package/dist/services/global-config.js.map +1 -1
  108. package/dist/services/llm.d.ts +21 -4
  109. package/dist/services/llm.d.ts.map +1 -1
  110. package/dist/services/llm.js +66 -8
  111. package/dist/services/llm.js.map +1 -1
  112. package/dist/services/model-context.d.ts +36 -0
  113. package/dist/services/model-context.d.ts.map +1 -1
  114. package/dist/services/model-context.js +238 -32
  115. package/dist/services/model-context.js.map +1 -1
  116. package/dist/services/redaction.js +1 -1
  117. package/dist/services/redaction.js.map +1 -1
  118. package/dist/services/session-storage.d.ts +39 -1
  119. package/dist/services/session-storage.d.ts.map +1 -1
  120. package/dist/services/session-storage.js +121 -27
  121. package/dist/services/session-storage.js.map +1 -1
  122. package/dist/services/usage-state.d.ts +55 -5
  123. package/dist/services/usage-state.d.ts.map +1 -1
  124. package/dist/services/usage-state.js +185 -59
  125. package/dist/services/usage-state.js.map +1 -1
  126. package/dist/services/verification-profile.d.ts.map +1 -1
  127. package/dist/services/verification-profile.js +3 -2
  128. package/dist/services/verification-profile.js.map +1 -1
  129. package/dist/services/workspace-diff.d.ts.map +1 -1
  130. package/dist/services/workspace-diff.js +34 -3
  131. package/dist/services/workspace-diff.js.map +1 -1
  132. package/dist/services/yaml-config.js +1 -1
  133. package/dist/skills/runtime.d.ts +9 -5
  134. package/dist/skills/runtime.d.ts.map +1 -1
  135. package/dist/skills/runtime.js +157 -12
  136. package/dist/skills/runtime.js.map +1 -1
  137. package/dist/terminal-ui/launch.d.ts +2 -0
  138. package/dist/terminal-ui/launch.d.ts.map +1 -1
  139. package/dist/terminal-ui/launch.js +65 -21
  140. package/dist/terminal-ui/launch.js.map +1 -1
  141. package/dist/terminal-ui/raw-editor.d.ts +9 -0
  142. package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
  143. package/dist/terminal-ui/raw-editor.js +207 -30
  144. package/dist/terminal-ui/raw-editor.js.map +1 -1
  145. package/dist/tools/bash_security.d.ts.map +1 -1
  146. package/dist/tools/bash_security.js +14 -5
  147. package/dist/tools/bash_security.js.map +1 -1
  148. package/dist/tools/index.d.ts.map +1 -1
  149. package/dist/tools/index.js +48 -20
  150. package/dist/tools/index.js.map +1 -1
  151. package/dist/tools/plan.d.ts.map +1 -1
  152. package/dist/tools/plan.js +7 -0
  153. package/dist/tools/plan.js.map +1 -1
  154. package/dist/tools/web.d.ts +10 -0
  155. package/dist/tools/web.d.ts.map +1 -1
  156. package/dist/tools/web.js +81 -1
  157. package/dist/tools/web.js.map +1 -1
  158. package/dist/tui-core/input-parser.d.ts +1 -1
  159. package/dist/tui-core/input-parser.d.ts.map +1 -1
  160. package/dist/tui-core/input-parser.js +20 -1
  161. package/dist/tui-core/input-parser.js.map +1 -1
  162. package/dist/tui-core/style.d.ts +16 -0
  163. package/dist/tui-core/style.d.ts.map +1 -1
  164. package/dist/tui-core/style.js +22 -1
  165. package/dist/tui-core/style.js.map +1 -1
  166. package/dist/tui-ui/inline-surface.d.ts +62 -11
  167. package/dist/tui-ui/inline-surface.d.ts.map +1 -1
  168. package/dist/tui-ui/inline-surface.js +343 -92
  169. package/dist/tui-ui/inline-surface.js.map +1 -1
  170. package/dist/tui-ui/launch.d.ts +1 -0
  171. package/dist/tui-ui/launch.d.ts.map +1 -1
  172. package/dist/tui-ui/launch.js +185 -41
  173. package/dist/tui-ui/launch.js.map +1 -1
  174. package/dist/tui-ui/layout.d.ts +51 -2
  175. package/dist/tui-ui/layout.d.ts.map +1 -1
  176. package/dist/tui-ui/layout.js +226 -76
  177. package/dist/tui-ui/layout.js.map +1 -1
  178. package/dist/tui-ui/runner.d.ts +69 -8
  179. package/dist/tui-ui/runner.d.ts.map +1 -1
  180. package/dist/tui-ui/runner.js +304 -41
  181. package/dist/tui-ui/runner.js.map +1 -1
  182. package/dist/tui-ui/state.d.ts +5 -4
  183. package/dist/tui-ui/state.d.ts.map +1 -1
  184. package/dist/tui-ui/state.js +65 -19
  185. package/dist/tui-ui/state.js.map +1 -1
  186. package/dist/tui-ui/theme.d.ts +13 -0
  187. package/dist/tui-ui/theme.d.ts.map +1 -0
  188. package/dist/tui-ui/theme.js +20 -0
  189. package/dist/tui-ui/theme.js.map +1 -0
  190. package/dist/tui-ui/transcript-cache.d.ts +5 -3
  191. package/dist/tui-ui/transcript-cache.d.ts.map +1 -1
  192. package/dist/tui-ui/transcript-cache.js +16 -9
  193. package/dist/tui-ui/transcript-cache.js.map +1 -1
  194. package/dist/tui-ui/transcript-layout.d.ts +21 -0
  195. package/dist/tui-ui/transcript-layout.d.ts.map +1 -0
  196. package/dist/tui-ui/transcript-layout.js +398 -0
  197. package/dist/tui-ui/transcript-layout.js.map +1 -0
  198. package/dist/ui/status-bar.d.ts +1 -1
  199. package/dist/ui/status-bar.d.ts.map +1 -1
  200. package/dist/ui/status-bar.js +2 -15
  201. package/dist/ui/status-bar.js.map +1 -1
  202. package/dist/ui-v2/components/shell.d.ts.map +1 -1
  203. package/dist/ui-v2/components/shell.js +0 -8
  204. package/dist/ui-v2/components/shell.js.map +1 -1
  205. package/package.json +1 -1
@@ -1,11 +1,5 @@
1
1
  "use strict";
2
- /**
3
- * Rich text layout: convert RichTextBlock[] to StyledRow[].
4
- *
5
- * Pure function: takes blocks + width + theme, returns styled rows.
6
- * Handles width degradation, code wrapping, diff prefixes, table fallback.
7
- * Every output row satisfies stringWidth(row) <= safeWidth.
8
- */
2
+ /** Pure rich-text layout: structured Markdown blocks to terminal-safe styled rows. */
9
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
10
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
5
  };
@@ -14,314 +8,355 @@ exports.layoutRichText = layoutRichText;
14
8
  const string_width_1 = __importDefault(require("string-width"));
15
9
  const grapheme_1 = require("../composer/grapheme");
16
10
  const style_1 = require("../../tui-core/style");
17
- /**
18
- * Layout a RichTextDocument into StyledRow[].
19
- * Each row's visual width <= safeWidth.
20
- */
11
+ /** Every returned row has a visual width less than or equal to options.width. */
21
12
  function layoutRichText(doc, options) {
22
- const safeWidth = Math.max(1, options.width);
23
- const rows = [];
24
- for (const block of doc.blocks) {
25
- const blockRows = layoutBlock(block, safeWidth, options.theme, options.indent ?? 0);
26
- rows.push(...blockRows);
27
- }
28
- return rows;
13
+ const width = normalizeWidth(options.width);
14
+ const indent = normalizeIndent(options.indent ?? 0, width);
15
+ return doc.blocks.flatMap(block => layoutBlock(block, width, options.theme, indent));
29
16
  }
30
17
  function layoutBlock(block, width, theme, indent) {
31
18
  switch (block.type) {
32
19
  case 'paragraph':
33
- return layoutParagraph(block.spans, width, theme('assistantText'), indent);
20
+ return layoutInline(block.spans, width, theme, theme('assistantText'), indent);
34
21
  case 'heading':
35
- return layoutParagraph(block.spans, width, theme('heading'), indent);
22
+ return layoutInline(block.spans, width, theme, theme('heading'), indent);
36
23
  case 'list':
37
24
  return layoutList(block, width, theme, indent);
38
25
  case 'quote':
39
26
  return layoutQuote(block, width, theme, indent);
40
27
  case 'code':
41
- return layoutCode(block.lines, block.language, width, theme('code'), indent);
28
+ return layoutCode(block.lines, width, theme, indent, block.language);
42
29
  case 'diff':
43
30
  return layoutDiff(block.lines, width, theme, indent);
44
31
  case 'table':
45
32
  return layoutTable(block, width, theme, indent);
46
- case 'rule':
47
- return [[{ text: '─'.repeat(width), style: theme('muted') }]];
48
- default:
49
- return [];
50
- }
51
- }
52
- function layoutParagraph(spans, width, style, indent) {
53
- const availableWidth = Math.max(1, width - indent);
54
- const styledSpans = spans.map(s => ({
55
- text: s.text,
56
- style: resolveSpanStyle(s, style),
57
- }));
58
- // If no text content, return an empty row with indent.
59
- const fullText = styledSpans.map(s => s.text).join('');
60
- if (!fullText)
61
- return [[{ text: ' '.repeat(indent), style }]];
62
- // Check if all spans share the same style — if so, use simple wrap.
63
- const allSameStyle = styledSpans.every(s => (0, style_1.styleKey)(s.style) === (0, style_1.styleKey)(styledSpans[0].style));
64
- if (allSameStyle) {
65
- // Simple path: join text, wrap, apply single style.
66
- const lines = fullText.split('\n');
67
- const rows = [];
68
- for (const line of lines) {
69
- const wrapped = wrapText(line, availableWidth);
70
- for (const wrappedLine of wrapped) {
71
- rows.push([{ text: ' '.repeat(indent) + wrappedLine, style: styledSpans[0].style }]);
72
- }
73
- }
74
- return rows;
75
- }
76
- // Mixed-style path: build a character-to-style map, wrap the joined text,
77
- // then reconstruct spans per row preserving style boundaries.
78
- const charStyles = [];
79
- for (const span of styledSpans) {
80
- Array.from(span.text).forEach(() => {
81
- charStyles.push(span.style);
82
- });
83
- }
84
- const lines = fullText.split('\n');
85
- const rows = [];
86
- let charOffset = 0;
87
- for (const line of lines) {
88
- const wrapped = wrapText(line, availableWidth);
89
- for (const wrappedLine of wrapped) {
90
- const lineStart = charOffset;
91
- const lineEnd = charOffset + Array.from(wrappedLine).length;
92
- // Build spans for this line from the charStyles map.
93
- const rowSpans = [{ text: ' '.repeat(indent), style }];
94
- let currentSpanText = '';
95
- let currentSpanStyle = null;
96
- for (let ci = lineStart; ci < lineEnd && ci < charStyles.length; ci++) {
97
- const ch = Array.from(fullText)[ci] || '';
98
- const chStyle = charStyles[ci];
99
- if (currentSpanStyle !== null && (0, style_1.styleKey)(chStyle) !== (0, style_1.styleKey)(currentSpanStyle)) {
100
- if (currentSpanText) {
101
- rowSpans.push({ text: currentSpanText, style: currentSpanStyle });
102
- }
103
- currentSpanText = ch;
104
- currentSpanStyle = chStyle;
105
- }
106
- else {
107
- currentSpanText += ch;
108
- currentSpanStyle = chStyle;
109
- }
110
- }
111
- if (currentSpanText && currentSpanStyle !== null) {
112
- rowSpans.push({ text: currentSpanText, style: currentSpanStyle });
113
- }
114
- rows.push(rowSpans);
115
- charOffset = lineEnd;
33
+ case 'rule': {
34
+ const available = Math.max(1, width - indent);
35
+ return [[
36
+ { text: ' '.repeat(indent), style: theme('muted') },
37
+ { text: '─'.repeat(available), style: theme('muted') },
38
+ ]];
116
39
  }
117
- // Skip the newline character in the charStyles map.
118
- charOffset += 1;
119
40
  }
120
- return rows;
121
41
  }
122
- function wrapText(text, maxWidth) {
123
- if (!text)
124
- return [''];
125
- if ((0, string_width_1.default)(text) <= maxWidth)
126
- return [text];
127
- const words = text.split(' ');
128
- const lines = [];
129
- let current = '';
130
- for (const word of words) {
131
- const candidate = current ? `${current} ${word}` : word;
132
- if ((0, string_width_1.default)(candidate) <= maxWidth) {
133
- current = candidate;
134
- }
135
- else {
136
- if (current)
137
- lines.push(current);
138
- // Hard-wrap long words.
139
- if ((0, string_width_1.default)(word) > maxWidth) {
140
- const chunks = splitByVisualWidth(word, maxWidth);
141
- lines.push(...chunks.slice(0, -1));
142
- current = chunks[chunks.length - 1];
143
- }
144
- else {
145
- current = word;
146
- }
147
- }
148
- }
149
- if (current)
150
- lines.push(current);
151
- return lines.length > 0 ? lines : [''];
152
- }
153
- function splitByVisualWidth(text, maxWidth) {
154
- if (text.length === 0)
155
- return [''];
156
- const chunks = [];
157
- let current = '';
158
- for (const part of (0, grapheme_1.segmentGraphemes)(text)) {
159
- const next = `${current}${part.segment}`;
160
- if (current && (0, string_width_1.default)(next) > maxWidth) {
161
- chunks.push(current);
162
- current = part.segment;
163
- }
164
- else {
165
- current = next;
166
- }
167
- }
168
- chunks.push(current);
169
- return chunks;
42
+ function layoutInline(spans, width, theme, baseStyle, indent) {
43
+ const prefix = indent > 0 ? [{ text: ' '.repeat(indent), style: baseStyle }] : [];
44
+ const styled = spans.map(span => ({
45
+ text: span.text,
46
+ style: resolveSpanStyle(span, baseStyle, theme),
47
+ }));
48
+ return layoutPrefixedSpans(styled, width, prefix, prefix, true);
170
49
  }
171
50
  function layoutList(block, width, theme, indent) {
172
51
  const rows = [];
173
- const marker = block.ordered ? '1. ' : '- ';
174
- const childIndent = indent + marker.length;
175
- block.items.forEach((item, i) => {
176
- const prefix = block.ordered ? `${i + 1}. ` : '- ';
177
- const availableWidth = Math.max(1, width - indent);
178
- // Render first block with prefix, rest with child indent.
179
- item.forEach((subBlock, j) => {
180
- if (j === 0) {
181
- const subRows = layoutBlock(subBlock, width, theme, indent);
182
- if (subRows.length > 0) {
183
- // Prepend marker to first row.
184
- const firstRow = subRows[0];
185
- rows.push([{ text: prefix + ' '.repeat(Math.max(0, indent)) + (firstRow[0]?.text ?? '').trimStart(), style: theme('muted') }]);
186
- rows.push(...subRows.slice(1));
187
- }
52
+ block.items.forEach((item, itemIndex) => {
53
+ const marker = block.ordered ? `${itemIndex + 1}. ` : '- ';
54
+ const markerIndent = normalizeIndent(indent, width);
55
+ const prefixStyle = theme('muted');
56
+ const firstPrefix = [
57
+ { text: ' '.repeat(markerIndent), style: prefixStyle },
58
+ { text: marker, style: prefixStyle },
59
+ ];
60
+ const continuationPrefix = [
61
+ { text: ' '.repeat(markerIndent + (0, string_width_1.default)(marker)), style: prefixStyle },
62
+ ];
63
+ item.forEach((subBlock, blockIndex) => {
64
+ if (blockIndex === 0 && (subBlock.type === 'paragraph' || subBlock.type === 'heading')) {
65
+ const base = subBlock.type === 'heading' ? theme('heading') : theme('assistantText');
66
+ const styled = subBlock.spans.map(span => ({
67
+ text: span.text,
68
+ style: resolveSpanStyle(span, base, theme),
69
+ }));
70
+ rows.push(...layoutPrefixedSpans(styled, width, firstPrefix, continuationPrefix, true));
71
+ return;
188
72
  }
189
- else {
190
- rows.push(...layoutBlock(subBlock, width, theme, childIndent));
73
+ if (blockIndex === 0) {
74
+ rows.push(clampRow(firstPrefix, width));
191
75
  }
76
+ const childIndent = normalizeIndent(markerIndent + (0, string_width_1.default)(marker), width);
77
+ rows.push(...layoutBlock(subBlock, width, theme, childIndent));
192
78
  });
193
- void availableWidth;
194
79
  });
195
80
  return rows;
196
81
  }
197
82
  function layoutQuote(block, width, theme, indent) {
83
+ const prefix = [{
84
+ text: `${' '.repeat(normalizeIndent(indent, width))}> `,
85
+ style: theme('muted'),
86
+ }];
87
+ const prefixWidth = rowWidth(prefix);
88
+ const childWidth = Math.max(1, width - prefixWidth);
198
89
  const rows = [];
199
- const quoteIndent = indent + 2;
200
- for (const subBlock of block.blocks) {
201
- const subRows = layoutBlock(subBlock, width, theme, quoteIndent);
202
- for (const row of subRows) {
203
- // Add quote prefix.
204
- rows.push([{ text: '> ', style: theme('muted') }, ...row]);
90
+ for (const child of block.blocks) {
91
+ const childRows = layoutBlock(child, childWidth, theme, 0);
92
+ if (childRows.length === 0)
93
+ rows.push(clampRow(prefix, width));
94
+ for (const childRow of childRows) {
95
+ rows.push(clampRow([...prefix, ...childRow], width));
205
96
  }
206
97
  }
207
98
  return rows;
208
99
  }
209
- function layoutCode(lines, _language, width, style, indent) {
210
- const availableWidth = Math.max(1, width - indent - 1); // 1 for border/prefix
100
+ function layoutCode(lines, width, theme, indent, language) {
101
+ const safeIndent = normalizeIndent(indent, width);
102
+ const available = Math.max(1, width - safeIndent);
103
+ const style = theme('code');
104
+ const prefix = safeIndent > 0 ? [{ text: ' '.repeat(safeIndent), style }] : [];
211
105
  const rows = [];
212
- for (const line of lines) {
213
- if ((0, string_width_1.default)(line) <= availableWidth) {
214
- rows.push([{ text: ' '.repeat(indent) + line, style }]);
215
- }
216
- else {
217
- const wrapped = splitByVisualWidth(line, availableWidth);
218
- wrapped.forEach((chunk, i) => {
219
- const marker = i > 0 ? '↳' : ' ';
220
- rows.push([{ text: ' '.repeat(indent) + marker + chunk, style }]);
221
- });
106
+ if (language) {
107
+ const labelStyle = mergeStyles(style, theme('muted'));
108
+ const labelPrefix = safeIndent > 0
109
+ ? [{ text: ' '.repeat(safeIndent), style: labelStyle }]
110
+ : [];
111
+ const label = truncateToWidth(language, available);
112
+ rows.push(clampRow([
113
+ ...labelPrefix,
114
+ ...padRow([{ text: label, style: labelStyle }], available, labelStyle),
115
+ ], width));
116
+ }
117
+ for (const line of lines.length > 0 ? lines : ['']) {
118
+ const wrapped = wrapStyledSpans([{ text: line, style }], available, false);
119
+ for (const row of wrapped) {
120
+ rows.push(clampRow([...prefix, ...padRow(row, available, style)], width));
222
121
  }
223
122
  }
224
123
  return rows;
225
124
  }
226
125
  function layoutDiff(lines, width, theme, indent) {
227
126
  const rows = [];
228
- const availableWidth = Math.max(1, width - indent - 1);
127
+ const safeIndent = normalizeIndent(indent, width);
229
128
  for (const line of lines) {
230
129
  const prefix = line.kind === 'add' ? '+' : line.kind === 'remove' ? '-' : ' ';
231
130
  const style = diffLineStyle(line.kind, theme);
232
- if ((0, string_width_1.default)(line.content) <= availableWidth) {
233
- rows.push([{ text: ' '.repeat(indent) + prefix + line.content, style }]);
234
- }
235
- else {
236
- const wrapped = splitByVisualWidth(line.content, availableWidth);
237
- wrapped.forEach((chunk, i) => {
238
- const marker = i > 0 ? '↳' : prefix;
239
- rows.push([{ text: ' '.repeat(indent) + marker + chunk, style }]);
240
- });
241
- }
131
+ const firstPrefix = [{ text: `${' '.repeat(safeIndent)}${prefix}`, style }];
132
+ const continuationPrefix = [{ text: `${' '.repeat(safeIndent)}↳`, style }];
133
+ rows.push(...layoutPrefixedSpans([{ text: line.content, style }], width, firstPrefix, continuationPrefix, false));
242
134
  }
243
135
  return rows;
244
136
  }
245
137
  function diffLineStyle(kind, theme) {
246
138
  switch (kind) {
247
- case 'add': return theme('diffAdded');
248
- case 'remove': return theme('diffRemoved');
139
+ case 'add':
140
+ return theme('diffAdded');
141
+ case 'remove':
142
+ return theme('diffRemoved');
249
143
  case 'hunk':
250
- case 'meta': return theme('diffHunk');
251
- case 'context': return theme('muted');
144
+ case 'meta':
145
+ return theme('diffHunk');
146
+ case 'context':
147
+ return theme('muted');
252
148
  }
253
149
  }
254
150
  function layoutTable(block, width, theme, indent) {
255
- const availableWidth = Math.max(1, width - indent);
256
- // Measure column widths.
257
- const colCount = Math.max(block.headers.length, ...block.rows.map(r => r.length));
258
- if (colCount === 0)
151
+ const safeIndent = normalizeIndent(indent, width);
152
+ const available = Math.max(1, width - safeIndent);
153
+ const columnCount = Math.max(block.headers.length, ...block.rows.map(row => row.length));
154
+ if (columnCount === 0)
259
155
  return [];
260
- const minColWidth = 3;
261
- const maxTotal = availableWidth;
262
- const idealColWidth = Math.floor(maxTotal / colCount);
263
- // If columns can't fit, fall back to key/value layout.
264
- if (idealColWidth < minColWidth) {
265
- return layoutTableAsKeyValue(block, width, theme, indent);
266
- }
267
- const colWidth = idealColWidth;
156
+ const columnWidth = Math.floor(available / columnCount);
157
+ if (columnWidth < 4)
158
+ return layoutTableAsKeyValue(block, width, theme, safeIndent);
268
159
  const rows = [];
269
- // Header row.
270
- const headerRow = [];
271
- for (let c = 0; c < colCount; c++) {
272
- const headerText = block.headers[c]?.map(s => s.text).join('') ?? '';
273
- const truncated = truncateToWidth(headerText, colWidth - 1);
274
- headerRow.push({ text: truncated.padEnd(colWidth), style: theme('heading') });
160
+ const indentSpan = safeIndent > 0
161
+ ? [{ text: ' '.repeat(safeIndent), style: theme('assistantText') }]
162
+ : [];
163
+ const header = [...indentSpan];
164
+ for (let column = 0; column < columnCount; column++) {
165
+ const text = block.headers[column]?.map(span => span.text).join('') ?? '';
166
+ header.push({
167
+ text: padText(truncateToWidth(text, columnWidth - 1), columnWidth),
168
+ style: theme('heading'),
169
+ });
275
170
  }
276
- rows.push(headerRow);
277
- // Separator.
278
- rows.push([{ text: '-'.repeat(Math.min(colWidth * colCount, maxTotal)), style: theme('muted') }]);
279
- // Data rows.
280
- for (const row of block.rows) {
281
- const dataRow = [];
282
- for (let c = 0; c < colCount; c++) {
283
- const cellText = row[c]?.map(s => s.text).join('') ?? '';
284
- const truncated = truncateToWidth(cellText, colWidth - 1);
285
- dataRow.push({ text: truncated.padEnd(colWidth), style: theme('assistantText') });
171
+ rows.push(clampRow(header, width));
172
+ rows.push([
173
+ ...indentSpan,
174
+ { text: '─'.repeat(columnWidth * columnCount), style: theme('muted') },
175
+ ]);
176
+ for (const sourceRow of block.rows) {
177
+ const row = [...indentSpan];
178
+ for (let column = 0; column < columnCount; column++) {
179
+ const text = sourceRow[column]?.map(span => span.text).join('') ?? '';
180
+ row.push({
181
+ text: padText(truncateToWidth(text, columnWidth - 1), columnWidth),
182
+ style: theme('assistantText'),
183
+ });
286
184
  }
287
- rows.push(dataRow);
185
+ rows.push(clampRow(row, width));
288
186
  }
289
187
  return rows;
290
188
  }
291
189
  function layoutTableAsKeyValue(block, width, theme, indent) {
292
190
  const rows = [];
293
- const keyWidth = Math.max(1, Math.floor(width / 3) - indent);
294
- for (let c = 0; c < block.headers.length; c++) {
295
- const key = block.headers[c]?.map(s => s.text).join('') ?? `col${c}`;
296
- rows.push([{ text: ' '.repeat(indent) + truncateToWidth(key, keyWidth).padEnd(keyWidth) + ': ', style: theme('heading') }]);
297
- for (const row of block.rows) {
298
- const value = row[c]?.map(s => s.text).join('') ?? '';
299
- rows.push([{ text: ' '.repeat(indent + keyWidth + 2) + value, style: theme('assistantText') }]);
191
+ const prefixIndent = ' '.repeat(normalizeIndent(indent, width));
192
+ for (const sourceRow of block.rows) {
193
+ for (let column = 0; column < block.headers.length; column++) {
194
+ const key = block.headers[column]?.map(span => span.text).join('') || `col${column + 1}`;
195
+ const value = sourceRow[column] ?? [];
196
+ const prefix = [{ text: `${prefixIndent}${key}: `, style: theme('heading') }];
197
+ const safePrefix = clampRow(prefix, Math.max(1, width - 1));
198
+ const continuation = [{ text: ' '.repeat(rowWidth(safePrefix)), style: theme('muted') }];
199
+ const styledValue = value.map(span => ({
200
+ text: span.text,
201
+ style: resolveSpanStyle(span, theme('assistantText'), theme),
202
+ }));
203
+ rows.push(...layoutPrefixedSpans(styledValue, width, safePrefix, continuation, true));
300
204
  }
301
205
  }
302
206
  return rows;
303
207
  }
304
- function resolveSpanStyle(span, baseStyle) {
305
- if (!span.bold && !span.italic && !span.code)
306
- return baseStyle;
307
- return {
308
- ...baseStyle,
309
- bold: span.bold || baseStyle.bold,
310
- italic: span.italic || baseStyle.italic,
311
- dim: span.code || baseStyle.dim,
312
- };
208
+ function layoutPrefixedSpans(spans, width, firstPrefix, continuationPrefix, softWrap) {
209
+ const prefixWidth = Math.max(rowWidth(firstPrefix), rowWidth(continuationPrefix));
210
+ if (prefixWidth >= width) {
211
+ const prefixRow = clampRow(firstPrefix, width);
212
+ const body = wrapStyledSpans(spans, width, softWrap);
213
+ const hasContent = spans.some(span => span.text.length > 0);
214
+ return hasContent ? [prefixRow, ...body.map(row => clampRow(row, width))] : [prefixRow];
215
+ }
216
+ const bodyRows = wrapStyledSpans(spans, width - prefixWidth, softWrap);
217
+ return bodyRows.map((row, index) => clampRow([
218
+ ...(index === 0 ? firstPrefix : continuationPrefix),
219
+ ...row,
220
+ ], width));
221
+ }
222
+ function wrapStyledSpans(spans, width, softWrap) {
223
+ const safeWidth = normalizeWidth(width);
224
+ const lines = splitStyledUnitsIntoLines(spans);
225
+ const rows = [];
226
+ for (const units of lines) {
227
+ if (units.length === 0) {
228
+ rows.push([]);
229
+ continue;
230
+ }
231
+ let remaining = units;
232
+ while (remaining.length > 0) {
233
+ let used = 0;
234
+ let take = 0;
235
+ let lastWhitespace = -1;
236
+ while (take < remaining.length && used + remaining[take].width <= safeWidth) {
237
+ used += remaining[take].width;
238
+ if (/^\s+$/u.test(remaining[take].text))
239
+ lastWhitespace = take;
240
+ take += 1;
241
+ }
242
+ if (take === remaining.length) {
243
+ rows.push(unitsToRow(remaining));
244
+ break;
245
+ }
246
+ if (take === 0) {
247
+ rows.push([{ text: '…', style: remaining[0].style }]);
248
+ remaining = remaining.slice(1);
249
+ continue;
250
+ }
251
+ const breakAt = softWrap && lastWhitespace > 0 ? lastWhitespace : take;
252
+ rows.push(unitsToRow(remaining.slice(0, breakAt)));
253
+ let consumed = softWrap && lastWhitespace > 0 ? lastWhitespace + 1 : take;
254
+ if (softWrap) {
255
+ while (consumed < remaining.length && /^\s+$/u.test(remaining[consumed].text)) {
256
+ consumed += 1;
257
+ }
258
+ }
259
+ remaining = remaining.slice(consumed);
260
+ }
261
+ }
262
+ return rows.length > 0 ? rows : [[]];
263
+ }
264
+ function splitStyledUnitsIntoLines(spans) {
265
+ const lines = [[]];
266
+ for (const span of spans) {
267
+ for (const part of span.text.split(/(\n)/u)) {
268
+ if (part === '\n') {
269
+ lines.push([]);
270
+ continue;
271
+ }
272
+ for (const grapheme of (0, grapheme_1.segmentGraphemes)(part)) {
273
+ lines[lines.length - 1].push({
274
+ text: grapheme.segment,
275
+ style: span.style,
276
+ width: (0, string_width_1.default)(grapheme.segment),
277
+ });
278
+ }
279
+ }
280
+ }
281
+ return lines;
282
+ }
283
+ function unitsToRow(units) {
284
+ const row = [];
285
+ for (const unit of units) {
286
+ const previous = row[row.length - 1];
287
+ if (previous && (0, style_1.styleKey)(previous.style) === (0, style_1.styleKey)(unit.style)) {
288
+ previous.text += unit.text;
289
+ }
290
+ else {
291
+ row.push({ text: unit.text, style: unit.style });
292
+ }
293
+ }
294
+ return row;
295
+ }
296
+ function resolveSpanStyle(span, baseStyle, theme) {
297
+ let style = baseStyle;
298
+ if (span.linkUrl)
299
+ style = mergeStyles(style, theme('link'));
300
+ if (span.code)
301
+ style = mergeStyles(style, theme('inlineCode'));
302
+ if (span.bold)
303
+ style = { ...style, bold: true };
304
+ if (span.italic)
305
+ style = { ...style, italic: true };
306
+ return style;
307
+ }
308
+ function mergeStyles(base, overlay) {
309
+ return { ...base, ...overlay };
310
+ }
311
+ function padRow(row, targetWidth, style) {
312
+ const padding = Math.max(0, targetWidth - rowWidth(row));
313
+ return padding > 0 ? [...row, { text: ' '.repeat(padding), style }] : row;
314
+ }
315
+ function clampRow(row, width) {
316
+ const result = [];
317
+ let used = 0;
318
+ for (const span of row) {
319
+ let text = '';
320
+ for (const grapheme of (0, grapheme_1.segmentGraphemes)(span.text.replace(/\n/gu, ''))) {
321
+ const graphemeWidth = (0, string_width_1.default)(grapheme.segment);
322
+ if (used + graphemeWidth > width)
323
+ break;
324
+ text += grapheme.segment;
325
+ used += graphemeWidth;
326
+ }
327
+ if (text)
328
+ result.push({ text, style: span.style });
329
+ if (used >= width)
330
+ break;
331
+ }
332
+ return result;
313
333
  }
314
334
  function truncateToWidth(text, maxWidth) {
335
+ if (maxWidth <= 0)
336
+ return '';
315
337
  if ((0, string_width_1.default)(text) <= maxWidth)
316
338
  return text;
317
- // Truncate by graphemes.
339
+ if (maxWidth === 1)
340
+ return '…';
318
341
  let result = '';
319
- for (const part of (0, grapheme_1.segmentGraphemes)(text)) {
320
- if ((0, string_width_1.default)(result + part.segment) > maxWidth - 1) {
321
- return result + '…';
322
- }
323
- result += part.segment;
342
+ for (const grapheme of (0, grapheme_1.segmentGraphemes)(text)) {
343
+ if ((0, string_width_1.default)(result + grapheme.segment) > maxWidth - 1)
344
+ break;
345
+ result += grapheme.segment;
324
346
  }
325
- return result;
347
+ return `${result}…`;
348
+ }
349
+ function padText(text, width) {
350
+ return text + ' '.repeat(Math.max(0, width - (0, string_width_1.default)(text)));
351
+ }
352
+ function rowWidth(row) {
353
+ return (0, string_width_1.default)(row.map(span => span.text).join(''));
354
+ }
355
+ function normalizeWidth(width) {
356
+ return Math.max(1, Number.isFinite(width) ? Math.floor(width) : 1);
357
+ }
358
+ function normalizeIndent(indent, width) {
359
+ const normalized = Number.isFinite(indent) ? Math.floor(indent) : 0;
360
+ return Math.max(0, Math.min(width - 1, normalized));
326
361
  }
327
362
  //# sourceMappingURL=layout.js.map