@dungle-scrubs/tallow 0.9.4 → 0.9.6

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 (195) hide show
  1. package/dist/cli.js +7 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +24 -12
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +229 -146
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/interactive-reset.d.ts +49 -0
  10. package/dist/interactive-reset.d.ts.map +1 -0
  11. package/dist/interactive-reset.js +40 -0
  12. package/dist/interactive-reset.js.map +1 -0
  13. package/dist/pi-tui-editor-patch.d.ts +10 -0
  14. package/dist/pi-tui-editor-patch.d.ts.map +1 -0
  15. package/dist/pi-tui-editor-patch.js +159 -0
  16. package/dist/pi-tui-editor-patch.js.map +1 -0
  17. package/dist/pi-tui-patch.d.ts +2 -0
  18. package/dist/pi-tui-patch.d.ts.map +1 -0
  19. package/dist/pi-tui-patch.js +563 -0
  20. package/dist/pi-tui-patch.js.map +1 -0
  21. package/dist/pi-tui-settings-list-patch.d.ts +11 -0
  22. package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
  23. package/dist/pi-tui-settings-list-patch.js +38 -0
  24. package/dist/pi-tui-settings-list-patch.js.map +1 -0
  25. package/dist/reset-diagnostics.d.ts +69 -0
  26. package/dist/reset-diagnostics.d.ts.map +1 -0
  27. package/dist/reset-diagnostics.js +41 -0
  28. package/dist/reset-diagnostics.js.map +1 -0
  29. package/dist/sdk.d.ts +5 -21
  30. package/dist/sdk.d.ts.map +1 -1
  31. package/dist/sdk.js +180 -149
  32. package/dist/sdk.js.map +1 -1
  33. package/dist/workspace-transition-interactive.d.ts +1 -0
  34. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  35. package/dist/workspace-transition-interactive.js +7 -17
  36. package/dist/workspace-transition-interactive.js.map +1 -1
  37. package/extensions/__integration__/audit-findings.test.ts +4 -5
  38. package/extensions/_icons/index.ts +2 -4
  39. package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
  40. package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
  41. package/extensions/_shared/image-metadata.ts +99 -0
  42. package/extensions/_shared/inline-preview.ts +1 -1
  43. package/extensions/_shared/terminal-links.ts +22 -0
  44. package/extensions/ask-user-question-tool/index.ts +0 -3
  45. package/extensions/clear/__tests__/clear.test.ts +269 -2
  46. package/extensions/command-expansion/index.ts +1 -1
  47. package/extensions/context-files/index.ts +5 -1
  48. package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
  49. package/extensions/context-fork/extension.json +1 -1
  50. package/extensions/context-fork/index.ts +32 -0
  51. package/extensions/edit-tool-enhanced/index.ts +2 -1
  52. package/extensions/hooks/index.ts +33 -11
  53. package/extensions/loop/index.ts +14 -1
  54. package/extensions/lsp/index.ts +64 -13
  55. package/extensions/lsp/package.json +2 -2
  56. package/extensions/random-spinner/index.ts +7 -642
  57. package/extensions/read-tool-enhanced/index.ts +6 -8
  58. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
  59. package/extensions/render-stabilizer/index.ts +6 -6
  60. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
  61. package/extensions/slash-command-bridge/index.ts +14 -2
  62. package/extensions/subagent-tool/model-resolver.ts +274 -7
  63. package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
  64. package/extensions/teams-tool/tools/register-extension.ts +1 -3
  65. package/extensions/web-search-tool/index.ts +2 -1
  66. package/extensions/write-tool-enhanced/index.ts +2 -1
  67. package/node_modules/@mariozechner/pi-tui/README.md +56 -34
  68. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
  69. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
  70. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
  71. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
  72. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
  73. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
  74. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
  75. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
  76. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
  77. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
  78. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
  79. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
  80. package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
  81. package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
  82. package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
  83. package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
  84. package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
  85. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
  86. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
  87. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
  88. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
  89. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
  90. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
  91. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
  92. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
  93. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
  94. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
  95. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
  96. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
  97. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
  98. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
  99. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
  101. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  102. package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
  103. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  104. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
  105. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  106. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
  107. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  108. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
  109. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  110. package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
  111. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  112. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
  113. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
  114. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
  115. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
  116. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
  117. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  118. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
  119. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  120. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
  121. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  122. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
  123. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  124. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
  125. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
  126. package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
  127. package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
  128. package/node_modules/@mariozechner/pi-tui/package.json +6 -6
  129. package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
  130. package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
  131. package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
  132. package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
  133. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
  134. package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
  135. package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
  136. package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
  137. package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
  138. package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
  139. package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
  140. package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
  141. package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
  142. package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
  143. package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
  144. package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
  145. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
  146. package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
  147. package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
  148. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
  149. package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
  150. package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
  151. package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
  152. package/package.json +13 -13
  153. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
  154. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
  155. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
  156. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
  157. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
  158. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
  159. package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
  160. package/packages/tallow-tui/node_modules/marked/README.md +5 -4
  161. package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
  162. package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
  163. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
  164. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
  165. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
  166. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
  167. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
  168. package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
  169. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
  170. package/packages/tallow-tui/node_modules/marked/package.json +26 -34
  171. package/skills/tallow-expert/SKILL.md +1 -3
  172. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
  173. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
  174. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
  175. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
  176. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
  177. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
  178. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
  179. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
  180. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
  181. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
  182. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
  183. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
  184. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
  185. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
  186. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
  187. package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
  188. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
  189. package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
  190. package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
  191. package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
  192. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
  193. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
  194. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
  195. package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
