784fd56505
The whole-line editor decorate ran on `displayText` after the editor appended the zero-width CURSOR_MARKER and cursor glyph; both start with ESC, so the magic-keyword regex's right-boundary `(?!\S)` rejected `ultrathink` glued to the marker and dropped the gradient until a trailing character was typed. - `Editor.#decorate` (pi-tui) now splits around CURSOR_MARKER and decorates each user-text segment independently, so word-boundary lookarounds resolve correctly on both sides; the matching comment block is corrected. - `KeywordHighlighter` / `highlightMagicKeywords` gain an optional `phase` in [0, 1) that cyclically rotates the gradient stops. `0` (default) yields the static palette, so sent-bubble rendering is unaffected. - `CustomEditor.decorateText` derives `phase` from `Date.now()` and chains `setTimeout(SHIMMER_FRAME_MS)` ticks while focused, the buffer holds a magic keyword, and `magicKeywords.enabled` is on — the render itself schedules the next frame, so losing focus, deleting the keyword, or flipping the setting stops the animation on its own. `interactive-mode` wires the repaint hook to `requestComponentRender(editor)` on construction and after `setEditorComponent`. - Adds `hasMagicKeyword(text)` (cheap prose-aware probe) and tests for the seam fix, the phase cycle, the gating, and timer cleanup. Fixes #2475
43 lines
2.0 KiB
TypeScript
43 lines
2.0 KiB
TypeScript
import { containsOrchestrate, highlightOrchestrate } from "./orchestrate";
|
|
import { containsUltrathink, highlightUltrathink } from "./ultrathink";
|
|
import { containsWorkflow, highlightWorkflow } from "./workflow";
|
|
|
|
/**
|
|
* Gradient-highlight every magic keyword ("ultrathink", "orchestrate",
|
|
* "workflowz") that appears as standalone prose, skipping any occurrence inside a
|
|
* code block, inline code span, or XML/HTML section. Each highlighter paints its
|
|
* own keyword with its own gradient, so chaining is order-independent — the
|
|
* earlier passes only inject zero-width SGR escapes (no backticks or angle
|
|
* brackets), which never confuse the later passes' markdown masking.
|
|
*
|
|
* `resetTo` is the SGR foreground sequence restored after each painted keyword;
|
|
* pass the surrounding text color when decorating already-colored content (e.g.
|
|
* a themed message bubble) so the gradient does not bleed into the rest of the
|
|
* line. Defaults to a plain foreground reset for default-colored editor text.
|
|
*
|
|
* `phase` ∈ [0, 1) cyclically rotates each gradient — the editor passes a
|
|
* `Date.now()`-derived value to animate a Claude-Code-style shimmer while a
|
|
* keyword is on screen and the prompt is focused; sent message bubbles omit it
|
|
* to keep the static gradient.
|
|
*/
|
|
export function highlightMagicKeywords(text: string, resetTo?: string, phase?: number): string {
|
|
return highlightWorkflow(
|
|
highlightOrchestrate(highlightUltrathink(text, resetTo, phase), resetTo, phase),
|
|
resetTo,
|
|
phase,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Cheap test for "does this text contain any magic keyword as standalone prose?".
|
|
* Short-circuits on a substring probe before paying for the markdown-aware
|
|
* prose check, so the common "no keyword in buffer" path is just three
|
|
* `String#indexOf`s. Used by the live editor to gate the shimmer timer.
|
|
*/
|
|
export function hasMagicKeyword(text: string): boolean {
|
|
if (!text.includes("ultrathink") && !text.includes("orchestrate") && !text.includes("workflowz")) {
|
|
return false;
|
|
}
|
|
return containsUltrathink(text) || containsOrchestrate(text) || containsWorkflow(text);
|
|
}
|