@cavelang/highlight 0.22.0 → 0.27.14
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/README.md +2 -0
- package/dist/src/browser.d.ts +14 -0
- package/dist/src/browser.d.ts.map +1 -0
- package/dist/src/browser.js +11 -0
- package/dist/src/browser.js.map +1 -0
- package/dist/src/core.d.ts +25 -0
- package/dist/src/core.d.ts.map +1 -0
- package/dist/src/core.js +82 -0
- package/dist/src/core.js.map +1 -0
- package/dist/src/index.d.ts +2 -21
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -78
- package/dist/src/index.js.map +1 -1
- package/package.json +6 -2
- package/src/browser.ts +26 -0
- package/src/core.ts +101 -0
- package/src/index.ts +4 -96
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ process.stdout.write(ansi('auth/middleware USES jwt @ 90% #security\n'))
|
|
|
16
16
|
- `ansi(text, theme?)` — ANSI-colored text; themes map capture names (or
|
|
17
17
|
dotted prefixes, longest wins) to SGR parameters
|
|
18
18
|
- `paint(text, spans, theme?)` — renderer, exported for custom span sources
|
|
19
|
+
- `@cavelang/highlight/browser` — browser factory accepting emitted parser
|
|
20
|
+
and language WASM URLs plus the shared highlights query
|
|
19
21
|
|
|
20
22
|
The default theme leaves entities uncolored — they are the bulk of every
|
|
21
23
|
line; color carries the structure (verbs, values, metadata, comments).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Browser entry point for CAVE's Tree-sitter highlighter. */
|
|
2
|
+
import { type Highlighter } from './core.ts';
|
|
3
|
+
export * from './core.ts';
|
|
4
|
+
export type BrowserHighlighterOptions = {
|
|
5
|
+
/** URL emitted for `web-tree-sitter/web-tree-sitter.wasm`. */
|
|
6
|
+
readonly parserWasmUrl: string;
|
|
7
|
+
/** URL emitted for `@cavelang/tree-sitter-cave/wasm`. */
|
|
8
|
+
readonly languageWasmUrl: string;
|
|
9
|
+
/** Contents of `@cavelang/tree-sitter-cave/highlights`. */
|
|
10
|
+
readonly querySource: string;
|
|
11
|
+
};
|
|
12
|
+
/** Loads the parser and CAVE grammar WASM, then creates a browser highlighter. */
|
|
13
|
+
export declare const createBrowserHighlighter: ({ parserWasmUrl, languageWasmUrl, querySource, }: BrowserHighlighterOptions) => Promise<Highlighter>;
|
|
14
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAG9D,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAE/D,cAAc,WAAW,CAAA;AAEzB,MAAM,MAAM,yBAAyB,GAAG;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,yDAAyD;IACzD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,2DAA2D;IAC3D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,GAAU,kDAI5C,yBAAyB,KAAG,OAAO,CAAC,WAAW,CAIjD,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Browser entry point for CAVE's Tree-sitter highlighter. */
|
|
2
|
+
import { Language, Parser } from 'web-tree-sitter';
|
|
3
|
+
import { createHighlighter } from "./core.js";
|
|
4
|
+
export * from "./core.js";
|
|
5
|
+
/** Loads the parser and CAVE grammar WASM, then creates a browser highlighter. */
|
|
6
|
+
export const createBrowserHighlighter = async ({ parserWasmUrl, languageWasmUrl, querySource, }) => {
|
|
7
|
+
await Parser.init({ locateFile: () => parserWasmUrl });
|
|
8
|
+
const language = await Language.load(languageWasmUrl);
|
|
9
|
+
return createHighlighter(language, querySource);
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAA;AAE/D,cAAc,WAAW,CAAA;AAWzB,kFAAkF;AAClF,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,EAAE,EAC7C,aAAa,EACb,eAAe,EACf,WAAW,GACe,EAAwB,EAAE;IACpD,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IACrD,OAAO,iBAAiB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAA;AACjD,CAAC,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Language } from 'web-tree-sitter';
|
|
2
|
+
/** One highlighted range: `capture` is a `highlights.scm` name like `keyword`. */
|
|
3
|
+
export type Span = {
|
|
4
|
+
readonly start: number;
|
|
5
|
+
readonly end: number;
|
|
6
|
+
readonly capture: string;
|
|
7
|
+
};
|
|
8
|
+
/** Capture name (or dotted prefix) to ANSI SGR parameters, e.g. `keyword: '35'`. */
|
|
9
|
+
export type Theme = Readonly<Record<string, string>>;
|
|
10
|
+
/**
|
|
11
|
+
* Terminal-default-friendly theme: entities (`variable`) stay uncolored on
|
|
12
|
+
* purpose — they are the bulk of every line; color carries the structure.
|
|
13
|
+
*/
|
|
14
|
+
export declare const defaultTheme: Theme;
|
|
15
|
+
export type Highlighter = {
|
|
16
|
+
/** Non-overlapping capture spans of `text`, in document order. */
|
|
17
|
+
readonly spans: (text: string) => readonly Span[];
|
|
18
|
+
/** `text` with ANSI colors applied per `theme` (default {@link defaultTheme}). */
|
|
19
|
+
readonly ansi: (text: string, theme?: Theme) => string;
|
|
20
|
+
};
|
|
21
|
+
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
22
|
+
export declare const paint: (text: string, spans: readonly Span[], theme?: Theme) => string;
|
|
23
|
+
/** Creates a highlighter from an initialized Tree-sitter language and query. */
|
|
24
|
+
export declare const createHighlighter: (language: Language, querySource: string) => Highlighter;
|
|
25
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAE9D,kFAAkF;AAClF,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,oFAAoF;AACpF,MAAM,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAEpD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAc1B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,IAAI,EAAE,CAAA;IACjD,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,MAAM,CAAA;CACvD,CAAA;AAeD,6EAA6E;AAC7E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,OAAO,SAAS,IAAI,EAAE,EAAE,QAAO,KAAoB,KAAG,MAWzF,CAAA;AAED,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,GAAI,UAAU,QAAQ,EAAE,aAAa,MAAM,KAAG,WAiC3E,CAAA"}
|
package/dist/src/core.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Parser, Query } from 'web-tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Terminal-default-friendly theme: entities (`variable`) stay uncolored on
|
|
4
|
+
* purpose — they are the bulk of every line; color carries the structure.
|
|
5
|
+
*/
|
|
6
|
+
export const defaultTheme = {
|
|
7
|
+
comment: '90',
|
|
8
|
+
keyword: '35',
|
|
9
|
+
'keyword.operator': '95',
|
|
10
|
+
property: '33',
|
|
11
|
+
number: '36',
|
|
12
|
+
type: '36',
|
|
13
|
+
string: '32',
|
|
14
|
+
'string.special': '96',
|
|
15
|
+
label: '34',
|
|
16
|
+
constant: '33',
|
|
17
|
+
operator: '91',
|
|
18
|
+
tag: '94',
|
|
19
|
+
punctuation: '90'
|
|
20
|
+
};
|
|
21
|
+
/** Longest dotted prefix of `capture` present in `theme`. */
|
|
22
|
+
const styleOf = (theme, capture) => {
|
|
23
|
+
for (let name = capture;; name = name.slice(0, name.lastIndexOf('.'))) {
|
|
24
|
+
const style = theme[name];
|
|
25
|
+
if (style !== undefined) {
|
|
26
|
+
return style;
|
|
27
|
+
}
|
|
28
|
+
if (!name.includes('.')) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
34
|
+
export const paint = (text, spans, theme = defaultTheme) => {
|
|
35
|
+
let out = '';
|
|
36
|
+
let at = 0;
|
|
37
|
+
for (const span of spans) {
|
|
38
|
+
const style = styleOf(theme, span.capture);
|
|
39
|
+
out += text.slice(at, span.start);
|
|
40
|
+
const piece = text.slice(span.start, span.end);
|
|
41
|
+
out += style === undefined ? piece : `\u001B[${style}m${piece}\u001B[0m`;
|
|
42
|
+
at = span.end;
|
|
43
|
+
}
|
|
44
|
+
return out + text.slice(at);
|
|
45
|
+
};
|
|
46
|
+
/** Creates a highlighter from an initialized Tree-sitter language and query. */
|
|
47
|
+
export const createHighlighter = (language, querySource) => {
|
|
48
|
+
const query = new Query(language, querySource);
|
|
49
|
+
const parser = new Parser();
|
|
50
|
+
parser.setLanguage(language);
|
|
51
|
+
const spans = (text) => {
|
|
52
|
+
const tree = parser.parse(text);
|
|
53
|
+
if (tree === null) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const all = query.captures(tree.rootNode)
|
|
58
|
+
.map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
|
|
59
|
+
.filter(span => span.end > span.start)
|
|
60
|
+
.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
61
|
+
// One capture per node keeps these disjoint already; guard anyway so a
|
|
62
|
+
// future query with nested captures degrades to outermost-wins.
|
|
63
|
+
const disjoint = [];
|
|
64
|
+
let at = 0;
|
|
65
|
+
for (const span of all) {
|
|
66
|
+
if (span.start >= at) {
|
|
67
|
+
disjoint.push(span);
|
|
68
|
+
at = span.end;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return disjoint;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
tree.delete();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
spans,
|
|
79
|
+
ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -11,27 +11,8 @@
|
|
|
11
11
|
* capture-name-keyed theme (longest dotted prefix wins, unstyled text passes
|
|
12
12
|
* through untouched).
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
readonly start: number;
|
|
17
|
-
readonly end: number;
|
|
18
|
-
readonly capture: string;
|
|
19
|
-
};
|
|
20
|
-
/** Capture name (or dotted prefix) to ANSI SGR parameters, e.g. `keyword: '35'`. */
|
|
21
|
-
export type Theme = Readonly<Record<string, string>>;
|
|
22
|
-
/**
|
|
23
|
-
* Terminal-default-friendly theme: entities (`variable`) stay uncolored on
|
|
24
|
-
* purpose — they are the bulk of every line; color carries the structure.
|
|
25
|
-
*/
|
|
26
|
-
export declare const defaultTheme: Theme;
|
|
27
|
-
export type Highlighter = {
|
|
28
|
-
/** Non-overlapping capture spans of `text`, in document order. */
|
|
29
|
-
readonly spans: (text: string) => readonly Span[];
|
|
30
|
-
/** `text` with ANSI colors applied per `theme` (default {@link defaultTheme}). */
|
|
31
|
-
readonly ansi: (text: string, theme?: Theme) => string;
|
|
32
|
-
};
|
|
33
|
-
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
34
|
-
export declare const paint: (text: string, spans: readonly Span[], theme?: Theme) => string;
|
|
14
|
+
import { type Highlighter } from './core.ts';
|
|
15
|
+
export * from './core.ts';
|
|
35
16
|
/** The process-wide highlighter; loads the grammar WASM on first call. */
|
|
36
17
|
export declare const highlighter: () => Promise<Highlighter>;
|
|
37
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA;AAE/D,cAAc,WAAW,CAAA;AAazB,0EAA0E;AAC1E,eAAO,MAAM,WAAW,QAAO,OAAO,CAAC,WAAW,CAC7B,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -13,88 +13,14 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync } from 'node:fs';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { Language, Parser
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
* purpose — they are the bulk of every line; color carries the structure.
|
|
20
|
-
*/
|
|
21
|
-
export const defaultTheme = {
|
|
22
|
-
comment: '90',
|
|
23
|
-
keyword: '35',
|
|
24
|
-
'keyword.operator': '95',
|
|
25
|
-
property: '33',
|
|
26
|
-
number: '36',
|
|
27
|
-
type: '36',
|
|
28
|
-
string: '32',
|
|
29
|
-
'string.special': '96',
|
|
30
|
-
label: '34',
|
|
31
|
-
constant: '33',
|
|
32
|
-
operator: '91',
|
|
33
|
-
tag: '94',
|
|
34
|
-
punctuation: '90'
|
|
35
|
-
};
|
|
16
|
+
import { Language, Parser } from 'web-tree-sitter';
|
|
17
|
+
import { createHighlighter } from "./core.js";
|
|
18
|
+
export * from "./core.js";
|
|
36
19
|
const resolvePath = (specifier) => fileURLToPath(import.meta.resolve(specifier));
|
|
37
|
-
/** Longest dotted prefix of `capture` present in `theme`. */
|
|
38
|
-
const styleOf = (theme, capture) => {
|
|
39
|
-
for (let name = capture;; name = name.slice(0, name.lastIndexOf('.'))) {
|
|
40
|
-
const style = theme[name];
|
|
41
|
-
if (style !== undefined) {
|
|
42
|
-
return style;
|
|
43
|
-
}
|
|
44
|
-
if (!name.includes('.')) {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
50
|
-
export const paint = (text, spans, theme = defaultTheme) => {
|
|
51
|
-
let out = '';
|
|
52
|
-
let at = 0;
|
|
53
|
-
for (const span of spans) {
|
|
54
|
-
const style = styleOf(theme, span.capture);
|
|
55
|
-
out += text.slice(at, span.start);
|
|
56
|
-
const piece = text.slice(span.start, span.end);
|
|
57
|
-
out += style === undefined ? piece : `\u001B[${style}m${piece}\u001B[0m`;
|
|
58
|
-
at = span.end;
|
|
59
|
-
}
|
|
60
|
-
return out + text.slice(at);
|
|
61
|
-
};
|
|
62
20
|
const create = async () => {
|
|
63
21
|
await Parser.init();
|
|
64
22
|
const language = await Language.load(resolvePath('@cavelang/tree-sitter-cave/wasm'));
|
|
65
|
-
|
|
66
|
-
const parser = new Parser();
|
|
67
|
-
parser.setLanguage(language);
|
|
68
|
-
const spans = (text) => {
|
|
69
|
-
const tree = parser.parse(text);
|
|
70
|
-
if (tree === null) {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
const all = query.captures(tree.rootNode)
|
|
75
|
-
.map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
|
|
76
|
-
.filter(span => span.end > span.start)
|
|
77
|
-
.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
78
|
-
// One capture per node keeps these disjoint already; guard anyway so a
|
|
79
|
-
// future query with nested captures degrades to outermost-wins.
|
|
80
|
-
const disjoint = [];
|
|
81
|
-
let at = 0;
|
|
82
|
-
for (const span of all) {
|
|
83
|
-
if (span.start >= at) {
|
|
84
|
-
disjoint.push(span);
|
|
85
|
-
at = span.end;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return disjoint;
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
tree.delete();
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
return {
|
|
95
|
-
spans,
|
|
96
|
-
ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
|
|
97
|
-
};
|
|
23
|
+
return createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'));
|
|
98
24
|
};
|
|
99
25
|
let cached;
|
|
100
26
|
/** The process-wide highlighter; loads the grammar WASM on first call. */
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAoB,MAAM,WAAW,CAAA;AAE/D,cAAc,WAAW,CAAA;AAEzB,MAAM,WAAW,GAAG,CAAC,SAAiB,EAAU,EAAE,CAChD,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAA;AAE/C,MAAM,MAAM,GAAG,KAAK,IAA0B,EAAE;IAC9C,MAAM,MAAM,CAAC,IAAI,EAAE,CAAA;IACnB,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,iCAAiC,CAAC,CAAC,CAAA;IACpF,OAAO,iBAAiB,CAAC,QAAQ,EAAE,YAAY,CAAC,WAAW,CAAC,uCAAuC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAChH,CAAC,CAAA;AAED,IAAI,MAAwC,CAAA;AAE5C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG,GAAyB,EAAE,CACpD,MAAM,KAAK,MAAM,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavelang/highlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CAVE syntax highlighting — web-tree-sitter over the @cavelang/tree-sitter-cave grammar, rendered to ANSI for terminals.",
|
|
6
6
|
"license": "CC0-1.0",
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
".": {
|
|
18
18
|
"types": "./dist/src/index.d.ts",
|
|
19
19
|
"default": "./dist/src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./browser": {
|
|
22
|
+
"types": "./dist/src/browser.d.ts",
|
|
23
|
+
"default": "./dist/src/browser.js"
|
|
20
24
|
}
|
|
21
25
|
},
|
|
22
26
|
"files": [
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
},
|
|
34
38
|
"dependencies": {
|
|
35
39
|
"web-tree-sitter": "^0.26.10",
|
|
36
|
-
"@cavelang/tree-sitter-cave": "0.
|
|
40
|
+
"@cavelang/tree-sitter-cave": "0.27.14"
|
|
37
41
|
},
|
|
38
42
|
"scripts": {
|
|
39
43
|
"build": "tsc -b",
|
package/src/browser.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Browser entry point for CAVE's Tree-sitter highlighter. */
|
|
2
|
+
|
|
3
|
+
import { Language, Parser } from 'web-tree-sitter'
|
|
4
|
+
import { createHighlighter, type Highlighter } from './core.ts'
|
|
5
|
+
|
|
6
|
+
export * from './core.ts'
|
|
7
|
+
|
|
8
|
+
export type BrowserHighlighterOptions = {
|
|
9
|
+
/** URL emitted for `web-tree-sitter/web-tree-sitter.wasm`. */
|
|
10
|
+
readonly parserWasmUrl: string
|
|
11
|
+
/** URL emitted for `@cavelang/tree-sitter-cave/wasm`. */
|
|
12
|
+
readonly languageWasmUrl: string
|
|
13
|
+
/** Contents of `@cavelang/tree-sitter-cave/highlights`. */
|
|
14
|
+
readonly querySource: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Loads the parser and CAVE grammar WASM, then creates a browser highlighter. */
|
|
18
|
+
export const createBrowserHighlighter = async ({
|
|
19
|
+
parserWasmUrl,
|
|
20
|
+
languageWasmUrl,
|
|
21
|
+
querySource,
|
|
22
|
+
}: BrowserHighlighterOptions): Promise<Highlighter> => {
|
|
23
|
+
await Parser.init({ locateFile: () => parserWasmUrl })
|
|
24
|
+
const language = await Language.load(languageWasmUrl)
|
|
25
|
+
return createHighlighter(language, querySource)
|
|
26
|
+
}
|
package/src/core.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Parser, Query, type Language } from 'web-tree-sitter'
|
|
2
|
+
|
|
3
|
+
/** One highlighted range: `capture` is a `highlights.scm` name like `keyword`. */
|
|
4
|
+
export type Span = {
|
|
5
|
+
readonly start: number
|
|
6
|
+
readonly end: number
|
|
7
|
+
readonly capture: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Capture name (or dotted prefix) to ANSI SGR parameters, e.g. `keyword: '35'`. */
|
|
11
|
+
export type Theme = Readonly<Record<string, string>>
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Terminal-default-friendly theme: entities (`variable`) stay uncolored on
|
|
15
|
+
* purpose — they are the bulk of every line; color carries the structure.
|
|
16
|
+
*/
|
|
17
|
+
export const defaultTheme: Theme = {
|
|
18
|
+
comment: '90',
|
|
19
|
+
keyword: '35',
|
|
20
|
+
'keyword.operator': '95',
|
|
21
|
+
property: '33',
|
|
22
|
+
number: '36',
|
|
23
|
+
type: '36',
|
|
24
|
+
string: '32',
|
|
25
|
+
'string.special': '96',
|
|
26
|
+
label: '34',
|
|
27
|
+
constant: '33',
|
|
28
|
+
operator: '91',
|
|
29
|
+
tag: '94',
|
|
30
|
+
punctuation: '90'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type Highlighter = {
|
|
34
|
+
/** Non-overlapping capture spans of `text`, in document order. */
|
|
35
|
+
readonly spans: (text: string) => readonly Span[]
|
|
36
|
+
/** `text` with ANSI colors applied per `theme` (default {@link defaultTheme}). */
|
|
37
|
+
readonly ansi: (text: string, theme?: Theme) => string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Longest dotted prefix of `capture` present in `theme`. */
|
|
41
|
+
const styleOf = (theme: Theme, capture: string): undefined | string => {
|
|
42
|
+
for (let name = capture; ; name = name.slice(0, name.lastIndexOf('.'))) {
|
|
43
|
+
const style = theme[name]
|
|
44
|
+
if (style !== undefined) {
|
|
45
|
+
return style
|
|
46
|
+
}
|
|
47
|
+
if (!name.includes('.')) {
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
54
|
+
export const paint = (text: string, spans: readonly Span[], theme: Theme = defaultTheme): string => {
|
|
55
|
+
let out = ''
|
|
56
|
+
let at = 0
|
|
57
|
+
for (const span of spans) {
|
|
58
|
+
const style = styleOf(theme, span.capture)
|
|
59
|
+
out += text.slice(at, span.start)
|
|
60
|
+
const piece = text.slice(span.start, span.end)
|
|
61
|
+
out += style === undefined ? piece : `\u001B[${style}m${piece}\u001B[0m`
|
|
62
|
+
at = span.end
|
|
63
|
+
}
|
|
64
|
+
return out + text.slice(at)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Creates a highlighter from an initialized Tree-sitter language and query. */
|
|
68
|
+
export const createHighlighter = (language: Language, querySource: string): Highlighter => {
|
|
69
|
+
const query = new Query(language, querySource)
|
|
70
|
+
const parser = new Parser()
|
|
71
|
+
parser.setLanguage(language)
|
|
72
|
+
const spans = (text: string): readonly Span[] => {
|
|
73
|
+
const tree = parser.parse(text)
|
|
74
|
+
if (tree === null) {
|
|
75
|
+
return []
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const all = query.captures(tree.rootNode)
|
|
79
|
+
.map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
|
|
80
|
+
.filter(span => span.end > span.start)
|
|
81
|
+
.sort((a, b) => a.start - b.start || b.end - a.end)
|
|
82
|
+
// One capture per node keeps these disjoint already; guard anyway so a
|
|
83
|
+
// future query with nested captures degrades to outermost-wins.
|
|
84
|
+
const disjoint: Span[] = []
|
|
85
|
+
let at = 0
|
|
86
|
+
for (const span of all) {
|
|
87
|
+
if (span.start >= at) {
|
|
88
|
+
disjoint.push(span)
|
|
89
|
+
at = span.end
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return disjoint
|
|
93
|
+
} finally {
|
|
94
|
+
tree.delete()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
spans,
|
|
99
|
+
ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,110 +14,18 @@
|
|
|
14
14
|
|
|
15
15
|
import { readFileSync } from 'node:fs'
|
|
16
16
|
import { fileURLToPath } from 'node:url'
|
|
17
|
-
import { Language, Parser
|
|
17
|
+
import { Language, Parser } from 'web-tree-sitter'
|
|
18
|
+
import { createHighlighter, type Highlighter } from './core.ts'
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
export type Span = {
|
|
21
|
-
readonly start: number
|
|
22
|
-
readonly end: number
|
|
23
|
-
readonly capture: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Capture name (or dotted prefix) to ANSI SGR parameters, e.g. `keyword: '35'`. */
|
|
27
|
-
export type Theme = Readonly<Record<string, string>>
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Terminal-default-friendly theme: entities (`variable`) stay uncolored on
|
|
31
|
-
* purpose — they are the bulk of every line; color carries the structure.
|
|
32
|
-
*/
|
|
33
|
-
export const defaultTheme: Theme = {
|
|
34
|
-
comment: '90',
|
|
35
|
-
keyword: '35',
|
|
36
|
-
'keyword.operator': '95',
|
|
37
|
-
property: '33',
|
|
38
|
-
number: '36',
|
|
39
|
-
type: '36',
|
|
40
|
-
string: '32',
|
|
41
|
-
'string.special': '96',
|
|
42
|
-
label: '34',
|
|
43
|
-
constant: '33',
|
|
44
|
-
operator: '91',
|
|
45
|
-
tag: '94',
|
|
46
|
-
punctuation: '90'
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export type Highlighter = {
|
|
50
|
-
/** Non-overlapping capture spans of `text`, in document order. */
|
|
51
|
-
readonly spans: (text: string) => readonly Span[]
|
|
52
|
-
/** `text` with ANSI colors applied per `theme` (default {@link defaultTheme}). */
|
|
53
|
-
readonly ansi: (text: string, theme?: Theme) => string
|
|
54
|
-
}
|
|
20
|
+
export * from './core.ts'
|
|
55
21
|
|
|
56
22
|
const resolvePath = (specifier: string): string =>
|
|
57
23
|
fileURLToPath(import.meta.resolve(specifier))
|
|
58
24
|
|
|
59
|
-
/** Longest dotted prefix of `capture` present in `theme`. */
|
|
60
|
-
const styleOf = (theme: Theme, capture: string): undefined | string => {
|
|
61
|
-
for (let name = capture; ; name = name.slice(0, name.lastIndexOf('.'))) {
|
|
62
|
-
const style = theme[name]
|
|
63
|
-
if (style !== undefined) {
|
|
64
|
-
return style
|
|
65
|
-
}
|
|
66
|
-
if (!name.includes('.')) {
|
|
67
|
-
return undefined
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Renders `spans` over `text` as ANSI; exported for custom span sources. */
|
|
73
|
-
export const paint = (text: string, spans: readonly Span[], theme: Theme = defaultTheme): string => {
|
|
74
|
-
let out = ''
|
|
75
|
-
let at = 0
|
|
76
|
-
for (const span of spans) {
|
|
77
|
-
const style = styleOf(theme, span.capture)
|
|
78
|
-
out += text.slice(at, span.start)
|
|
79
|
-
const piece = text.slice(span.start, span.end)
|
|
80
|
-
out += style === undefined ? piece : `\u001B[${style}m${piece}\u001B[0m`
|
|
81
|
-
at = span.end
|
|
82
|
-
}
|
|
83
|
-
return out + text.slice(at)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
25
|
const create = async (): Promise<Highlighter> => {
|
|
87
26
|
await Parser.init()
|
|
88
27
|
const language = await Language.load(resolvePath('@cavelang/tree-sitter-cave/wasm'))
|
|
89
|
-
|
|
90
|
-
const parser = new Parser()
|
|
91
|
-
parser.setLanguage(language)
|
|
92
|
-
const spans = (text: string): readonly Span[] => {
|
|
93
|
-
const tree = parser.parse(text)
|
|
94
|
-
if (tree === null) {
|
|
95
|
-
return []
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const all = query.captures(tree.rootNode)
|
|
99
|
-
.map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
|
|
100
|
-
.filter(span => span.end > span.start)
|
|
101
|
-
.sort((a, b) => a.start - b.start || b.end - a.end)
|
|
102
|
-
// One capture per node keeps these disjoint already; guard anyway so a
|
|
103
|
-
// future query with nested captures degrades to outermost-wins.
|
|
104
|
-
const disjoint: Span[] = []
|
|
105
|
-
let at = 0
|
|
106
|
-
for (const span of all) {
|
|
107
|
-
if (span.start >= at) {
|
|
108
|
-
disjoint.push(span)
|
|
109
|
-
at = span.end
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return disjoint
|
|
113
|
-
} finally {
|
|
114
|
-
tree.delete()
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
spans,
|
|
119
|
-
ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
|
|
120
|
-
}
|
|
28
|
+
return createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'))
|
|
121
29
|
}
|
|
122
30
|
|
|
123
31
|
let cached: undefined | Promise<Highlighter>
|