@@ -1,8 +1,14 @@
1
- import { getEditorKeybindings } from "../keybindings.js";
1
+ import { getKeybindings } from "../keybindings.js";
2
2
  import type { Component } from "../tui.js";
3
- import { truncateToWidth } from "../utils.js";
3
+ import { truncateToWidth, visibleWidth } from "../utils.js";
4
+
5
+ const DEFAULT_PRIMARY_COLUMN_WIDTH = 32;
6
+ const PRIMARY_COLUMN_GAP = 2;
7
+ const MIN_DESCRIPTION_WIDTH = 10;
4
8
 
5
9
  const normalizeToSingleLine = (text: string): string => text.replace(/[\r\n]+/g, " ").trim();
10
+ const clamp = (value: number, min: number, max: number): number =>
11
+ Math.max(min, Math.min(value, max));
6
12
 
7
13
  export interface SelectItem {
8
14
  value: string;
@@ -18,22 +24,43 @@ export interface SelectListTheme {
18
24
  noMatch: (text: string) => string;
19
25
  }
20
26
 
27
+ export interface SelectListTruncatePrimaryContext {
28
+ text: string;
29
+ maxWidth: number;
30
+ columnWidth: number;
31
+ item: SelectItem;
32
+ isSelected: boolean;
33
+ }
34
+
35
+ export interface SelectListLayoutOptions {
36
+ minPrimaryColumnWidth?: number;
37
+ maxPrimaryColumnWidth?: number;
38
+ truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
39
+ }
40
+
21
41
  export class SelectList implements Component {
22
42
  private items: SelectItem[] = [];
23
43
  private filteredItems: SelectItem[] = [];
24
44
  private selectedIndex: number = 0;
25
45
  private maxVisible: number = 5;
26
46
  private theme: SelectListTheme;
47
+ private layout: SelectListLayoutOptions;
27
48
 
28
49
  public onSelect?: (item: SelectItem) => void;
29
50
  public onCancel?: () => void;
30
51
  public onSelectionChange?: (item: SelectItem) => void;
31
52
 
32
- constructor(items: SelectItem[], maxVisible: number, theme: SelectListTheme) {
53
+ constructor(
54
+ items: SelectItem[],
55
+ maxVisible: number,
56
+ theme: SelectListTheme,
57
+ layout: SelectListLayoutOptions = {}
58
+ ) {
33
59
  this.items = items;
34
60
  this.filteredItems = items;
35
61
  this.maxVisible = maxVisible;
36
62
  this.theme = theme;
63
+ this.layout = layout;
37
64
  }
38
65
 
39
66
  setFilter(filter: string): void {
@@ -61,6 +88,8 @@ export class SelectList implements Component {
61
88
  return lines;
62
89
  }
63
90
 
91
+ const primaryColumnWidth = this.getPrimaryColumnWidth();
92
+
64
93
  // Calculate visible range with scrolling
65
94
  const startIndex = Math.max(
66
95
  0,
@@ -80,68 +109,9 @@ export class SelectList implements Component {
80
109
  const descriptionSingleLine = item.description
81
110
  ? normalizeToSingleLine(item.description)
82
111
  : undefined;
83
-
84
- let line = "";
85
- if (isSelected) {
86
- // Use arrow indicator for selection - entire line uses selectedText color
87
- const prefixWidth = 2; // "↗ " is 2 characters visually
88
- const displayValue = item.label || item.value;
89
-
90
- if (descriptionSingleLine && width > 40) {
91
- // Calculate how much space we have for value + description
92
- const maxValueWidth = Math.min(30, width - prefixWidth - 4);
93
- const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
94
- const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
95
-
96
- // Calculate remaining space for description using visible widths
97
- const descriptionStart = prefixWidth + truncatedValue.length + spacing.length;
98
- const remainingWidth = width - descriptionStart - 2; // -2 for safety
99
-
100
- if (remainingWidth > 10) {
101
- const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
102
- // Apply selectedText to entire line content
103
- line = this.theme.selectedText(`↗ ${truncatedValue}${spacing}${truncatedDesc}`);
104
- } else {
105
- // Not enough space for description
106
- const maxWidth = width - prefixWidth - 2;
107
- line = this.theme.selectedText(`↗ ${truncateToWidth(displayValue, maxWidth, "")}`);
108
- }
109
- } else {
110
- // No description or not enough width
111
- const maxWidth = width - prefixWidth - 2;
112
- line = this.theme.selectedText(`↗ ${truncateToWidth(displayValue, maxWidth, "")}`);
113
- }
114
- } else {
115
- const displayValue = item.label || item.value;
116
- const prefix = " ";
117
-
118
- if (descriptionSingleLine && width > 40) {
119
- // Calculate how much space we have for value + description
120
- const maxValueWidth = Math.min(30, width - prefix.length - 4);
121
- const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
122
- const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
123
-
124
- // Calculate remaining space for description
125
- const descriptionStart = prefix.length + truncatedValue.length + spacing.length;
126
- const remainingWidth = width - descriptionStart - 2; // -2 for safety
127
-
128
- if (remainingWidth > 10) {
129
- const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
130
- const descText = this.theme.description(spacing + truncatedDesc);
131
- line = prefix + truncatedValue + descText;
132
- } else {
133
- // Not enough space for description
134
- const maxWidth = width - prefix.length - 2;
135
- line = prefix + truncateToWidth(displayValue, maxWidth, "");
136
- }
137
- } else {
138
- // No description or not enough width
139
- const maxWidth = width - prefix.length - 2;
140
- line = prefix + truncateToWidth(displayValue, maxWidth, "");
141
- }
142
- }
143
-
144
- lines.push(line);
112
+ lines.push(
113
+ this.renderItem(item, isSelected, width, descriptionSingleLine, primaryColumnWidth)
114
+ );
145
115
  }
146
116
 
147
117
  // Add scroll indicators if needed
@@ -155,34 +125,130 @@ export class SelectList implements Component {
155
125
  }
156
126
 
157
127
  handleInput(keyData: string): void {
158
- const kb = getEditorKeybindings();
128
+ const kb = getKeybindings();
159
129
  // Up arrow - wrap to bottom when at top
160
- if (kb.matches(keyData, "selectUp")) {
130
+ if (kb.matches(keyData, "tui.select.up")) {
161
131
  this.selectedIndex =
162
132
  this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;
163
133
  this.notifySelectionChange();
164
134
  }
165
135
  // Down arrow - wrap to top when at bottom
166
- else if (kb.matches(keyData, "selectDown")) {
136
+ else if (kb.matches(keyData, "tui.select.down")) {
167
137
  this.selectedIndex =
168
138
  this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;
169
139
  this.notifySelectionChange();
170
140
  }
171
141
  // Enter
172
- else if (kb.matches(keyData, "selectConfirm")) {
142
+ else if (kb.matches(keyData, "tui.select.confirm")) {
173
143
  const selectedItem = this.filteredItems[this.selectedIndex];
174
144
  if (selectedItem && this.onSelect) {
175
145
  this.onSelect(selectedItem);
176
146
  }
177
147
  }
178
148
  // Escape or Ctrl+C
179
- else if (kb.matches(keyData, "selectCancel")) {
149
+ else if (kb.matches(keyData, "tui.select.cancel")) {
180
150
  if (this.onCancel) {
181
151
  this.onCancel();
182
152
  }
183
153
  }
184
154
  }
185
155
 
156
+ private renderItem(
157
+ item: SelectItem,
158
+ isSelected: boolean,
159
+ width: number,
160
+ descriptionSingleLine: string | undefined,
161
+ primaryColumnWidth: number
162
+ ): string {
163
+ const prefix = isSelected ? "→ " : " ";
164
+ const prefixWidth = visibleWidth(prefix);
165
+
166
+ if (descriptionSingleLine && width > 40) {
167
+ const effectivePrimaryColumnWidth = Math.max(
168
+ 1,
169
+ Math.min(primaryColumnWidth, width - prefixWidth - 4)
170
+ );
171
+ const maxPrimaryWidth = Math.max(1, effectivePrimaryColumnWidth - PRIMARY_COLUMN_GAP);
172
+ const truncatedValue = this.truncatePrimary(
173
+ item,
174
+ isSelected,
175
+ maxPrimaryWidth,
176
+ effectivePrimaryColumnWidth
177
+ );
178
+ const truncatedValueWidth = visibleWidth(truncatedValue);
179
+ const spacing = " ".repeat(Math.max(1, effectivePrimaryColumnWidth - truncatedValueWidth));
180
+ const descriptionStart = prefixWidth + truncatedValueWidth + spacing.length;
181
+ const remainingWidth = width - descriptionStart - 2; // -2 for safety
182
+
183
+ if (remainingWidth > MIN_DESCRIPTION_WIDTH) {
184
+ const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
185
+ if (isSelected) {
186
+ return this.theme.selectedText(`${prefix}${truncatedValue}${spacing}${truncatedDesc}`);
187
+ }
188
+
189
+ const descText = this.theme.description(spacing + truncatedDesc);
190
+ return prefix + truncatedValue + descText;
191
+ }
192
+ }
193
+
194
+ const maxWidth = width - prefixWidth - 2;
195
+ const truncatedValue = this.truncatePrimary(item, isSelected, maxWidth, maxWidth);
196
+ if (isSelected) {
197
+ return this.theme.selectedText(`${prefix}${truncatedValue}`);
198
+ }
199
+
200
+ return prefix + truncatedValue;
201
+ }
202
+
203
+ private getPrimaryColumnWidth(): number {
204
+ const { min, max } = this.getPrimaryColumnBounds();
205
+ const widestPrimary = this.filteredItems.reduce((widest, item) => {
206
+ return Math.max(widest, visibleWidth(this.getDisplayValue(item)) + PRIMARY_COLUMN_GAP);
207
+ }, 0);
208
+
209
+ return clamp(widestPrimary, min, max);
210
+ }
211
+
212
+ private getPrimaryColumnBounds(): { min: number; max: number } {
213
+ const rawMin =
214
+ this.layout.minPrimaryColumnWidth ??
215
+ this.layout.maxPrimaryColumnWidth ??
216
+ DEFAULT_PRIMARY_COLUMN_WIDTH;
217
+ const rawMax =
218
+ this.layout.maxPrimaryColumnWidth ??
219
+ this.layout.minPrimaryColumnWidth ??
220
+ DEFAULT_PRIMARY_COLUMN_WIDTH;
221
+
222
+ return {
223
+ min: Math.max(1, Math.min(rawMin, rawMax)),
224
+ max: Math.max(1, Math.max(rawMin, rawMax)),
225
+ };
226
+ }
227
+
228
+ private truncatePrimary(
229
+ item: SelectItem,
230
+ isSelected: boolean,
231
+ maxWidth: number,
232
+ columnWidth: number
233
+ ): string {
234
+ const displayValue = this.getDisplayValue(item);
235
+ const truncatedValue = this.layout.truncatePrimary
236
+ ? this.layout.truncatePrimary({
237
+ text: displayValue,
238
+ maxWidth,
239
+ columnWidth,
240
+ item,
241
+ isSelected,
242
+ })
243
+ : truncateToWidth(displayValue, maxWidth, "");
244
+
245
+ return truncateToWidth(truncatedValue, maxWidth, "");
246
+ }
247
+
248
+ private getDisplayValue(item: SelectItem): string {
249
+ return item.label || item.value;
250
+ }
251
+
186
252
  private notifySelectionChange(): void {
187
253
  const selectedItem = this.filteredItems[this.selectedIndex];
188
254
  if (selectedItem && this.onSelectionChange) {
@@ -1,5 +1,5 @@
1
1
  import { fuzzyFilter } from "../fuzzy.js";
2
- import { getEditorKeybindings } from "../keybindings.js";
2
+ import { getKeybindings } from "../keybindings.js";
3
3
  import type { Component } from "../tui.js";
4
4
  import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../utils.js";
5
5
  import { Input } from "./input.js";
@@ -45,9 +45,6 @@ export class SettingsList implements Component {
45
45
  // Submenu state
46
46
  private submenuComponent: Component | null = null;
47
47
  private submenuItemIndex: number | null = null;
48
- private lastRenderLineCount = 0;
49
- private nextMinLineCount = 0;
50
- private layoutTransitionCallback?: () => void;
51
48
 
52
49
  constructor(
53
50
  items: SettingItem[],
@@ -69,13 +66,7 @@ export class SettingsList implements Component {
69
66
  }
70
67
  }
71
68
 
72
- /**
73
- * Update an item's current value.
74
- *
75
- * @param id - Setting identifier
76
- * @param newValue - New value to display
77
- * @returns Nothing
78
- */
69
+ /** Update an item's currentValue */
79
70
  updateValue(id: string, newValue: string): void {
80
71
  const item = this.items.find((i) => i.id === id);
81
72
  if (item) {
@@ -83,35 +74,17 @@ export class SettingsList implements Component {
83
74
  }
84
75
  }
85
76
 
86
- /**
87
- * Set a callback fired before submenu-driven layout transitions.
88
- *
89
- * Callers can use this to reset TUI render-grace state before the settings
90
- * list expands into, or collapses out of, a submenu.
91
- *
92
- * @param callback - Transition callback, or undefined to clear it
93
- * @returns Nothing
94
- */
95
- setLayoutTransitionCallback(callback?: () => void): void {
96
- this.layoutTransitionCallback = callback;
97
- }
98
-
99
77
  invalidate(): void {
100
78
  this.submenuComponent?.invalidate?.();
101
79
  }
102
80
 
103
81
  render(width: number): string[] {
104
- const lines = this.submenuComponent
105
- ? this.submenuComponent.render(width)
106
- : this.renderMainList(width);
107
-
108
- if (this.nextMinLineCount > lines.length) {
109
- lines.push(...Array.from({ length: this.nextMinLineCount - lines.length }, () => ""));
82
+ // If submenu is active, render it instead
83
+ if (this.submenuComponent) {
84
+ return this.submenuComponent.render(width);
110
85
  }
111
86
 
112
- this.nextMinLineCount = 0;
113
- this.lastRenderLineCount = lines.length;
114
- return lines;
87
+ return this.renderMainList(width);
115
88
  }
116
89
 
117
90
  private renderMainList(width: number): string[] {
@@ -211,19 +184,19 @@ export class SettingsList implements Component {
211
184
  }
212
185
 
213
186
  // Main list input handling
214
- const kb = getEditorKeybindings();
187
+ const kb = getKeybindings();
215
188
  const displayItems = this.searchEnabled ? this.filteredItems : this.items;
216
- if (kb.matches(data, "selectUp")) {
189
+ if (kb.matches(data, "tui.select.up")) {
217
190
  if (displayItems.length === 0) return;
218
191
  this.selectedIndex =
219
192
  this.selectedIndex === 0 ? displayItems.length - 1 : this.selectedIndex - 1;
220
- } else if (kb.matches(data, "selectDown")) {
193
+ } else if (kb.matches(data, "tui.select.down")) {
221
194
  if (displayItems.length === 0) return;
222
195
  this.selectedIndex =
223
196
  this.selectedIndex === displayItems.length - 1 ? 0 : this.selectedIndex + 1;
224
- } else if (kb.matches(data, "selectConfirm") || data === " ") {
197
+ } else if (kb.matches(data, "tui.select.confirm") || data === " ") {
225
198
  this.activateItem();
226
- } else if (kb.matches(data, "selectCancel")) {
199
+ } else if (kb.matches(data, "tui.select.cancel")) {
227
200
  this.onCancel();
228
201
  } else if (this.searchEnabled && this.searchInput) {
229
202
  const sanitized = data.replace(/ /g, "");
@@ -242,10 +215,7 @@ export class SettingsList implements Component {
242
215
  if (!item) return;
243
216
 
244
217
  if (item.submenu) {
245
- // Preserve height for the first submenu frame so the TUI doesn't treat
246
- // the editor→submenu swap as a destructive shrink/expand transition.
247
- this.nextMinLineCount = this.lastRenderLineCount;
248
- this.layoutTransitionCallback?.();
218
+ // Open submenu, passing current value so it can pre-select correctly
249
219
  this.submenuItemIndex = this.selectedIndex;
250
220
  this.submenuComponent = item.submenu(item.currentValue, (selectedValue?: string) => {
251
221
  if (selectedValue !== undefined) {
@@ -264,16 +234,7 @@ export class SettingsList implements Component {
264
234
  }
265
235
  }
266
236
 
267
- /**
268
- * Close the active submenu and restore the main settings list.
269
- *
270
- * @returns Nothing
271
- */
272
237
  private closeSubmenu(): void {
273
- // Preserve one frame of the submenu height so the transition back to the
274
- // main settings list doesn't trigger a screen-clearing shrink redraw.
275
- this.nextMinLineCount = this.lastRenderLineCount;
276
- this.layoutTransitionCallback?.();
277
238
  this.submenuComponent = null;
278
239
  // Restore selection to the item that opened the submenu
279
240
  if (this.submenuItemIndex !== null) {
@@ -4,33 +4,25 @@
4
4
  export {
5
5
  type AutocompleteItem,
6
6
  type AutocompleteProvider,
7
+ type AutocompleteSuggestions,
7
8
  CombinedAutocompleteProvider,
8
9
  type SlashCommand,
9
10
  } from "./autocomplete.js";
10
- // Border styles
11
- export {
12
- type BorderStyle,
13
- defaultBorderStyle,
14
- FLAT,
15
- ROUNDED,
16
- SHARP,
17
- setDefaultBorderStyle,
18
- } from "./border-styles.js";
19
11
  // Components
20
- export { BorderedBox, type BorderedBoxOptions } from "./components/bordered-box.js";
21
12
  export { Box } from "./components/box.js";
22
13
  export { CancellableLoader } from "./components/cancellable-loader.js";
23
14
  export { Editor, type EditorOptions, type EditorTheme } from "./components/editor.js";
24
- export {
25
- Image,
26
- type ImageOptions,
27
- type ImageTheme,
28
- setNextImageFilePath,
29
- } from "./components/image.js";
15
+ export { Image, type ImageOptions, type ImageTheme } from "./components/image.js";
30
16
  export { Input } from "./components/input.js";
31
- export { Loader, type LoaderOptions, type MessageTransformContext } from "./components/loader.js";
17
+ export { Loader } from "./components/loader.js";
32
18
  export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown.js";
33
- export { type SelectItem, SelectList, type SelectListTheme } from "./components/select-list.js";
19
+ export {
20
+ type SelectItem,
21
+ SelectList,
22
+ type SelectListLayoutOptions,
23
+ type SelectListTheme,
24
+ type SelectListTruncatePrimaryContext,
25
+ } from "./components/select-list.js";
34
26
  export {
35
27
  type SettingItem,
36
28
  SettingsList,
@@ -45,34 +37,28 @@ export type { EditorComponent } from "./editor-component.js";
45
37
  export { type FuzzyMatch, fuzzyFilter, fuzzyMatch } from "./fuzzy.js";
46
38
  // Keybindings
47
39
  export {
48
- DEFAULT_EDITOR_KEYBINDINGS,
49
- type EditorAction,
50
- type EditorKeybindingsConfig,
51
- EditorKeybindingsManager,
52
- getEditorKeybindings,
53
40
  getKeybindings,
54
41
  type Keybinding,
42
+ type KeybindingConflict,
55
43
  type KeybindingDefinition,
44
+ type KeybindingDefinitions,
56
45
  type Keybindings,
57
46
  type KeybindingsConfig,
58
47
  KeybindingsManager,
59
- setEditorKeybindings,
60
48
  setKeybindings,
61
49
  TUI_KEYBINDINGS,
62
50
  } from "./keybindings.js";
63
51
  // Keyboard input handling
64
52
  export {
53
+ decodeKittyPrintable,
65
54
  isKeyRelease,
66
55
  isKeyRepeat,
67
56
  isKittyProtocolActive,
68
- isMouseEvent,
69
57
  Key,
70
58
  type KeyEventType,
71
59
  type KeyId,
72
- type MouseEvent,
73
60
  matchesKey,
74
61
  parseKey,
75
- parseMouseEvent,
76
62
  setKittyProtocolActive,
77
63
  } from "./keys.js";
78
64
  // Input buffering for batch splitting
@@ -83,15 +69,12 @@ export { ProcessTerminal, type Terminal } from "./terminal.js";
83
69
  export {
84
70
  allocateImageId,
85
71
  type CellDimensions,
86
- calculateImageLayout,
87
- createImageMetadata,
72
+ calculateImageRows,
88
73
  deleteAllKittyImages,
89
74
  deleteKittyImage,
90
75
  detectCapabilities,
91
- detectImageFormat,
92
76
  encodeITerm2,
93
77
  encodeKitty,
94
- formatImageDimensions,
95
78
  getCapabilities,
96
79
  getCellDimensions,
97
80
  getGifDimensions,
@@ -99,16 +82,14 @@ export {
99
82
  getJpegDimensions,
100
83
  getPngDimensions,
101
84
  getWebpDimensions,
85
+ hyperlink,
102
86
  type ImageDimensions,
103
- type ImageFormat,
104
- type ImageLayout,
105
- type ImageMetadata,
106
87
  type ImageProtocol,
107
88
  type ImageRenderOptions,
108
89
  imageFallback,
109
- imageFormatToMime,
110
90
  renderImage,
111
91
  resetCapabilitiesCache,
92
+ setCapabilities,
112
93
  setCellDimensions,
113
94
  type TerminalCapabilities,
114
95
  } from "./terminal-image.js";
@@ -126,4 +107,4 @@ export {
126
107
  TUI,
127
108
  } from "./tui.js";
128
109
  // Utilities
129
- export { fileLink, hyperlink, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils.js";
110
+ export { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "./utils.js";