@cavelang/highlight 0.35.0 → 0.36.0

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
@@ -3,7 +3,10 @@
3
3
  CAVE syntax highlighting for terminals — web-tree-sitter over the
4
4
  [`@cavelang/tree-sitter-cave`](../tree-sitter-cave) grammar, colored by the
5
5
  grammar's own `queries/highlights.scm` so terminal output and editor
6
- highlighting share one source of truth.
6
+ highlighting share one source of truth. Explicit full-claim `@claim`
7
+ markers use keyword highlighting; reserved and numeric-looking subjects remain
8
+ entities, while a
9
+ trailing `@claim` remains a context label.
7
10
 
8
11
  ```ts
9
12
  import { highlighter } from '@cavelang/highlight'
@@ -21,3 +24,37 @@ process.stdout.write(ansi('auth/middleware USES jwt @ 90% #security\n'))
21
24
 
22
25
  The default theme leaves entities uncolored — they are the bulk of every
23
26
  line; color carries the structure (verbs, values, metadata, comments).
27
+ Theme lookup uses only the object's own entries, with the longest dotted prefix
28
+ winning; inherited properties never supply styles. Custom `paint` spans use
29
+ JavaScript UTF-16 offsets and must be ordered, non-overlapping integer ranges
30
+ within `0..text.length`, with `start <= end`. Adjacent and empty ranges are
31
+ allowed. Invalid ranges throw `TypeError` rather than silently duplicating or
32
+ truncating source text. The grammar-backed `spans()` already supplies valid
33
+ ordered ranges.
34
+ `paint()` captures the supplied range values before looking up theme styles.
35
+ Theme getters cannot rewrite those validated ranges or extend the in-progress
36
+ render by changing the caller's span array.
37
+
38
+ `highlighter()` shares one in-flight or successful initialization per process.
39
+ A failed initialization rejects its callers and clears that cache, allowing a
40
+ later call to retry after the underlying problem is repaired. It does not retry
41
+ automatically or create a retry loop.
42
+
43
+ The shared factory releases its compiled query and allocated parser if parser
44
+ setup fails. Per-call parse trees are released even when capture extraction
45
+ throws. `createHighlighter(language, querySource)` and
46
+ `createBrowserHighlighter(options)` return an `OwnedHighlighter`. Call its
47
+ `close()` when finished to release the parser and compiled query. Closing twice
48
+ is harmless; `spans()` and `ansi()` throw after closure. The initialized language
49
+ and WASM runtime are not unloaded.
50
+
51
+ When setup or capture fails and releasing its resources also fails, an
52
+ `AggregateError` retains the original error as its cause and first entry and
53
+ the cleanup error as its second entry. Multiple owned-resource cleanup failures
54
+ are retained in parser, then query order. Single errors propagate unchanged;
55
+ every resource release is attempted, and `close()` never retries failed cleanup.
56
+
57
+ `highlighter()` returns the shared `Highlighter` interface without `close()`;
58
+ its parser and query remain available for the process lifetime. The website's
59
+ cached browser highlighter likewise lives for the page lifetime. Components
60
+ borrowing that cache must not close it on unmount.
@@ -1,5 +1,5 @@
1
1
  /** Browser entry point for CAVE's Tree-sitter highlighter. */
2
- import { type Highlighter } from './core.ts';
2
+ import { type OwnedHighlighter } from './core.ts';
3
3
  export * from './core.ts';
4
4
  export type BrowserHighlighterOptions = {
5
5
  /** URL emitted for `web-tree-sitter/web-tree-sitter.wasm`. */
@@ -10,5 +10,5 @@ export type BrowserHighlighterOptions = {
10
10
  readonly querySource: string;
11
11
  };
12
12
  /** Loads the parser and CAVE grammar WASM, then creates a browser highlighter. */
13
- export declare const createBrowserHighlighter: ({ parserWasmUrl, languageWasmUrl, querySource, }: BrowserHighlighterOptions) => Promise<Highlighter>;
13
+ export declare const createBrowserHighlighter: ({ parserWasmUrl, languageWasmUrl, querySource, }: BrowserHighlighterOptions) => Promise<OwnedHighlighter>;
14
14
  //# sourceMappingURL=browser.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAG9D,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAEpE,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,gBAAgB,CAItD,CAAA"}
@@ -1 +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"}
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,EAAyB,MAAM,WAAW,CAAA;AAEpE,cAAc,WAAW,CAAA;AAWzB,kFAAkF;AAClF,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,EAAE,EAC7C,aAAa,EACb,eAAe,EACf,WAAW,GACe,EAA6B,EAAE;IACzD,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"}
@@ -18,8 +18,13 @@ export type Highlighter = {
18
18
  /** `text` with ANSI colors applied per `theme` (default {@link defaultTheme}). */
19
19
  readonly ansi: (text: string, theme?: Theme) => string;
20
20
  };
21
+ /** A factory-created highlighter whose parser and query belong to the caller. */
22
+ export type OwnedHighlighter = Highlighter & {
23
+ /** Release the parser and query once; subsequent spans/ansi calls throw. */
24
+ readonly close: () => void;
25
+ };
21
26
  /** Renders `spans` over `text` as ANSI; exported for custom span sources. */
22
27
  export declare const paint: (text: string, spans: readonly Span[], theme?: Theme) => string;
23
28
  /** Creates a highlighter from an initialized Tree-sitter language and query. */
24
- export declare const createHighlighter: (language: Language, querySource: string) => Highlighter;
29
+ export declare const createHighlighter: (language: Language, querySource: string) => OwnedHighlighter;
25
30
  //# sourceMappingURL=core.d.ts.map
@@ -1 +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"}
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,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAeD,6EAA6E;AAC7E,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,OAAO,SAAS,IAAI,EAAE,EAAE,QAAO,KAAoB,KAAG,MAgBzF,CAAA;AAeD,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,GAAI,UAAU,QAAQ,EAAE,aAAa,MAAM,KAAG,gBAoD3E,CAAA"}
package/dist/src/core.js CHANGED
@@ -21,7 +21,7 @@ export const defaultTheme = {
21
21
  /** Longest dotted prefix of `capture` present in `theme`. */
22
22
  const styleOf = (theme, capture) => {
23
23
  for (let name = capture;; name = name.slice(0, name.lastIndexOf('.'))) {
24
- const style = theme[name];
24
+ const style = Object.hasOwn(theme, name) ? theme[name] : undefined;
25
25
  if (style !== undefined) {
26
26
  return style;
27
27
  }
@@ -32,9 +32,14 @@ const styleOf = (theme, capture) => {
32
32
  };
33
33
  /** Renders `spans` over `text` as ANSI; exported for custom span sources. */
34
34
  export const paint = (text, spans, theme = defaultTheme) => {
35
+ const ranges = spans.map(({ start, end, capture }) => ({ start, end, capture }));
35
36
  let out = '';
36
37
  let at = 0;
37
- for (const span of spans) {
38
+ for (const span of ranges) {
39
+ if (!Number.isSafeInteger(span.start) || !Number.isSafeInteger(span.end) ||
40
+ span.start < at || span.end < span.start || span.end > text.length) {
41
+ throw new TypeError('Highlight spans must be ordered, non-overlapping integer ranges within the source');
42
+ }
38
43
  const style = styleOf(theme, span.capture);
39
44
  out += text.slice(at, span.start);
40
45
  const piece = text.slice(span.start, span.end);
@@ -43,17 +48,64 @@ export const paint = (text, spans, theme = defaultTheme) => {
43
48
  }
44
49
  return out + text.slice(at);
45
50
  };
51
+ /** Release an owned resource without replacing a primary failure. */
52
+ const withCleanup = (body, cleanup, message) => {
53
+ let result;
54
+ try {
55
+ result = body();
56
+ }
57
+ catch (error) {
58
+ try {
59
+ cleanup();
60
+ }
61
+ catch (cleanupError) {
62
+ throw new AggregateError([error, cleanupError], message, { cause: error });
63
+ }
64
+ throw error;
65
+ }
66
+ cleanup();
67
+ return result;
68
+ };
46
69
  /** Creates a highlighter from an initialized Tree-sitter language and query. */
47
70
  export const createHighlighter = (language, querySource) => {
48
71
  const query = new Query(language, querySource);
49
- const parser = new Parser();
50
- parser.setLanguage(language);
72
+ let parser;
73
+ const release = () => {
74
+ const errors = [];
75
+ try {
76
+ parser?.delete();
77
+ }
78
+ catch (error) {
79
+ errors.push(error);
80
+ }
81
+ try {
82
+ query.delete();
83
+ }
84
+ catch (error) {
85
+ errors.push(error);
86
+ }
87
+ if (errors.length === 1)
88
+ throw errors[0];
89
+ if (errors.length > 1) {
90
+ throw new AggregateError(errors, 'Highlighter cleanup failed', { cause: errors[0] });
91
+ }
92
+ };
93
+ try {
94
+ parser = new Parser();
95
+ parser.setLanguage(language);
96
+ }
97
+ catch (error) {
98
+ withCleanup(() => { throw error; }, release, 'Highlighter setup failed and cleanup also failed');
99
+ }
100
+ let closed = false;
51
101
  const spans = (text) => {
102
+ if (closed)
103
+ throw new Error('Highlighter is closed');
52
104
  const tree = parser.parse(text);
53
105
  if (tree === null) {
54
106
  return [];
55
107
  }
56
- try {
108
+ return withCleanup(() => {
57
109
  const all = query.captures(tree.rootNode)
58
110
  .map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
59
111
  .filter(span => span.end > span.start)
@@ -69,14 +121,17 @@ export const createHighlighter = (language, querySource) => {
69
121
  }
70
122
  }
71
123
  return disjoint;
72
- }
73
- finally {
74
- tree.delete();
75
- }
124
+ }, () => tree.delete(), 'Highlight capture failed and tree cleanup also failed');
76
125
  };
77
126
  return {
78
127
  spans,
79
- ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
128
+ ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme),
129
+ close: () => {
130
+ if (closed)
131
+ return;
132
+ closed = true;
133
+ release();
134
+ }
80
135
  };
81
136
  };
82
137
  //# sourceMappingURL=core.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,6 @@
13
13
  */
14
14
  import { type Highlighter } from './core.ts';
15
15
  export * from './core.ts';
16
- /** The process-wide highlighter; loads the grammar WASM on first call. */
16
+ /** The process-wide highlighter; failed loads may be retried by later calls. */
17
17
  export declare const highlighter: () => Promise<Highlighter>;
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
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;AAczB,gFAAgF;AAChF,eAAO,MAAM,WAAW,QAAO,OAAO,CAAC,WAAW,CAI9C,CAAA"}
package/dist/src/index.js CHANGED
@@ -20,9 +20,13 @@ const resolvePath = (specifier) => fileURLToPath(import.meta.resolve(specifier))
20
20
  const create = async () => {
21
21
  await Parser.init();
22
22
  const language = await Language.load(resolvePath('@cavelang/tree-sitter-cave/wasm'));
23
- return createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'));
23
+ const { spans, ansi } = createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'));
24
+ return { spans, ansi };
24
25
  };
25
26
  let cached;
26
- /** The process-wide highlighter; loads the grammar WASM on first call. */
27
- export const highlighter = () => cached ??= create();
27
+ /** The process-wide highlighter; failed loads may be retried by later calls. */
28
+ export const highlighter = () => cached ??= create().catch(error => {
29
+ cached = undefined;
30
+ throw error;
31
+ });
28
32
  //# sourceMappingURL=index.js.map
@@ -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,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"}
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,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,YAAY,CAAC,WAAW,CAAC,uCAAuC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;IAC/H,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACxB,CAAC,CAAA;AAED,IAAI,MAAwC,CAAA;AAE5C,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,GAAyB,EAAE,CACpD,MAAM,KAAK,MAAM,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IAChC,MAAM,GAAG,SAAS,CAAA;IAClB,MAAM,KAAK,CAAA;AACb,CAAC,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cavelang/highlight",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
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
  "keywords": [
@@ -21,7 +21,7 @@
21
21
  "directory": "packages/highlight"
22
22
  },
23
23
  "engines": {
24
- "node": "^22.18.0 || ^24.0.0 || ^26.0.0"
24
+ "node": "^24.16.0 || ^26.1.0"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
@@ -47,9 +47,9 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@types/emscripten": "^1.41.5",
51
- "web-tree-sitter": "^0.26.13",
52
- "@cavelang/tree-sitter-cave": "0.35.0"
50
+ "@types/emscripten": "^1.41.6",
51
+ "web-tree-sitter": "^0.27.0",
52
+ "@cavelang/tree-sitter-cave": "0.36.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc -b",
package/src/browser.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Browser entry point for CAVE's Tree-sitter highlighter. */
2
2
 
3
3
  import { Language, Parser } from 'web-tree-sitter'
4
- import { createHighlighter, type Highlighter } from './core.ts'
4
+ import { createHighlighter, type OwnedHighlighter } from './core.ts'
5
5
 
6
6
  export * from './core.ts'
7
7
 
@@ -19,7 +19,7 @@ export const createBrowserHighlighter = async ({
19
19
  parserWasmUrl,
20
20
  languageWasmUrl,
21
21
  querySource,
22
- }: BrowserHighlighterOptions): Promise<Highlighter> => {
22
+ }: BrowserHighlighterOptions): Promise<OwnedHighlighter> => {
23
23
  await Parser.init({ locateFile: () => parserWasmUrl })
24
24
  const language = await Language.load(languageWasmUrl)
25
25
  return createHighlighter(language, querySource)
package/src/core.ts CHANGED
@@ -37,10 +37,16 @@ export type Highlighter = {
37
37
  readonly ansi: (text: string, theme?: Theme) => string
38
38
  }
39
39
 
40
+ /** A factory-created highlighter whose parser and query belong to the caller. */
41
+ export type OwnedHighlighter = Highlighter & {
42
+ /** Release the parser and query once; subsequent spans/ansi calls throw. */
43
+ readonly close: () => void
44
+ }
45
+
40
46
  /** Longest dotted prefix of `capture` present in `theme`. */
41
47
  const styleOf = (theme: Theme, capture: string): undefined | string => {
42
48
  for (let name = capture; ; name = name.slice(0, name.lastIndexOf('.'))) {
43
- const style = theme[name]
49
+ const style = Object.hasOwn(theme, name) ? theme[name] : undefined
44
50
  if (style !== undefined) {
45
51
  return style
46
52
  }
@@ -52,9 +58,14 @@ const styleOf = (theme: Theme, capture: string): undefined | string => {
52
58
 
53
59
  /** Renders `spans` over `text` as ANSI; exported for custom span sources. */
54
60
  export const paint = (text: string, spans: readonly Span[], theme: Theme = defaultTheme): string => {
61
+ const ranges = spans.map(({ start, end, capture }) => ({ start, end, capture }))
55
62
  let out = ''
56
63
  let at = 0
57
- for (const span of spans) {
64
+ for (const span of ranges) {
65
+ if (!Number.isSafeInteger(span.start) || !Number.isSafeInteger(span.end) ||
66
+ span.start < at || span.end < span.start || span.end > text.length) {
67
+ throw new TypeError('Highlight spans must be ordered, non-overlapping integer ranges within the source')
68
+ }
58
69
  const style = styleOf(theme, span.capture)
59
70
  out += text.slice(at, span.start)
60
71
  const piece = text.slice(span.start, span.end)
@@ -64,17 +75,46 @@ export const paint = (text: string, spans: readonly Span[], theme: Theme = defau
64
75
  return out + text.slice(at)
65
76
  }
66
77
 
78
+ /** Release an owned resource without replacing a primary failure. */
79
+ const withCleanup = <T>(body: () => T, cleanup: () => void, message: string): T => {
80
+ let result: T
81
+ try { result = body() } catch (error) {
82
+ try { cleanup() } catch (cleanupError) {
83
+ throw new AggregateError([error, cleanupError], message, { cause: error })
84
+ }
85
+ throw error
86
+ }
87
+ cleanup()
88
+ return result
89
+ }
90
+
67
91
  /** Creates a highlighter from an initialized Tree-sitter language and query. */
68
- export const createHighlighter = (language: Language, querySource: string): Highlighter => {
92
+ export const createHighlighter = (language: Language, querySource: string): OwnedHighlighter => {
69
93
  const query = new Query(language, querySource)
70
- const parser = new Parser()
71
- parser.setLanguage(language)
94
+ let parser: Parser | undefined
95
+ const release = (): void => {
96
+ const errors: unknown[] = []
97
+ try { parser?.delete() } catch (error) { errors.push(error) }
98
+ try { query.delete() } catch (error) { errors.push(error) }
99
+ if (errors.length === 1) throw errors[0]
100
+ if (errors.length > 1) {
101
+ throw new AggregateError(errors, 'Highlighter cleanup failed', { cause: errors[0] })
102
+ }
103
+ }
104
+ try {
105
+ parser = new Parser()
106
+ parser.setLanguage(language)
107
+ } catch (error) {
108
+ withCleanup(() => { throw error }, release, 'Highlighter setup failed and cleanup also failed')
109
+ }
110
+ let closed = false
72
111
  const spans = (text: string): readonly Span[] => {
73
- const tree = parser.parse(text)
112
+ if (closed) throw new Error('Highlighter is closed')
113
+ const tree = parser!.parse(text)
74
114
  if (tree === null) {
75
115
  return []
76
116
  }
77
- try {
117
+ return withCleanup(() => {
78
118
  const all = query.captures(tree.rootNode)
79
119
  .map(({ name, node }) => ({ start: node.startIndex, end: node.endIndex, capture: name }))
80
120
  .filter(span => span.end > span.start)
@@ -90,12 +130,15 @@ export const createHighlighter = (language: Language, querySource: string): High
90
130
  }
91
131
  }
92
132
  return disjoint
93
- } finally {
94
- tree.delete()
95
- }
133
+ }, () => tree.delete(), 'Highlight capture failed and tree cleanup also failed')
96
134
  }
97
135
  return {
98
136
  spans,
99
- ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme)
137
+ ansi: (text, theme = defaultTheme) => paint(text, spans(text), theme),
138
+ close: () => {
139
+ if (closed) return
140
+ closed = true
141
+ release()
142
+ }
100
143
  }
101
144
  }
package/src/index.ts CHANGED
@@ -25,11 +25,15 @@ const resolvePath = (specifier: string): string =>
25
25
  const create = async (): Promise<Highlighter> => {
26
26
  await Parser.init()
27
27
  const language = await Language.load(resolvePath('@cavelang/tree-sitter-cave/wasm'))
28
- return createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'))
28
+ const { spans, ansi } = createHighlighter(language, readFileSync(resolvePath('@cavelang/tree-sitter-cave/highlights'), 'utf8'))
29
+ return { spans, ansi }
29
30
  }
30
31
 
31
32
  let cached: undefined | Promise<Highlighter>
32
33
 
33
- /** The process-wide highlighter; loads the grammar WASM on first call. */
34
+ /** The process-wide highlighter; failed loads may be retried by later calls. */
34
35
  export const highlighter = (): Promise<Highlighter> =>
35
- cached ??= create()
36
+ cached ??= create().catch(error => {
37
+ cached = undefined
38
+ throw error
39
+ })