@oh-my-pi/pi-tui 3.6.1337 → 3.9.1337
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/editor.ts +64 -17
- package/src/index.ts +1 -1
- package/src/tui.ts +4 -0
package/package.json
CHANGED
package/src/components/editor.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
isTab,
|
|
27
27
|
} from "../keys";
|
|
28
28
|
import type { Component } from "../tui";
|
|
29
|
-
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils";
|
|
29
|
+
import { getSegmenter, isPunctuationChar, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils";
|
|
30
30
|
import { SelectList, type SelectListTheme } from "./select-list";
|
|
31
31
|
|
|
32
32
|
const segmenter = getSegmenter();
|
|
@@ -48,6 +48,13 @@ export interface EditorTheme {
|
|
|
48
48
|
selectList: SelectListTheme;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface EditorTopBorder {
|
|
52
|
+
/** The status content (already styled) */
|
|
53
|
+
content: string;
|
|
54
|
+
/** Visible width of the content */
|
|
55
|
+
width: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
51
58
|
export class Editor implements Component {
|
|
52
59
|
private state: EditorState = {
|
|
53
60
|
lines: [""],
|
|
@@ -85,6 +92,9 @@ export class Editor implements Component {
|
|
|
85
92
|
public onChange?: (text: string) => void;
|
|
86
93
|
public disableSubmit: boolean = false;
|
|
87
94
|
|
|
95
|
+
// Custom top border (for status line integration)
|
|
96
|
+
private topBorderContent?: EditorTopBorder;
|
|
97
|
+
|
|
88
98
|
constructor(theme: EditorTheme) {
|
|
89
99
|
this.theme = theme;
|
|
90
100
|
this.borderColor = theme.borderColor;
|
|
@@ -94,6 +104,14 @@ export class Editor implements Component {
|
|
|
94
104
|
this.autocompleteProvider = provider;
|
|
95
105
|
}
|
|
96
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Set custom content for the top border (e.g., status line).
|
|
109
|
+
* Pass undefined to use the default plain border.
|
|
110
|
+
*/
|
|
111
|
+
setTopBorder(content: EditorTopBorder | undefined): void {
|
|
112
|
+
this.topBorderContent = content;
|
|
113
|
+
}
|
|
114
|
+
|
|
97
115
|
/**
|
|
98
116
|
* Add a prompt to history for up/down arrow navigation.
|
|
99
117
|
* Called after successful submission.
|
|
@@ -162,17 +180,42 @@ export class Editor implements Component {
|
|
|
162
180
|
// Store width for cursor navigation
|
|
163
181
|
this.lastWidth = width;
|
|
164
182
|
|
|
183
|
+
// Box-drawing characters for rounded corners
|
|
184
|
+
const topLeft = this.borderColor("╭─");
|
|
185
|
+
const topRight = this.borderColor("─╮");
|
|
186
|
+
const bottomLeft = this.borderColor("╰─");
|
|
187
|
+
const bottomRight = this.borderColor("─╯");
|
|
165
188
|
const horizontal = this.borderColor("─");
|
|
166
189
|
|
|
167
|
-
// Layout the text -
|
|
168
|
-
const
|
|
190
|
+
// Layout the text - content area is width minus 4 for borders (2 left + 2 right)
|
|
191
|
+
const contentAreaWidth = width - 4;
|
|
192
|
+
const layoutLines = this.layoutText(contentAreaWidth);
|
|
169
193
|
|
|
170
194
|
const result: string[] = [];
|
|
171
195
|
|
|
172
|
-
// Render top border
|
|
173
|
-
|
|
196
|
+
// Render top border: ╭─ [status content] ────────────────╮
|
|
197
|
+
// Reserve: 2 for "╭─", 2 for "─╮" = 4 total for corners
|
|
198
|
+
const topFillWidth = width - 4;
|
|
199
|
+
if (this.topBorderContent) {
|
|
200
|
+
const { content, width: statusWidth } = this.topBorderContent;
|
|
201
|
+
if (statusWidth <= topFillWidth) {
|
|
202
|
+
// Status fits - add fill after it
|
|
203
|
+
const fillWidth = topFillWidth - statusWidth;
|
|
204
|
+
result.push(topLeft + content + this.borderColor("─".repeat(fillWidth)) + topRight);
|
|
205
|
+
} else {
|
|
206
|
+
// Status too long - truncate it
|
|
207
|
+
const truncated = truncateToWidth(content, topFillWidth - 1, this.borderColor("…"));
|
|
208
|
+
const truncatedWidth = visibleWidth(truncated);
|
|
209
|
+
const fillWidth = Math.max(0, topFillWidth - truncatedWidth);
|
|
210
|
+
result.push(topLeft + truncated + this.borderColor("─".repeat(fillWidth)) + topRight);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
result.push(topLeft + horizontal.repeat(topFillWidth) + topRight);
|
|
214
|
+
}
|
|
174
215
|
|
|
175
216
|
// Render each layout line
|
|
217
|
+
// Content area is width - 4 (for prefix and suffix borders)
|
|
218
|
+
const lineContentWidth = width - 4;
|
|
176
219
|
for (const layoutLine of layoutLines) {
|
|
177
220
|
let displayText = layoutLine.text;
|
|
178
221
|
let lineVisibleWidth = visibleWidth(layoutLine.text);
|
|
@@ -192,10 +235,11 @@ export class Editor implements Component {
|
|
|
192
235
|
displayText = before + cursor + restAfter;
|
|
193
236
|
// lineVisibleWidth stays the same - we're replacing, not adding
|
|
194
237
|
} else {
|
|
195
|
-
// Cursor is at the end - check if we have room for the
|
|
196
|
-
if (lineVisibleWidth <
|
|
197
|
-
// We have room - add
|
|
198
|
-
|
|
238
|
+
// Cursor is at the end - check if we have room for the cursor
|
|
239
|
+
if (lineVisibleWidth < lineContentWidth) {
|
|
240
|
+
// We have room - add thin bar cursor (▏) with blink
|
|
241
|
+
// \x1b[5m = slow blink, no reverse video so it's a thin line
|
|
242
|
+
const cursor = "\x1b[5m▏\x1b[0m";
|
|
199
243
|
displayText = before + cursor;
|
|
200
244
|
// lineVisibleWidth increases by 1 - we're adding a space
|
|
201
245
|
lineVisibleWidth = lineVisibleWidth + 1;
|
|
@@ -218,16 +262,19 @@ export class Editor implements Component {
|
|
|
218
262
|
}
|
|
219
263
|
}
|
|
220
264
|
|
|
221
|
-
//
|
|
222
|
-
const
|
|
265
|
+
// All lines get 1 char padding on each side for consistent alignment
|
|
266
|
+
const isLastLine = layoutLine === layoutLines[layoutLines.length - 1];
|
|
267
|
+
const padding = " ".repeat(Math.max(0, lineContentWidth - lineVisibleWidth - 2));
|
|
223
268
|
|
|
224
|
-
|
|
225
|
-
|
|
269
|
+
if (isLastLine) {
|
|
270
|
+
result.push(`${bottomLeft} ${displayText}${padding} ${bottomRight}`);
|
|
271
|
+
} else {
|
|
272
|
+
const leftBorder = this.borderColor("│ ");
|
|
273
|
+
const rightBorder = this.borderColor(" │");
|
|
274
|
+
result.push(leftBorder + displayText + padding + rightBorder);
|
|
275
|
+
}
|
|
226
276
|
}
|
|
227
277
|
|
|
228
|
-
// Render bottom border
|
|
229
|
-
result.push(horizontal.repeat(width));
|
|
230
|
-
|
|
231
278
|
// Add autocomplete list if active
|
|
232
279
|
if (this.isAutocompleting && this.autocompleteList) {
|
|
233
280
|
const autocompleteResult = this.autocompleteList.render(width);
|
|
@@ -1284,7 +1331,7 @@ export class Editor implements Component {
|
|
|
1284
1331
|
https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/559322883
|
|
1285
1332
|
17 this job fails with https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19
|
|
1286
1333
|
536643416/job/55932288317 havea look at .gi
|
|
1287
|
-
|
|
1334
|
+
*/
|
|
1288
1335
|
private forceFileAutocomplete(): void {
|
|
1289
1336
|
if (!this.autocompleteProvider) return;
|
|
1290
1337
|
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ export {
|
|
|
10
10
|
// Components
|
|
11
11
|
export { Box } from "./components/box";
|
|
12
12
|
export { CancellableLoader } from "./components/cancellable-loader";
|
|
13
|
-
export { Editor, type EditorTheme } from "./components/editor";
|
|
13
|
+
export { Editor, type EditorTheme, type EditorTopBorder } from "./components/editor";
|
|
14
14
|
export { Image, type ImageOptions, type ImageTheme } from "./components/image";
|
|
15
15
|
export { Input } from "./components/input";
|
|
16
16
|
export { Loader } from "./components/loader";
|
package/src/tui.ts
CHANGED