@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 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"}
@@ -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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"}
@@ -11,27 +11,8 @@
11
11
  * capture-name-keyed theme (longest dotted prefix wins, unstyled text passes
12
12
  * through untouched).
13
13
  */
14
- /** One highlighted range: `capture` is a `highlights.scm` name like `keyword`. */
15
- export type Span = {
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,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;AAkBD,6EAA6E;AAC7E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,OAAO,SAAS,IAAI,EAAE,EAAE,QAAO,KAAoB,KAAG,MAWzF,CAAA;AAyCD,0EAA0E;AAC1E,eAAO,MAAM,WAAW,QAAO,OAAO,CAAC,WAAW,CAC7B,CAAA"}
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, Query } from 'web-tree-sitter';
17
- /**
18
- * Terminal-default-friendly theme: entities (`variable`) stay uncolored on
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
- const query = new Query(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'));
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. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
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.22.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.22.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, Query } from 'web-tree-sitter'
17
+ import { Language, Parser } from 'web-tree-sitter'
18
+ import { createHighlighter, type Highlighter } from './core.ts'
18
19
 
19
- /** One highlighted range: `capture` is a `highlights.scm` name like `keyword`. */
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
- const query = new Query(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'))
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>