@cueloop/client 0.1.0-alpha.0 → 0.1.0-alpha.10
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/package.json +13 -7
- package/src/App.autoclose.test.tsx +118 -0
- package/src/App.export.test.tsx +10 -2
- package/src/App.plan-review.test.tsx +259 -0
- package/src/App.rendering.test.tsx +108 -0
- package/src/App.submit-confirm.test.tsx +183 -0
- package/src/App.test.tsx +6 -5
- package/src/App.tsx +0 -0
- package/src/config.ts +37 -0
- package/src/herdr.test.ts +68 -0
- package/src/herdr.ts +34 -0
- package/src/keymap.test.ts +270 -0
- package/src/keymap.ts +173 -0
- package/src/run.ts +3 -1
- package/src/session-controller.ts +398 -0
- package/src/syntax.test.ts +41 -0
- package/src/syntax.ts +88 -0
- package/src/view.test.ts +6 -6
- package/src/view.ts +0 -0
package/src/syntax.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code-block syntax highlighting via Shiki (#67): TextMate-grammar tokens
|
|
3
|
+
* colored by a theme built from cueloop's tokens. The highlighter loads
|
|
4
|
+
* lazily on the first code block; unknown languages render unstyled.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { DARK as T } from "./theme";
|
|
8
|
+
|
|
9
|
+
export interface CodeToken {
|
|
10
|
+
content: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type Highlighter = {
|
|
15
|
+
codeToTokensBase(code: string, opts: { lang: string; theme: string }): { content: string; color?: string }[][];
|
|
16
|
+
getLoadedLanguages(): string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const LANGS = ["typescript", "tsx", "javascript", "jsx", "json", "bash", "python", "rust", "go", "yaml", "diff", "markdown", "toml", "sql", "html", "css"];
|
|
20
|
+
|
|
21
|
+
/** Markdown fence info → Shiki language id. */
|
|
22
|
+
export function filetypeFor(lang?: string): string {
|
|
23
|
+
const l = (lang ?? "").toLowerCase();
|
|
24
|
+
const map: Record<string, string> = {
|
|
25
|
+
ts: "typescript",
|
|
26
|
+
js: "javascript",
|
|
27
|
+
py: "python",
|
|
28
|
+
rb: "ruby",
|
|
29
|
+
rs: "rust",
|
|
30
|
+
sh: "bash",
|
|
31
|
+
shell: "bash",
|
|
32
|
+
zsh: "bash",
|
|
33
|
+
yml: "yaml",
|
|
34
|
+
};
|
|
35
|
+
return map[l] ?? l;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function cueloopTheme() {
|
|
39
|
+
const scope = (scopes: string, foreground: string, fontStyle?: string) => ({
|
|
40
|
+
scope: scopes.split(", "),
|
|
41
|
+
settings: fontStyle ? { foreground, fontStyle } : { foreground },
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
name: "cueloop",
|
|
45
|
+
type: "dark" as const,
|
|
46
|
+
colors: { "editor.background": T.elevated, "editor.foreground": T.textMuted },
|
|
47
|
+
settings: [
|
|
48
|
+
{ settings: { foreground: T.textMuted } },
|
|
49
|
+
scope("comment, punctuation.definition.comment", T.textDim, "italic"),
|
|
50
|
+
scope("string, string.quoted, punctuation.definition.string", T.green),
|
|
51
|
+
scope("constant.numeric, constant.language, constant.character", T.blue),
|
|
52
|
+
scope("keyword, storage, storage.type, storage.modifier, keyword.control", T.accent),
|
|
53
|
+
scope("entity.name.function, support.function, meta.function-call", T.text),
|
|
54
|
+
scope("entity.name.type, entity.name.class, support.type, support.class", T.blue),
|
|
55
|
+
scope("variable, variable.other, variable.parameter", T.textMuted),
|
|
56
|
+
scope("entity.name.tag", T.accent),
|
|
57
|
+
scope("entity.other.attribute-name", T.blue),
|
|
58
|
+
scope("punctuation, meta.brace", T.textDim),
|
|
59
|
+
scope("keyword.operator", T.textDim),
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let highlighterPromise: Promise<Highlighter | null> | null = null;
|
|
65
|
+
|
|
66
|
+
function getHighlighter(): Promise<Highlighter | null> {
|
|
67
|
+
highlighterPromise ??= import("shiki")
|
|
68
|
+
.then((shiki) => shiki.createHighlighter({ themes: [cueloopTheme()], langs: LANGS }) as Promise<Highlighter>)
|
|
69
|
+
.catch(() => null); // highlighting is an enhancement, never a blocker
|
|
70
|
+
return highlighterPromise;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tokenize code into styled lines. Token text concatenates back to the exact
|
|
75
|
+
* source line, so verbatim rendering (indentation, no wrap) is preserved.
|
|
76
|
+
* Returns null when the language is unknown or the highlighter is unavailable.
|
|
77
|
+
*/
|
|
78
|
+
export async function highlightCode(content: string, lang?: string): Promise<CodeToken[][] | null> {
|
|
79
|
+
const language = filetypeFor(lang);
|
|
80
|
+
if (!language) return null;
|
|
81
|
+
const highlighter = await getHighlighter();
|
|
82
|
+
if (!highlighter || !highlighter.getLoadedLanguages().includes(language)) return null;
|
|
83
|
+
try {
|
|
84
|
+
return highlighter.codeToTokensBase(content, { lang: language, theme: "cueloop" });
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/view.test.ts
CHANGED
|
@@ -2,17 +2,15 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import {
|
|
3
3
|
blockRuns,
|
|
4
4
|
buildDisplay,
|
|
5
|
-
cutBlock,
|
|
6
5
|
displayText,
|
|
7
6
|
marksByDisplay,
|
|
7
|
+
nextWorkBlock,
|
|
8
8
|
overlayMarks,
|
|
9
|
-
restoreBlock,
|
|
10
|
-
restoreLine,
|
|
11
9
|
spanKey,
|
|
12
10
|
startSpan,
|
|
13
11
|
wrapRuns,
|
|
14
12
|
} from "./view";
|
|
15
|
-
import { parseBlocks, type Annotation } from "@cueloop/schema";
|
|
13
|
+
import { cutBlock, parseBlocks, restoreBlock, restoreLine, type Annotation } from "@cueloop/schema";
|
|
16
14
|
|
|
17
15
|
const BASE = `# Plan
|
|
18
16
|
|
|
@@ -150,8 +148,10 @@ describe("cut / restore round-trip", () => {
|
|
|
150
148
|
expect(cut).not.toContain("second item");
|
|
151
149
|
const display = buildDisplay(BASE, cut);
|
|
152
150
|
const delIdx = display.findIndex((d) => d.type === "del");
|
|
153
|
-
const line = restoreLine(display, delIdx, cut.split("\n").length);
|
|
154
|
-
|
|
151
|
+
const line = restoreLine(nextWorkBlock(display, delIdx), cut.split("\n").length);
|
|
152
|
+
// undefined = the working copy round-tripped back to the submitted revision
|
|
153
|
+
const restored = restoreBlock(BASE, cut, li, line);
|
|
154
|
+
expect(restored).toBeUndefined();
|
|
155
155
|
expect(buildDisplay(BASE, restored).every((d) => d.type === "same")).toBe(true);
|
|
156
156
|
});
|
|
157
157
|
});
|
package/src/view.ts
CHANGED
|
Binary file
|