@oh-my-pi/pi-tui 3.8.1337 → 3.13.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 +69 -24
- 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,20 +180,45 @@ 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 6 for borders (3 left + 3 right)
|
|
191
|
+
const contentAreaWidth = width - 6;
|
|
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 - 6 (for "│ " prefix and " │" suffix borders)
|
|
218
|
+
const lineContentWidth = width - 6;
|
|
176
219
|
for (const layoutLine of layoutLines) {
|
|
177
220
|
let displayText = layoutLine.text;
|
|
178
|
-
let
|
|
221
|
+
let displayWidth = visibleWidth(layoutLine.text);
|
|
179
222
|
|
|
180
223
|
// Add cursor if this line has it
|
|
181
224
|
if (layoutLine.hasCursor && layoutLine.cursorPos !== undefined) {
|
|
@@ -190,16 +233,14 @@ export class Editor implements Component {
|
|
|
190
233
|
const restAfter = after.slice(firstGrapheme.length);
|
|
191
234
|
const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
|
|
192
235
|
displayText = before + cursor + restAfter;
|
|
193
|
-
//
|
|
236
|
+
// displayWidth stays the same - we're replacing, not adding
|
|
194
237
|
} else {
|
|
195
|
-
// Cursor is at the end -
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
lineVisibleWidth = lineVisibleWidth + 1;
|
|
202
|
-
} else {
|
|
238
|
+
// Cursor is at the end - add thin blinking bar cursor
|
|
239
|
+
// The ▏ character has width 1
|
|
240
|
+
const cursor = "\x1b[5m▏\x1b[0m";
|
|
241
|
+
displayText = before + cursor;
|
|
242
|
+
displayWidth += 1; // Account for cursor width
|
|
243
|
+
if (displayWidth > lineContentWidth) {
|
|
203
244
|
// Line is at full width - use reverse video on last grapheme if possible
|
|
204
245
|
// or just show cursor at the end without adding space
|
|
205
246
|
const beforeGraphemes = [...segmenter.segment(before)];
|
|
@@ -212,22 +253,26 @@ export class Editor implements Component {
|
|
|
212
253
|
.map((g) => g.segment)
|
|
213
254
|
.join("");
|
|
214
255
|
displayText = beforeWithoutLast + cursor;
|
|
256
|
+
displayWidth -= 1; // Back to original width (reverse video replaces, doesn't add)
|
|
215
257
|
}
|
|
216
|
-
// lineVisibleWidth stays the same
|
|
217
258
|
}
|
|
218
259
|
}
|
|
219
260
|
}
|
|
220
261
|
|
|
221
|
-
//
|
|
222
|
-
const
|
|
262
|
+
// All lines have consistent 6-char borders (3 left + 3 right)
|
|
263
|
+
const isLastLine = layoutLine === layoutLines[layoutLines.length - 1];
|
|
264
|
+
const padding = " ".repeat(Math.max(0, lineContentWidth - displayWidth));
|
|
223
265
|
|
|
224
|
-
|
|
225
|
-
|
|
266
|
+
if (isLastLine) {
|
|
267
|
+
// Last line: "╰─ " (3) + content + padding + " ─╯" (3) = 6 chars border
|
|
268
|
+
result.push(`${bottomLeft} ${displayText}${padding} ${bottomRight}`);
|
|
269
|
+
} else {
|
|
270
|
+
const leftBorder = this.borderColor("│ ");
|
|
271
|
+
const rightBorder = this.borderColor(" │");
|
|
272
|
+
result.push(leftBorder + displayText + padding + rightBorder);
|
|
273
|
+
}
|
|
226
274
|
}
|
|
227
275
|
|
|
228
|
-
// Render bottom border
|
|
229
|
-
result.push(horizontal.repeat(width));
|
|
230
|
-
|
|
231
276
|
// Add autocomplete list if active
|
|
232
277
|
if (this.isAutocompleting && this.autocompleteList) {
|
|
233
278
|
const autocompleteResult = this.autocompleteList.render(width);
|
|
@@ -1284,7 +1329,7 @@ export class Editor implements Component {
|
|
|
1284
1329
|
https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19536643416/job/559322883
|
|
1285
1330
|
17 this job fails with https://github.com/EsotericSoftware/spine-runtimes/actions/runs/19
|
|
1286
1331
|
536643416/job/55932288317 havea look at .gi
|
|
1287
|
-
|
|
1332
|
+
*/
|
|
1288
1333
|
private forceFileAutocomplete(): void {
|
|
1289
1334
|
if (!this.autocompleteProvider) return;
|
|
1290
1335
|
|
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