From 42a4d48b0bf9cf08b169ebefbd7dd35a7b451d69 Mon Sep 17 00:00:00 2001 From: shahondin1624 Date: Thu, 23 Apr 2026 22:09:01 +0200 Subject: [PATCH] Darken warpGreen to nurgle-green + patch Editor for cursor/typing - Theme: warpGreen #7a8a42 -> #5a6b2e (darker, more "nurgle", still readable for syntaxString / success / toolDiffAdded). - markdown-body-color: extend to also monkey-patch Editor.prototype.render so typed-text and the cursor (reverse-video) inherit the same inkPurple body color instead of falling through to the terminal-default fg. Re-opens the color after \x1b[0m (cursor's full reset) and \x1b[39m (nested theme.fg close) so color survives those breaks within a line. Co-Authored-By: Claude Opus 4.7 (1M context) --- markdown-body-color.ts | 75 +++++++++++++++++++++++-------------- themes/dark-mechanicus.json | 2 +- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/markdown-body-color.ts b/markdown-body-color.ts index 0c1c131..c82d424 100644 --- a/markdown-body-color.ts +++ b/markdown-body-color.ts @@ -1,30 +1,37 @@ /** - * markdown-body-color — force a theme-aware color on markdown paragraph text. + * markdown-body-color — force a theme-aware color on text paths that pi-tui + * emits uncoloured, so they stop inheriting the terminal profile's default + * foreground color (green on many themes). * - * Why: pi-tui's Markdown component only wraps paragraph text in a color when - * the caller passes a `defaultTextStyle.color` fn (assistant-message.js does - * this for thinking blocks but not for regular assistant content). Without it, - * paragraph output is emitted uncoloured and inherits the terminal profile's - * foreground color — which on many terminal themes is green. + * Two patches: * - * Fix: monkey-patch Markdown.prototype.render so that if no defaultTextStyle - * is set at render time, we install one that wraps output in the active pi - * theme's `text` token color. This leaves thinking blocks and custom-colored - * markdowns alone (they already have their own defaultTextStyle). + * 1. Markdown.prototype.render — pi-tui's Markdown component only wraps + * paragraph text when the caller passes a `defaultTextStyle.color` fn. + * pi-coding-agent does this for thinking blocks but not for regular + * assistant content. We install a fallback defaultTextStyle before the + * original render runs. + * + * 2. Editor.prototype.render — the Editor emits typed text and the cursor + * (via \x1b[7m reverse-video) with no color wrapping at all. We wrap the + * whole line in our body color and re-open the color after any `\x1b[0m` + * (cursor's full-reset) or `\x1b[39m` (default-fg reset, used by nested + * theme.fg wrappers) so the color persists across those breaks. + * + * Body color defaults to the dark-mechanicus "inkPurple" (#d4c5e8). Override + * via PI_MARKDOWN_BODY_COLOR env var. */ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Markdown } from "@mariozechner/pi-tui"; +import { Editor, Markdown } from "@mariozechner/pi-tui"; -// Hardcoded fallback — matches themes/dark-mechanicus.json "inkPurple" var. -// Override via PI_MARKDOWN_BODY_COLOR env var (hex, e.g. "#d4c5e8"). const FALLBACK_HEX = process.env.PI_MARKDOWN_BODY_COLOR ?? "#d4c5e8"; function hexToRgb(hex: string): [number, number, number] { const m = hex.replace(/^#/, ""); - const n = parseInt(m.length === 3 - ? m.split("").map((c) => c + c).join("") - : m, 16); + const n = parseInt( + m.length === 3 ? m.split("").map((c) => c + c).join("") : m, + 16, + ); return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]; } @@ -32,11 +39,14 @@ const [R, G, B] = hexToRgb(FALLBACK_HEX); const OPEN = `\x1b[38;2;${R};${G};${B}m`; const CLOSE = `\x1b[39m`; -function wrapWithBodyColor(text: string): string { - // Re-open the color after any existing `\x1b[39m` (default-fg reset) so - // nested styled runs inside a paragraph don't bleed back to terminal fg. - const safe = text.split(CLOSE).join(CLOSE + OPEN); - return OPEN + safe + CLOSE; +// Re-open the body color after any ANSI reset inside a line. Cursor inverse +// video uses \x1b[0m (full reset) and theme.fg helpers close with \x1b[39m. +function reopenAfterResets(s: string): string { + return s.replace(/\x1b\[0m/g, `\x1b[0m${OPEN}`).replace(/\x1b\[39m/g, `\x1b[39m${OPEN}`); +} + +function wrap(s: string): string { + return OPEN + reopenAfterResets(s) + CLOSE; } let patched = false; @@ -45,19 +55,26 @@ export default function (_pi: ExtensionAPI) { if (patched) return; patched = true; - const proto = Markdown.prototype as any; - const originalRender = proto.render; - - proto.render = function (width: number) { + // ─ 1. Markdown component ──────────────────────────────────────────── + const mdProto = Markdown.prototype as any; + const origMdRender = mdProto.render; + mdProto.render = function (width: number) { if (!this.defaultTextStyle) { - this.defaultTextStyle = { color: wrapWithBodyColor }; - // Force cache invalidation so the first post-patch render colors content. + this.defaultTextStyle = { color: wrap }; if (typeof this.invalidate === "function") this.invalidate(); } - return originalRender.call(this, width); + return origMdRender.call(this, width); + }; + + // ─ 2. Editor component ────────────────────────────────────────────── + const edProto = Editor.prototype as any; + const origEdRender = edProto.render; + edProto.render = function (width: number) { + const lines: string[] = origEdRender.call(this, width); + return lines.map(wrap); }; console.log( - `[markdown-body-color] Default markdown body color = ${FALLBACK_HEX}`, + `[markdown-body-color] Body color = ${FALLBACK_HEX} (Markdown + Editor)`, ); } diff --git a/themes/dark-mechanicus.json b/themes/dark-mechanicus.json index 10cea8e..ca0e2f1 100644 --- a/themes/dark-mechanicus.json +++ b/themes/dark-mechanicus.json @@ -25,7 +25,7 @@ "steel": "#5a5f6a", "gunmetal": "#3d4148", "oldBronze": "#8b6914", - "warpGreen": "#7a8a42", + "warpGreen": "#5a6b2e", "voidPurple": "#4a2d4d", "cognitorPink": "#c75a8a",