@kolisachint/hoocode-tui 0.5.66 → 0.5.68
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/dist/components/editor.d.ts +20 -18
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +54 -58
- package/dist/components/editor.js.map +1 -1
- package/dist/components/frame.d.ts +117 -0
- package/dist/components/frame.d.ts.map +1 -0
- package/dist/components/frame.js +203 -0
- package/dist/components/frame.js.map +1 -0
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +8 -2
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +7 -2
- package/dist/components/settings-list.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +9 -4
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +14 -2
- package/dist/keybindings.js.map +1 -1
- package/dist/mouse.d.ts +75 -0
- package/dist/mouse.d.ts.map +1 -0
- package/dist/mouse.js +115 -0
- package/dist/mouse.js.map +1 -0
- package/dist/terminal.d.ts +30 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +49 -0
- package/dist/terminal.js.map +1 -1
- package/dist/tui.d.ts +286 -0
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +642 -1
- package/dist/tui.js.map +1 -1
- package/dist/undo-stack.d.ts +39 -6
- package/dist/undo-stack.d.ts.map +1 -1
- package/dist/undo-stack.js +59 -6
- package/dist/undo-stack.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,8 @@ import { decodePrintableKey, matchesKey } from "../keys.js";
|
|
|
3
3
|
import { KillRing } from "../kill-ring.js";
|
|
4
4
|
import { CURSOR_MARKER } from "../tui.js";
|
|
5
5
|
import { UndoStack } from "../undo-stack.js";
|
|
6
|
-
import { getSegmenter, isPunctuationChar, isWhitespaceChar,
|
|
6
|
+
import { getSegmenter, isPunctuationChar, isWhitespaceChar, visibleWidth } from "../utils.js";
|
|
7
|
+
import { DEFAULT_FRAME_BORDER_CHARS, renderFrameEdge, } from "./frame.js";
|
|
7
8
|
import { SelectList } from "./select-list.js";
|
|
8
9
|
const baseSegmenter = getSegmenter();
|
|
9
10
|
/** Regex matching paste markers like `[paste #1 +123 lines]` or `[paste #2 1234 chars]`. */
|
|
@@ -170,23 +171,7 @@ export function wordWrapLine(line, maxWidth, preSegmented) {
|
|
|
170
171
|
}
|
|
171
172
|
return chunks;
|
|
172
173
|
}
|
|
173
|
-
export const DEFAULT_EDITOR_BORDER_CHARS =
|
|
174
|
-
horizontal: "─",
|
|
175
|
-
vertical: "│",
|
|
176
|
-
topLeft: "┌",
|
|
177
|
-
topRight: "┐",
|
|
178
|
-
bottomLeft: "└",
|
|
179
|
-
bottomRight: "┘",
|
|
180
|
-
};
|
|
181
|
-
/** Border cells kept between the label and the top-right corner, so it reads as inset rather than jammed. */
|
|
182
|
-
const LABEL_RIGHT_INSET = 2;
|
|
183
|
-
/**
|
|
184
|
-
* Border cells required to the *left* of the label. Below this the label is
|
|
185
|
-
* dropped rather than drawn: a label butting straight up against the scroll
|
|
186
|
-
* indicator reads as one run-on string, and a border with no run of border in it
|
|
187
|
-
* has stopped looking like a border.
|
|
188
|
-
*/
|
|
189
|
-
const MIN_LABEL_LEAD_IN = 4;
|
|
174
|
+
export const DEFAULT_EDITOR_BORDER_CHARS = DEFAULT_FRAME_BORDER_CHARS;
|
|
190
175
|
const SLASH_COMMAND_SELECT_LIST_LAYOUT = {
|
|
191
176
|
minPrimaryColumnWidth: 12,
|
|
192
177
|
maxPrimaryColumnWidth: 32,
|
|
@@ -385,45 +370,23 @@ export class Editor {
|
|
|
385
370
|
* @param box - Whether to draw corners
|
|
386
371
|
*/
|
|
387
372
|
renderBorder(edge, hidden, barWidth, box) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
// reader needs *now*, while the label says which session this is, which
|
|
398
|
-
// they can also read in the footer.
|
|
399
|
-
const label = edge === "top" ? this.topBorderLabel : undefined;
|
|
400
|
-
const leadIn = label ? barWidth - indicatorWidth - visibleWidth(label.plain) - LABEL_RIGHT_INSET : -1;
|
|
401
|
-
let bar;
|
|
402
|
-
if (label && leadIn >= MIN_LABEL_LEAD_IN) {
|
|
403
|
-
bar =
|
|
404
|
-
this.borderColor(indicator + chars.horizontal.repeat(leadIn)) +
|
|
405
|
-
label.styled +
|
|
406
|
-
this.borderColor(chars.horizontal.repeat(LABEL_RIGHT_INSET));
|
|
407
|
-
}
|
|
408
|
-
else if (indicatorWidth > 0) {
|
|
409
|
-
const remaining = barWidth - indicatorWidth;
|
|
410
|
-
bar = this.borderColor(remaining >= 0 ? indicator + chars.horizontal.repeat(remaining) : truncateToWidth(indicator, barWidth));
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
bar = this.borderColor(chars.horizontal.repeat(barWidth));
|
|
414
|
-
}
|
|
415
|
-
if (!box)
|
|
416
|
-
return bar;
|
|
417
|
-
// Corners must stay aligned, so pad back any width lost to truncation.
|
|
418
|
-
bar += this.borderColor(chars.horizontal.repeat(Math.max(0, barWidth - visibleWidth(bar))));
|
|
419
|
-
const left = edge === "top" ? chars.topLeft : chars.bottomLeft;
|
|
420
|
-
const right = edge === "top" ? chars.topRight : chars.bottomRight;
|
|
421
|
-
return this.borderColor(left) + bar + this.borderColor(right);
|
|
373
|
+
return renderFrameEdge({
|
|
374
|
+
edge,
|
|
375
|
+
barWidth,
|
|
376
|
+
box,
|
|
377
|
+
chars: this.borderChars,
|
|
378
|
+
color: this.borderColor,
|
|
379
|
+
label: this.topBorderLabel,
|
|
380
|
+
hidden,
|
|
381
|
+
});
|
|
422
382
|
}
|
|
423
383
|
render(width) {
|
|
424
384
|
// Box mode needs 2 columns for the side borders plus at least 1 content
|
|
425
385
|
// column; below that it degrades to rule mode rather than overflowing.
|
|
426
386
|
const box = this.border === "box" && width >= 4;
|
|
387
|
+
// `none` is for an editor nested in a frame that already rules it: the
|
|
388
|
+
// rows are the same, the two horizontals are simply not drawn.
|
|
389
|
+
const bare = this.border === "none";
|
|
427
390
|
const borderWidth = box ? 1 : 0;
|
|
428
391
|
const innerWidth = Math.max(1, width - borderWidth * 2);
|
|
429
392
|
const maxPadding = Math.max(0, Math.floor((innerWidth - 1) / 2));
|
|
@@ -466,7 +429,8 @@ export class Editor {
|
|
|
466
429
|
const leftPadding = " ".repeat(paddingX);
|
|
467
430
|
const rightPadding = leftPadding;
|
|
468
431
|
// Render top border (with scroll indicator if scrolled down)
|
|
469
|
-
|
|
432
|
+
if (!bare)
|
|
433
|
+
result.push(this.renderBorder("top", this.scrollOffset, barWidth, box));
|
|
470
434
|
// Render each visible layout line
|
|
471
435
|
// Emit hardware cursor marker only when focused and not showing autocomplete
|
|
472
436
|
const emitCursorMarker = this.focused && !this.autocompleteState;
|
|
@@ -516,7 +480,8 @@ export class Editor {
|
|
|
516
480
|
}
|
|
517
481
|
// Render bottom border (with scroll indicator if more content below)
|
|
518
482
|
const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
|
|
519
|
-
|
|
483
|
+
if (!bare)
|
|
484
|
+
result.push(this.renderBorder("bottom", linesBelow, barWidth, box));
|
|
520
485
|
// Add autocomplete list if active
|
|
521
486
|
if (this.autocompleteState && this.autocompleteList) {
|
|
522
487
|
const autocompleteResult = this.autocompleteList.render(contentWidth);
|
|
@@ -583,6 +548,10 @@ export class Editor {
|
|
|
583
548
|
this.undo();
|
|
584
549
|
return;
|
|
585
550
|
}
|
|
551
|
+
if (kb.matches(data, "tui.editor.redo")) {
|
|
552
|
+
this.redo();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
586
555
|
// Handle autocomplete mode
|
|
587
556
|
if (this.autocompleteState && this.autocompleteList) {
|
|
588
557
|
if (kb.matches(data, "tui.select.cancel")) {
|
|
@@ -1744,10 +1713,18 @@ export class Editor {
|
|
|
1744
1713
|
this.undoStack.push(this.state);
|
|
1745
1714
|
}
|
|
1746
1715
|
undo() {
|
|
1747
|
-
|
|
1748
|
-
|
|
1716
|
+
// `undo` rather than `pop`: the stack needs the state being left in order
|
|
1717
|
+
// to have somewhere to bring you back to.
|
|
1718
|
+
this.applySnapshot(this.undoStack.undo(this.state));
|
|
1719
|
+
}
|
|
1720
|
+
redo() {
|
|
1721
|
+
this.applySnapshot(this.undoStack.redo(this.state));
|
|
1722
|
+
}
|
|
1723
|
+
/** Shared by undo and redo: both replace the state and settle the editor. */
|
|
1724
|
+
applySnapshot(snapshot) {
|
|
1749
1725
|
if (!snapshot)
|
|
1750
1726
|
return;
|
|
1727
|
+
this.historyIndex = -1; // Exit history browsing mode
|
|
1751
1728
|
Object.assign(this.state, snapshot);
|
|
1752
1729
|
this.lastAction = null;
|
|
1753
1730
|
this.preferredVisualCol = null;
|
|
@@ -1988,7 +1965,7 @@ export class Editor {
|
|
|
1988
1965
|
if (bestMatchIndex >= 0) {
|
|
1989
1966
|
this.autocompleteList.setSelectedIndex(bestMatchIndex);
|
|
1990
1967
|
}
|
|
1991
|
-
this.
|
|
1968
|
+
this.setAutocompleteState(state);
|
|
1992
1969
|
}
|
|
1993
1970
|
cancelAutocompleteRequest() {
|
|
1994
1971
|
this.autocompleteStartToken += 1;
|
|
@@ -2000,14 +1977,33 @@ export class Editor {
|
|
|
2000
1977
|
this.autocompleteAbort = undefined;
|
|
2001
1978
|
}
|
|
2002
1979
|
clearAutocompleteUi() {
|
|
2003
|
-
this.
|
|
1980
|
+
this.setAutocompleteState(null);
|
|
2004
1981
|
this.autocompleteList = undefined;
|
|
2005
1982
|
this.autocompletePrefix = "";
|
|
2006
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* The one place the list's presence changes, so it is the one place that can
|
|
1986
|
+
* announce it.
|
|
1987
|
+
*
|
|
1988
|
+
* Suggestions arrive from an async provider, which is why a caller cannot
|
|
1989
|
+
* just look after handling a keystroke: on the keypress that opens the list
|
|
1990
|
+
* the state is still null, and if that keypress was the last one — `/mod`
|
|
1991
|
+
* then a pause — nothing ever looks again. Anyone lending the list screen
|
|
1992
|
+
* space has to hear about it when it actually happens.
|
|
1993
|
+
*/
|
|
1994
|
+
setAutocompleteState(state) {
|
|
1995
|
+
const was = this.autocompleteState !== null;
|
|
1996
|
+
this.autocompleteState = state;
|
|
1997
|
+
const now = state !== null;
|
|
1998
|
+
if (was !== now)
|
|
1999
|
+
this.onAutocompleteVisibilityChange?.(now);
|
|
2000
|
+
}
|
|
2007
2001
|
cancelAutocomplete() {
|
|
2008
2002
|
this.cancelAutocompleteRequest();
|
|
2009
2003
|
this.clearAutocompleteUi();
|
|
2010
2004
|
}
|
|
2005
|
+
/** Called when the completion list appears or disappears. */
|
|
2006
|
+
onAutocompleteVisibilityChange;
|
|
2011
2007
|
isShowingAutocomplete() {
|
|
2012
2008
|
return this.autocompleteState !== null;
|
|
2013
2009
|
}
|