diff options
| author | user <user@clank> | 2026-08-11 00:09:05 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 19:34:35 +0200 |
| commit | 456bc7e80ccee97e0435e380b7127972db65577a (patch) | |
| tree | cc47e338e3f96859038149e7160595c43551250d | |
| parent | chrome: drop side rails across boxes (zellij draws the pane border) (diff) | |
| download | sl0ppi-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.ts | 96 |
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); |
