aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-11 00:09:05 +0200
committeruser <user@clank>2026-08-12 19:34:35 +0200
commit456bc7e80ccee97e0435e380b7127972db65577a (patch)
treecc47e338e3f96859038149e7160595c43551250d
parentchrome: drop side rails across boxes (zellij draws the pane border) (diff)
downloadsl0ppi-456bc7e80ccee97e0435e380b7127972db65577a.tar.gz
sl0ppi-456bc7e80ccee97e0435e380b7127972db65577a.tar.xz
sl0ppi-456bc7e80ccee97e0435e380b7127972db65577a.zip
input-frame: remove header rule and rails, plain separator
The input box now renders as bare text: no top header (the "╭─ ❯ ───╮" rule is dropped), no side rails, and the bottom is a plain edge-to-edge rule rather than a ├──┤ divider, so nothing around the input reads as a box. Content is laid out at the real full width and passed through verbatim, which keeps the cursor/IME marker exactly where the editor put it. Scroll indicators ("↑ N more") still render, since they carry information rather than chrome; set KEEP_SCROLL_INDICATOR to false to drop those too. Removed the now-dead corner constants and railWrap, and renamed buildBorder to buildRule to match what it emits.
-rw-r--r--pi-ext/input-frame.ts96
1 files changed, 42 insertions, 54 deletions
diff --git a/pi-ext/input-frame.ts b/pi-ext/input-frame.ts
index 7227cb7..c34607d 100644
--- a/pi-ext/input-frame.ts
+++ b/pi-ext/input-frame.ts
@@ -12,30 +12,32 @@
* Editor, and the class the main input is built from), so extension-input and
* selector editors keep the plain native chrome.
*
- * We render content at a reduced inner width and wrap it in a rounded, side-
- * railed box that matches the sl0p tool/prompt boxes. The BOTTOM is a divider
- * (├──┤) rather than a closing corner, so the sl0p-footer directly below (which
- * draws matching side rails + the real ╰──╯) reads as one attached box:
- * ╭─ ❯ ───────────────╮
- * │ your input text │
- * ├────────────────────┤ ← divider; sl0p-footer continues below
- * /slash menu below, borderless, indented to sit under the rail interior
+ * All chrome around the input itself is stripped: the TOP rule is dropped (no
+ * header) and there are NO side rails, so the text sits flush at full width.
+ * Only the BOTTOM survives, and as a plain edge-to-edge rule (no ├┤ caps), so
+ * nothing around the input reads as a box:
+ * your input text
+ * ───────────────────── ← separator; sl0p-footer continues below
+ * /slash menu below, borderless
+ *
+ * Exception: when the editor's top rule is a scroll indicator (`─── ↑ 3 more ───`)
+ * it carries real information, so it is still drawn (set KEEP_SCROLL_INDICATOR
+ * to false to suppress that too).
*
* Correctness notes:
- * - Content is laid out at width-4 (orig.call(this, inner)) so the editor's
- * own word-wrap / lastWidth / cursor navigation all match what we display.
- * - The hardware cursor / IME marker (CURSOR_MARKER) is located by the TUI via
- * visibleWidth(textBeforeMarker); prefixing every content line with the same
- * "│ " shifts that column by exactly the 2 cols the visible cursor moved, so
- * the cursor stays aligned. We never truncate content lines (that could drop
- * the marker) — we only pad.
+ * - Content is laid out at the real full width (orig.call(this, width)) so the
+ * editor's own word-wrap / lastWidth / cursor navigation match the display.
+ * - Content lines are passed through untouched: no prefix, no padding, no
+ * truncation. That keeps the hardware cursor / IME marker (CURSOR_MARKER),
+ * which the TUI locates via visibleWidth(textBeforeMarker), exactly where
+ * the editor put it.
* - Scroll indicators keep their "↑/↓ N more" text; we round their end caps and
* rebuild them at full width.
- * - The autocomplete/slash menu (lines after the bottom rule) is left as the
- * editor produced it, just indented to align under the box interior.
+ * - The autocomplete/slash menu (lines after the bottom rule) is left exactly
+ * as the editor produced it.
*
* Reuses the instance's own `borderColor`, so the box still reflects bash-mode /
- * thinking-level colors. Change GLYPH / corner glyphs below.
+ * thinking-level colors. Change the corner glyphs below.
*
* Safety: idempotent + reload-friendly, feature-detects, and try/catches to the
* native full-width render on any error; restores the original on shutdown.
@@ -46,17 +48,12 @@ import { CustomEditor } from "@earendil-works/pi-coding-agent";
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
// ── Style knobs ─────────────────────────────────────────────────────────────
-const GLYPH = "❯"; // top-left corner glyph (shell-prompt feel). "" for none.
+// The top header rule is not drawn. Scroll indicators ("↑ N more") are kept
+// because they are information rather than chrome; set to false to drop them.
+const KEEP_SCROLL_INDICATOR = true;
// ────────────────────────────────────────────────────────────────────────────
const H = "─";
-const V = "│";
-const TL = "╭";
-const TR = "╮";
-// The input box's bottom is a divider (tee junctions), not closing corners, so
-// the footer beneath it looks like a continuation of the same box.
-const ML = "├";
-const MR = "┤";
const ORIG = Symbol.for("sl0p.inputFrame.origRender");
@@ -77,23 +74,21 @@ function classifyRule(line: string): "plain" | "indicator" | null {
return null;
}
-// The label to embed in a rounded border for a given rule line. Plain top rules
-// get the corner glyph; scroll indicators keep their "↑/↓ N more" text; plain
-// bottom rules get nothing.
-function borderLabel(line: string, kind: "top" | "bottom"): string | undefined {
- const type = classifyRule(line);
- if (type === "indicator") {
+// The label to embed in a rule line. Scroll indicators keep their "↑/↓ N more"
+// text; plain rules get nothing.
+function borderLabel(line: string): string | undefined {
+ if (classifyRule(line) === "indicator") {
const m = stripAnsi(line).match(/[↑↓] \d+ more/);
if (m) return ` ${m[0]} `;
}
- if (kind === "top" && GLYPH) return ` ${GLYPH} `;
return undefined;
}
-// Build a full-width rounded border line, optionally with a left-anchored label.
-function buildBorder(width: number, kind: "top" | "bottom", label: string | undefined, paint: Paint): string {
- const L = kind === "top" ? TL : ML;
- const R = kind === "top" ? TR : MR;
+// Build a full-width plain rule, optionally with a left-anchored label. No end
+// caps: the rule runs edge to edge so nothing reads as a box rail.
+function buildRule(width: number, label: string | undefined, paint: Paint): string {
+ const L = H;
+ const R = H;
const inner = Math.max(0, width - 2);
if (!label) return paint(L + H.repeat(inner) + R);
const leftLen = 1;
@@ -104,15 +99,6 @@ function buildBorder(width: number, kind: "top" | "bottom", label: string | unde
return paint(L + H.repeat(leftLen) + content + H.repeat(rightLen) + R);
}
-// Wrap one already-laid-out (inner-width) content line in side rails. Never
-// truncates — content is already sized to `inner`, and truncating could drop the
-// zero-width cursor marker. Pads short lines to keep the right rail aligned.
-function railWrap(line: string, width: number, paint: Paint): string {
- const inner = Math.max(0, width - 4); // rails (2) + one space of padding each side
- const pad = Math.max(0, inner - visibleWidth(line));
- return `${paint(V)} ${line}${" ".repeat(pad)} ${paint(V)}`;
-}
-
export default function (pi: ExtensionAPI) {
const Cls = CustomEditor as unknown as {
prototype: { render?: (width: number) => string[]; borderColor?: Paint };
@@ -127,11 +113,11 @@ export default function (pi: ExtensionAPI) {
const orig = (Cls as any)[ORIG] as (width: number) => string[];
proto.render = function patchedRender(this: any, width: number): string[] {
- // Too narrow to inset — fall back to the native full-width chrome.
+ // Too narrow to restyle — fall back to the native chrome.
if (!width || width < 12) return orig.call(this, width);
let lines: string[];
try {
- lines = orig.call(this, width - 4); // lay content out at the inner width
+ lines = orig.call(this, width); // no rails: content uses the real width
} catch {
return orig.call(this, width);
}
@@ -154,12 +140,14 @@ export default function (pi: ExtensionAPI) {
if (topIdx < 0 || botIdx < 0) return orig.call(this, width); // unexpected shape
const out: string[] = [];
- out.push(buildBorder(width, "top", borderLabel(lines[topIdx], "top"), paint));
- for (let i = topIdx + 1; i < botIdx; i++) out.push(railWrap(lines[i], width, paint));
- out.push(buildBorder(width, "bottom", borderLabel(lines[botIdx], "bottom"), paint));
- // Menu rows below the box: keep as-is, indented 2 cols to align under the
- // rail interior (rail + inner space). No cursor marker lives here.
- for (let i = botIdx + 1; i < lines.length; i++) out.push(" " + lines[i]);
+ // No header: the top rule is dropped unless it's a scroll indicator.
+ const topLabel = KEEP_SCROLL_INDICATOR ? borderLabel(lines[topIdx]) : undefined;
+ if (topLabel) out.push(buildRule(width, topLabel, paint));
+ // No rails: content rows pass through verbatim (cursor marker intact).
+ for (let i = topIdx + 1; i < botIdx; i++) out.push(lines[i]);
+ out.push(buildRule(width, borderLabel(lines[botIdx]), paint));
+ // Menu rows below the divider: exactly as the editor produced them.
+ for (let i = botIdx + 1; i < lines.length; i++) out.push(lines[i]);
return out;
} catch {
return orig.call(this, width);