@fuzdev/fuz_code 0.45.1 → 0.46.1
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 +1 -0
- package/dist/Code.svelte +2 -2
- package/dist/Code.svelte.d.ts +2 -2
- package/dist/CodeHighlight.svelte +18 -54
- package/dist/CodeHighlight.svelte.d.ts +4 -4
- package/dist/CodeHighlight.svelte.d.ts.map +1 -1
- package/dist/CodeTextarea.svelte +149 -0
- package/dist/CodeTextarea.svelte.d.ts +43 -0
- package/dist/CodeTextarea.svelte.d.ts.map +1 -0
- package/dist/grammar_markdown.js +3 -3
- package/dist/grammar_markup.d.ts +8 -7
- package/dist/grammar_markup.d.ts.map +1 -1
- package/dist/grammar_markup.js +8 -7
- package/dist/highlight_manager.d.ts +21 -7
- package/dist/highlight_manager.d.ts.map +1 -1
- package/dist/highlight_manager.js +130 -74
- package/dist/range_highlighting.svelte.d.ts +39 -0
- package/dist/range_highlighting.svelte.d.ts.map +1 -0
- package/dist/range_highlighting.svelte.js +57 -0
- package/dist/svelte_preprocess_fuz_code.d.ts +4 -4
- package/dist/svelte_preprocess_fuz_code.d.ts.map +1 -1
- package/dist/svelte_preprocess_fuz_code.js +3 -3
- package/dist/syntax_styler.d.ts +40 -32
- package/dist/syntax_styler.d.ts.map +1 -1
- package/dist/syntax_styler.js +81 -49
- package/dist/syntax_token.d.ts +4 -4
- package/dist/syntax_token.js +2 -2
- package/dist/tokenize_syntax.d.ts +2 -4
- package/dist/tokenize_syntax.d.ts.map +1 -1
- package/dist/tokenize_syntax.js +2 -4
- package/package.json +27 -29
- package/src/lib/grammar_markdown.ts +3 -3
- package/src/lib/grammar_markup.ts +8 -7
- package/src/lib/highlight_manager.ts +154 -84
- package/src/lib/range_highlighting.svelte.ts +100 -0
- package/src/lib/svelte_preprocess_fuz_code.ts +6 -6
- package/src/lib/syntax_styler.ts +98 -53
- package/src/lib/syntax_token.ts +4 -4
- package/src/lib/tokenize_syntax.ts +2 -4
|
@@ -1,13 +1,40 @@
|
|
|
1
|
+
import { DEV } from 'esm-env';
|
|
1
2
|
import { highlight_priorities } from './highlight_priorities.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
+
* Checks for CSS Highlights API support.
|
|
4
5
|
*/
|
|
5
|
-
export const supports_css_highlight_api = () => !!(globalThis.CSS?.highlights && globalThis.Highlight);
|
|
6
|
+
export const supports_css_highlight_api = () => !!(globalThis.CSS?.highlights && globalThis.Highlight);
|
|
7
|
+
const detect_range_kind = () => typeof globalThis.StaticRange === 'function' ? 'static' : 'live';
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
* Tracks ranges per element and only removes its own ranges when clearing.
|
|
9
|
+
* Finds the first text node child of `element`, or `null` if there is none.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
+
* The text node might not be `firstChild` because frameworks (e.g. Svelte) can
|
|
12
|
+
* insert comment/anchor nodes around it.
|
|
13
|
+
*/
|
|
14
|
+
const find_text_node = (element) => {
|
|
15
|
+
for (const node of element.childNodes) {
|
|
16
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
17
|
+
return node;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
const has_tokens = (tokens) => tokens.some((t) => typeof t !== 'string');
|
|
22
|
+
const push_range = (ranges_by_name, name, range) => {
|
|
23
|
+
const existing = ranges_by_name.get(name);
|
|
24
|
+
if (existing) {
|
|
25
|
+
existing.push(range);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
ranges_by_name.set(name, [range]);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Manages CSS Custom Highlight API ranges for a single element's text node.
|
|
33
|
+
* Tracks ranges per element and only removes its own ranges when clearing,
|
|
34
|
+
* cooperating with other managers that share the global `CSS.highlights` registry.
|
|
35
|
+
*
|
|
36
|
+
* **Experimental** — limited browser support. Use `Code.svelte` for production
|
|
37
|
+
* block code; this powers the experimental `CodeHighlight` and `CodeTextarea`.
|
|
11
38
|
*
|
|
12
39
|
* @example
|
|
13
40
|
* ```ts
|
|
@@ -16,65 +43,100 @@ export const supports_css_highlight_api = () => !!(globalThis.CSS?.highlights &&
|
|
|
16
43
|
* ```
|
|
17
44
|
*/
|
|
18
45
|
export class HighlightManager {
|
|
46
|
+
/**
|
|
47
|
+
* This manager's ranges, keyed by prefixed highlight name (e.g. `token_keyword`).
|
|
48
|
+
* A single range object may be shared across several names (a token type plus
|
|
49
|
+
* its aliases), since one range can belong to multiple `Highlight` sets.
|
|
50
|
+
*/
|
|
19
51
|
element_ranges;
|
|
52
|
+
#range_kind;
|
|
20
53
|
constructor() {
|
|
21
54
|
if (!supports_css_highlight_api()) {
|
|
22
55
|
throw Error('CSS Highlights API not supported');
|
|
23
56
|
}
|
|
24
57
|
this.element_ranges = new Map();
|
|
58
|
+
this.#range_kind = detect_range_kind();
|
|
25
59
|
}
|
|
26
60
|
/**
|
|
27
|
-
*
|
|
61
|
+
* Highlights `element`'s text node from a `SyntaxTokenStream` produced by
|
|
62
|
+
* `tokenize_syntax`. Clears this manager's previous ranges first.
|
|
63
|
+
*
|
|
64
|
+
* In production this never throws on a tokenizer/DOM mismatch: out-of-bounds
|
|
65
|
+
* tokens are clamped and a missing text node is a no-op. In DEV the same
|
|
66
|
+
* conditions throw loudly to surface grammar bugs.
|
|
28
67
|
*/
|
|
29
68
|
highlight_from_syntax_tokens(element, tokens) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
this.clear_element_ranges();
|
|
70
|
+
const text_node = find_text_node(element);
|
|
71
|
+
if (!text_node) {
|
|
72
|
+
if (has_tokens(tokens)) {
|
|
73
|
+
if (DEV) {
|
|
74
|
+
throw new Error('no text node to highlight');
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error('[HighlightManager] tokens present but no text node to highlight');
|
|
79
|
+
}
|
|
36
80
|
}
|
|
81
|
+
return;
|
|
37
82
|
}
|
|
38
|
-
|
|
39
|
-
|
|
83
|
+
try {
|
|
84
|
+
this.#apply(text_node, tokens);
|
|
40
85
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
86
|
+
catch (err) {
|
|
87
|
+
// some engines may reject `StaticRange` in `Highlight.add` -- fall back to
|
|
88
|
+
// live `Range` once rather than letting the throw escape into the effect
|
|
89
|
+
if (this.#range_kind === 'static') {
|
|
90
|
+
this.#range_kind = 'live';
|
|
91
|
+
this.clear_element_ranges(); // undo any partial application
|
|
92
|
+
this.#apply(text_node, tokens);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
#apply(text_node, tokens) {
|
|
100
|
+
const ranges_by_name = new Map();
|
|
101
|
+
const final_pos = this.#collect_ranges(tokens, text_node, ranges_by_name, 0);
|
|
102
|
+
if (DEV) {
|
|
103
|
+
const text_length = text_node.textContent?.length ?? 0;
|
|
104
|
+
if (final_pos !== text_length) {
|
|
105
|
+
throw new Error(`Token stream length mismatch: tokens covered ${final_pos} chars but text node has ${text_length} chars`);
|
|
106
|
+
}
|
|
48
107
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
// TODO: cross-instance coupling -- all managers share one global `Highlight`
|
|
109
|
+
// per token type, so re-highlighting one element (e.g. a textarea on every
|
|
110
|
+
// keystroke) mutates highlights that also hold ranges from every other code
|
|
111
|
+
// block on the page, forcing the browser to re-evaluate the shared set.
|
|
112
|
+
// Isolating per-instance needs unique highlight names + runtime-injected
|
|
113
|
+
// `::highlight()` CSS (≈50 token types × N instances), trading the static
|
|
114
|
+
// theme file for generated CSS. Only worth it with many concurrently-updating
|
|
115
|
+
// instances; revisit if profiling shows it.
|
|
116
|
+
for (const [name, ranges] of ranges_by_name) {
|
|
117
|
+
this.element_ranges.set(name, ranges);
|
|
118
|
+
let highlight = CSS.highlights.get(name);
|
|
56
119
|
if (!highlight) {
|
|
57
120
|
highlight = new Highlight();
|
|
58
|
-
//
|
|
59
|
-
highlight.priority =
|
|
60
|
-
|
|
61
|
-
CSS.highlights.set(prefixed_type, highlight);
|
|
121
|
+
// priority follows CSS cascade order (higher = later in CSS = wins)
|
|
122
|
+
highlight.priority = highlight_priorities[name] ?? 0;
|
|
123
|
+
CSS.highlights.set(name, highlight);
|
|
62
124
|
}
|
|
63
|
-
// Add all ranges to the highlight
|
|
64
125
|
for (const range of ranges) {
|
|
65
126
|
highlight.add(range);
|
|
66
127
|
}
|
|
67
128
|
}
|
|
68
129
|
}
|
|
69
130
|
/**
|
|
70
|
-
*
|
|
131
|
+
* Clears only this manager's ranges from the shared highlights. Defensive:
|
|
132
|
+
* a highlight may already be gone (e.g. another manager removed the last
|
|
133
|
+
* range, or HMR reset the registry), which is a valid state, not an error.
|
|
71
134
|
*/
|
|
72
135
|
clear_element_ranges() {
|
|
73
136
|
for (const [name, ranges] of this.element_ranges) {
|
|
74
137
|
const highlight = CSS.highlights.get(name);
|
|
75
|
-
if (!highlight)
|
|
76
|
-
|
|
77
|
-
}
|
|
138
|
+
if (!highlight)
|
|
139
|
+
continue;
|
|
78
140
|
for (const range of ranges) {
|
|
79
141
|
highlight.delete(range);
|
|
80
142
|
}
|
|
@@ -87,10 +149,25 @@ export class HighlightManager {
|
|
|
87
149
|
destroy() {
|
|
88
150
|
this.clear_element_ranges();
|
|
89
151
|
}
|
|
152
|
+
#make_range(text_node, start, end) {
|
|
153
|
+
if (this.#range_kind === 'static') {
|
|
154
|
+
return new StaticRange({
|
|
155
|
+
startContainer: text_node,
|
|
156
|
+
startOffset: start,
|
|
157
|
+
endContainer: text_node,
|
|
158
|
+
endOffset: end,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const range = new Range();
|
|
162
|
+
range.setStart(text_node, start);
|
|
163
|
+
range.setEnd(text_node, end);
|
|
164
|
+
return range;
|
|
165
|
+
}
|
|
90
166
|
/**
|
|
91
|
-
*
|
|
167
|
+
* Walks the token tree, collecting one range per non-empty token (shared
|
|
168
|
+
* across its type and aliases). Returns the end position covered.
|
|
92
169
|
*/
|
|
93
|
-
#
|
|
170
|
+
#collect_ranges(tokens, text_node, ranges_by_name, offset) {
|
|
94
171
|
const text_length = text_node.textContent?.length ?? 0;
|
|
95
172
|
let pos = offset;
|
|
96
173
|
for (const token of tokens) {
|
|
@@ -98,49 +175,28 @@ export class HighlightManager {
|
|
|
98
175
|
pos += token.length;
|
|
99
176
|
continue;
|
|
100
177
|
}
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
// Validate positions are within text node bounds before creating ranges
|
|
104
|
-
if (end_pos > text_length) {
|
|
178
|
+
const end_pos = pos + token.length;
|
|
179
|
+
if (DEV && end_pos > text_length) {
|
|
105
180
|
throw new Error(`Token ${token.type} extends beyond text node: position ${end_pos} > length ${text_length}`);
|
|
106
181
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
range
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
ranges_by_type.set(type, []);
|
|
115
|
-
}
|
|
116
|
-
ranges_by_type.get(type).push(range);
|
|
117
|
-
// Also add range for any aliases (alias is always an array)
|
|
182
|
+
// production-safe: clamp rather than throw on a tokenizer edge case
|
|
183
|
+
const safe_end = end_pos > text_length ? text_length : end_pos;
|
|
184
|
+
if (safe_end > pos) {
|
|
185
|
+
// one range shared across the token type and all its aliases --
|
|
186
|
+
// the same range object can belong to multiple `Highlight` sets
|
|
187
|
+
const range = this.#make_range(text_node, pos, safe_end);
|
|
188
|
+
push_range(ranges_by_name, `token_${token.type}`, range);
|
|
118
189
|
for (const alias of token.alias) {
|
|
119
|
-
|
|
120
|
-
ranges_by_type.set(alias, []);
|
|
121
|
-
}
|
|
122
|
-
// Create a new range for each alias (ranges can't be reused)
|
|
123
|
-
const alias_range = new Range();
|
|
124
|
-
alias_range.setStart(text_node, pos);
|
|
125
|
-
alias_range.setEnd(text_node, end_pos);
|
|
126
|
-
ranges_by_type.get(alias).push(alias_range);
|
|
190
|
+
push_range(ranges_by_name, `token_${alias}`, range);
|
|
127
191
|
}
|
|
128
192
|
}
|
|
129
|
-
catch (e) {
|
|
130
|
-
throw new Error(`Failed to create range for ${token.type}: ${e}`);
|
|
131
|
-
}
|
|
132
|
-
// Process nested tokens
|
|
133
193
|
if (Array.isArray(token.content)) {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
throw new Error(`Token ${token.type} length mismatch: claimed ${length} chars (${pos}-${end_pos}) but nested content covered ${actual_end_pos - pos} chars (${pos}-${actual_end_pos})`);
|
|
194
|
+
const nested_end = this.#collect_ranges(token.content, text_node, ranges_by_name, pos);
|
|
195
|
+
if (DEV && nested_end !== end_pos) {
|
|
196
|
+
throw new Error(`Token ${token.type} length mismatch: claimed ${token.length} chars (${pos}-${end_pos}) but nested content covered ${nested_end - pos} chars (${pos}-${nested_end})`);
|
|
138
197
|
}
|
|
139
|
-
pos = actual_end_pos;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
pos = end_pos;
|
|
143
198
|
}
|
|
199
|
+
pos = end_pos;
|
|
144
200
|
}
|
|
145
201
|
return pos;
|
|
146
202
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SyntaxStyler, SyntaxGrammar } from './syntax_styler.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reactive inputs for `create_range_highlighting`. All values are getters so the
|
|
4
|
+
* helper can track the consuming component's reactive state across the call
|
|
5
|
+
* boundary (the Svelte 5 getter-injection pattern).
|
|
6
|
+
*/
|
|
7
|
+
export interface RangeHighlightingOptions {
|
|
8
|
+
/** The element whose first text node receives the highlight ranges. */
|
|
9
|
+
element: () => Element | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The text to tokenize. Must match the element's text node exactly (e.g. a
|
|
12
|
+
* textarea backdrop includes its trailing newline here too).
|
|
13
|
+
*/
|
|
14
|
+
text: () => string;
|
|
15
|
+
/** Language id; `null` disables highlighting. */
|
|
16
|
+
lang: () => string | null;
|
|
17
|
+
/** Optional custom grammar; takes precedence over `lang` for tokenization. */
|
|
18
|
+
grammar: () => SyntaxGrammar | undefined;
|
|
19
|
+
/** The syntax styler whose registered grammars back `lang` lookups. */
|
|
20
|
+
syntax_styler: () => SyntaxStyler;
|
|
21
|
+
/** Extra gate — ranges are only applied when this returns true. Defaults to always-on. */
|
|
22
|
+
enabled?: () => boolean;
|
|
23
|
+
/** Component name used in DEV warnings. */
|
|
24
|
+
dev_label: string;
|
|
25
|
+
}
|
|
26
|
+
/** Reactive outputs from `create_range_highlighting`. */
|
|
27
|
+
export interface RangeHighlighting {
|
|
28
|
+
readonly highlighting_disabled: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Wires up CSS Custom Highlight API range highlighting for a single element's
|
|
32
|
+
* text node, shared by `CodeHighlight` and `CodeTextarea`. Creates a
|
|
33
|
+
* `HighlightManager`, memoizes tokenization, applies/clears ranges in an effect,
|
|
34
|
+
* emits DEV warnings for unsupported languages, and tears down on destroy.
|
|
35
|
+
*
|
|
36
|
+
* Must be called during component initialization (it uses `$effect`/`onDestroy`).
|
|
37
|
+
*/
|
|
38
|
+
export declare const create_range_highlighting: (options: RangeHighlightingOptions) => RangeHighlighting;
|
|
39
|
+
//# sourceMappingURL=range_highlighting.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"range_highlighting.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/range_highlighting.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAGpE;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC,uEAAuE;IACvE,OAAO,EAAE,MAAM,OAAO,GAAG,SAAS,CAAC;IACnC;;;OAGG;IACH,IAAI,EAAE,MAAM,MAAM,CAAC;IACnB,iDAAiD;IACjD,IAAI,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC1B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,aAAa,GAAG,SAAS,CAAC;IACzC,uEAAuE;IACvE,aAAa,EAAE,MAAM,YAAY,CAAC;IAClC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;IACxB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,wBAAwB,KAAG,iBAuD7E,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { onDestroy } from 'svelte';
|
|
2
|
+
import { DEV } from 'esm-env';
|
|
3
|
+
import { HighlightManager, supports_css_highlight_api } from './highlight_manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* Wires up CSS Custom Highlight API range highlighting for a single element's
|
|
6
|
+
* text node, shared by `CodeHighlight` and `CodeTextarea`. Creates a
|
|
7
|
+
* `HighlightManager`, memoizes tokenization, applies/clears ranges in an effect,
|
|
8
|
+
* emits DEV warnings for unsupported languages, and tears down on destroy.
|
|
9
|
+
*
|
|
10
|
+
* Must be called during component initialization (it uses `$effect`/`onDestroy`).
|
|
11
|
+
*/
|
|
12
|
+
export const create_range_highlighting = (options) => {
|
|
13
|
+
const manager = supports_css_highlight_api() ? new HighlightManager() : null;
|
|
14
|
+
const is_enabled = options.enabled ?? (() => true);
|
|
15
|
+
const language_supported = $derived(options.lang() !== null && !!options.syntax_styler().langs[options.lang()]);
|
|
16
|
+
const highlighting_disabled = $derived(options.lang() === null || (!language_supported && !options.grammar()));
|
|
17
|
+
// tokenize once per (text, grammar, lang) change -- memoized so unrelated
|
|
18
|
+
// reactivity doesn't trigger a full re-tokenization
|
|
19
|
+
const range_tokens = $derived.by(() => {
|
|
20
|
+
if (!manager || !is_enabled() || highlighting_disabled)
|
|
21
|
+
return null;
|
|
22
|
+
const text = options.text();
|
|
23
|
+
if (!text)
|
|
24
|
+
return null;
|
|
25
|
+
// route through the styler so `before_tokenize`/`after_tokenize` hooks run,
|
|
26
|
+
// matching `stylize`'s HTML path; `grammar` undefined falls back to the
|
|
27
|
+
// registered lang grammar (`! safe bc of `highlighting_disabled`)
|
|
28
|
+
return options.syntax_styler().tokenize(text, options.lang(), options.grammar());
|
|
29
|
+
});
|
|
30
|
+
if (manager) {
|
|
31
|
+
$effect(() => {
|
|
32
|
+
const element = options.element();
|
|
33
|
+
if (!element || !range_tokens) {
|
|
34
|
+
manager.clear_element_ranges();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
manager.highlight_from_syntax_tokens(element, range_tokens);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (DEV) {
|
|
41
|
+
$effect(() => {
|
|
42
|
+
// a lang was requested but we can't highlight it (unknown id, no grammar)
|
|
43
|
+
if (options.lang() && highlighting_disabled) {
|
|
44
|
+
const langs = Object.keys(options.syntax_styler().langs).join(', ');
|
|
45
|
+
// eslint-disable-next-line no-console
|
|
46
|
+
console.error(`[${options.dev_label}] Language "${options.lang()}" is not supported and no custom grammar provided. ` +
|
|
47
|
+
`Highlighting disabled. Supported: ${langs}`);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
onDestroy(() => manager?.destroy());
|
|
52
|
+
return {
|
|
53
|
+
get highlighting_disabled() {
|
|
54
|
+
return highlighting_disabled;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -6,13 +6,13 @@ import type { SyntaxStyler } from './syntax_styler.js';
|
|
|
6
6
|
export interface PreprocessFuzCodeOptions {
|
|
7
7
|
/** File patterns to exclude. */
|
|
8
8
|
exclude?: Array<string | RegExp>;
|
|
9
|
-
/** Custom
|
|
9
|
+
/** Custom `SyntaxStyler` instance. @default syntax_styler_global */
|
|
10
10
|
syntax_styler?: SyntaxStyler;
|
|
11
11
|
/** Enable in-memory caching. @default true */
|
|
12
12
|
cache?: boolean;
|
|
13
13
|
/**
|
|
14
|
-
* Import sources that resolve to the Code component.
|
|
15
|
-
* Used to verify that `<Code>` in templates actually refers to
|
|
14
|
+
* Import sources that resolve to the `Code` component.
|
|
15
|
+
* Used to verify that `<Code>` in templates actually refers to `Code.svelte`.
|
|
16
16
|
*
|
|
17
17
|
* @default ['@fuzdev/fuz_code/Code.svelte']
|
|
18
18
|
*/
|
|
@@ -27,7 +27,7 @@ export interface PreprocessFuzCodeOptions {
|
|
|
27
27
|
* Svelte preprocessor that compiles static `Code` component content at build time,
|
|
28
28
|
* replacing runtime syntax highlighting with pre-rendered HTML.
|
|
29
29
|
*
|
|
30
|
-
* @param options
|
|
30
|
+
* @param options - `PreprocessFuzCodeOptions` configuration
|
|
31
31
|
* @returns a Svelte preprocessor group
|
|
32
32
|
*
|
|
33
33
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte_preprocess_fuz_code.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_fuz_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAgBxE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,
|
|
1
|
+
{"version":3,"file":"svelte_preprocess_fuz_code.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_fuz_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAgBxE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,oEAAoE;IACpE,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,0BAA0B,GACtC,UAAS,wBAA6B,KACpC,iBA6DF,CAAC"}
|
|
@@ -9,7 +9,7 @@ import { syntax_styler_global } from './syntax_styler_global.js';
|
|
|
9
9
|
* Svelte preprocessor that compiles static `Code` component content at build time,
|
|
10
10
|
* replacing runtime syntax highlighting with pre-rendered HTML.
|
|
11
11
|
*
|
|
12
|
-
* @param options
|
|
12
|
+
* @param options - `PreprocessFuzCodeOptions` configuration
|
|
13
13
|
* @returns a Svelte preprocessor group
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
@@ -68,7 +68,7 @@ export const svelte_preprocess_fuz_code = (options = {}) => {
|
|
|
68
68
|
};
|
|
69
69
|
};
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* Attempts to highlight content, using cache if available.
|
|
72
72
|
* Returns the highlighted HTML, or `null` on error.
|
|
73
73
|
*/
|
|
74
74
|
const try_highlight = (text, lang, syntax_styler, options) => {
|
|
@@ -87,7 +87,7 @@ const try_highlight = (text, lang, syntax_styler, options) => {
|
|
|
87
87
|
return html;
|
|
88
88
|
};
|
|
89
89
|
/**
|
|
90
|
-
* Walks the AST to find Code component usages with static `content` props
|
|
90
|
+
* Walks the AST to find `Code` component usages with static `content` props
|
|
91
91
|
* and generates transformations to replace them with `dangerous_raw_html`.
|
|
92
92
|
*/
|
|
93
93
|
const find_code_usages = (ast, syntax_styler, code_names, options) => {
|
package/dist/syntax_styler.d.ts
CHANGED
|
@@ -34,15 +34,14 @@ export declare class SyntaxStyler {
|
|
|
34
34
|
* - Custom grammar: `stylize(code, 'ts', customGrammar)` - uses custom grammar but keeps 'ts' label
|
|
35
35
|
* - Extended grammar: `stylize(code, 'custom', this.extend_grammar('ts', extension))` - new language variant
|
|
36
36
|
*
|
|
37
|
-
* @param text -
|
|
38
|
-
* @param lang -
|
|
39
|
-
* -
|
|
40
|
-
* -
|
|
41
|
-
* -
|
|
42
|
-
* @param grammar -
|
|
43
|
-
* looks up the grammar via `this.get_lang(lang)
|
|
44
|
-
* or modified grammar instead of the registered one
|
|
45
|
-
*
|
|
37
|
+
* @param text - the source code to syntax highlight
|
|
38
|
+
* @param lang - language identifier (e.g., 'ts', 'css', 'html'), used for:
|
|
39
|
+
* - grammar lookup when `grammar` is undefined
|
|
40
|
+
* - hook context (`lang` field passed to hooks)
|
|
41
|
+
* - language identification in output
|
|
42
|
+
* @param grammar - optional custom `SyntaxGrammar` object; when undefined, automatically
|
|
43
|
+
* looks up the grammar via `this.get_lang(lang)`; provide this to use a custom
|
|
44
|
+
* or modified grammar instead of the registered one
|
|
46
45
|
* @returns HTML string with syntax highlighting using CSS classes (`.token_*`)
|
|
47
46
|
*
|
|
48
47
|
* @example
|
|
@@ -66,6 +65,18 @@ export declare class SyntaxStyler {
|
|
|
66
65
|
* ```
|
|
67
66
|
*/
|
|
68
67
|
stylize(text: string, lang: string, grammar?: SyntaxGrammar | undefined): string;
|
|
68
|
+
/**
|
|
69
|
+
* Tokenizes `text` into a `SyntaxTokenStream`, running the `before_tokenize`
|
|
70
|
+
* and `after_tokenize` hooks. This is the tokenization half of `stylize` — use
|
|
71
|
+
* it when you need the token stream itself (e.g. CSS Custom Highlight API range
|
|
72
|
+
* highlighting) rather than HTML.
|
|
73
|
+
*
|
|
74
|
+
* @param text - source to tokenize
|
|
75
|
+
* @param lang - language identifier; passed to the tokenize hooks
|
|
76
|
+
* @param grammar - grammar to tokenize with; defaults to `this.get_lang(lang)`
|
|
77
|
+
* @returns the resulting token stream
|
|
78
|
+
*/
|
|
79
|
+
tokenize(text: string, lang: string, grammar?: SyntaxGrammar | undefined): SyntaxTokenStream;
|
|
69
80
|
/**
|
|
70
81
|
* Inserts tokens _before_ another token in a language definition or any other grammar.
|
|
71
82
|
*
|
|
@@ -83,7 +94,7 @@ export declare class SyntaxStyler {
|
|
|
83
94
|
* };
|
|
84
95
|
* ```
|
|
85
96
|
*
|
|
86
|
-
* then the `style` token will be added (and processed) at the end. `
|
|
97
|
+
* then the `style` token will be added (and processed) at the end. `grammar_insert_before` allows you to insert tokens
|
|
87
98
|
* before existing tokens. For the CSS example above, you would use it like this:
|
|
88
99
|
*
|
|
89
100
|
* ```js
|
|
@@ -110,12 +121,12 @@ export declare class SyntaxStyler {
|
|
|
110
121
|
*
|
|
111
122
|
* ## Limitations
|
|
112
123
|
*
|
|
113
|
-
* The main problem `
|
|
124
|
+
* The main problem `grammar_insert_before` has to solve is iteration order. Since ES2015, the iteration order for object
|
|
114
125
|
* properties is guaranteed to be the insertion order (except for integer keys) but some browsers behave
|
|
115
|
-
* differently when keys are deleted and re-inserted. So `
|
|
126
|
+
* differently when keys are deleted and re-inserted. So `grammar_insert_before` can't be implemented by temporarily
|
|
116
127
|
* deleting properties which is necessary to insert at arbitrary positions.
|
|
117
128
|
*
|
|
118
|
-
* To solve this problem, `
|
|
129
|
+
* To solve this problem, `grammar_insert_before` doesn't actually insert the given tokens into the target object.
|
|
119
130
|
* Instead, it will create a new object and replace all references to the target object with the new one. This
|
|
120
131
|
* can be done without temporarily deleting properties, so the iteration order is well-defined.
|
|
121
132
|
*
|
|
@@ -130,16 +141,13 @@ export declare class SyntaxStyler {
|
|
|
130
141
|
* assert(newMarkup === syntax_styler.get_lang('markup'));
|
|
131
142
|
* ```
|
|
132
143
|
*
|
|
133
|
-
* @param inside -
|
|
134
|
-
* object to be modified
|
|
135
|
-
* @param before -
|
|
136
|
-
* @param insert -
|
|
137
|
-
* @param root -
|
|
138
|
-
* object to be modified.
|
|
139
|
-
*
|
|
140
|
-
* Defaults to `syntax_styler.langs`.
|
|
141
|
-
*
|
|
142
|
-
* @returns the new grammar object
|
|
144
|
+
* @param inside - the property of `root` (e.g. a language id in `syntax_styler.langs`) that contains the
|
|
145
|
+
* object to be modified
|
|
146
|
+
* @param before - the key to insert before
|
|
147
|
+
* @param insert - an object containing the key-value pairs to be inserted
|
|
148
|
+
* @param root - the object containing `inside`, i.e. the object that contains the
|
|
149
|
+
* object to be modified; defaults to `syntax_styler.langs`
|
|
150
|
+
* @returns the new `SyntaxGrammar` object
|
|
143
151
|
*/
|
|
144
152
|
grammar_insert_before(inside: string, before: string, insert: SyntaxGrammarRaw, root?: Record<string, any>): SyntaxGrammar;
|
|
145
153
|
/**
|
|
@@ -147,9 +155,9 @@ export declare class SyntaxStyler {
|
|
|
147
155
|
*
|
|
148
156
|
* Runs the `wrap` hook on each `SyntaxToken`.
|
|
149
157
|
*
|
|
150
|
-
* @param o -
|
|
151
|
-
* @param lang -
|
|
152
|
-
* @returns
|
|
158
|
+
* @param o - the token or `SyntaxTokenStream` to be converted
|
|
159
|
+
* @param lang - the name of current language
|
|
160
|
+
* @returns HTML representation of the token or token stream
|
|
153
161
|
*/
|
|
154
162
|
stringify_token(o: string | SyntaxToken | SyntaxTokenStream, lang: string): string;
|
|
155
163
|
/**
|
|
@@ -167,9 +175,9 @@ export declare class SyntaxStyler {
|
|
|
167
175
|
* Therefore, it is encouraged to order overwriting tokens according to the positions of the overwritten tokens.
|
|
168
176
|
* Furthermore, all non-overwriting tokens should be placed after the overwriting ones.
|
|
169
177
|
*
|
|
170
|
-
* @param base_id -
|
|
171
|
-
* @param extension -
|
|
172
|
-
* @returns the new
|
|
178
|
+
* @param base_id - the id of the language to extend, must be a key in `syntax_styler.langs`
|
|
179
|
+
* @param extension - the new tokens to append
|
|
180
|
+
* @returns the new `SyntaxGrammar`
|
|
173
181
|
*/
|
|
174
182
|
extend_grammar(base_id: string, extension: SyntaxGrammarRaw): SyntaxGrammar;
|
|
175
183
|
plugins: Record<string, any>;
|
|
@@ -221,13 +229,13 @@ export interface SyntaxGrammarTokenRaw {
|
|
|
221
229
|
*/
|
|
222
230
|
alias?: string | Array<string>;
|
|
223
231
|
/**
|
|
224
|
-
* The nested
|
|
232
|
+
* The nested `SyntaxGrammarRaw` of this token.
|
|
225
233
|
*/
|
|
226
234
|
inside?: SyntaxGrammarRaw | null;
|
|
227
235
|
}
|
|
228
236
|
/**
|
|
229
237
|
* Grammar token with all properties required.
|
|
230
|
-
* This is the normalized representation used at runtime.
|
|
238
|
+
* This is the normalized representation of `SyntaxGrammarTokenRaw` used at runtime.
|
|
231
239
|
*/
|
|
232
240
|
export interface SyntaxGrammarToken {
|
|
233
241
|
pattern: RegExp;
|
|
@@ -238,7 +246,7 @@ export interface SyntaxGrammarToken {
|
|
|
238
246
|
}
|
|
239
247
|
/**
|
|
240
248
|
* A grammar after normalization.
|
|
241
|
-
* All values are arrays of normalized
|
|
249
|
+
* All values are arrays of normalized `SyntaxGrammarToken` with consistent shapes.
|
|
242
250
|
*/
|
|
243
251
|
export type SyntaxGrammar = Record<string, Array<SyntaxGrammarToken>>;
|
|
244
252
|
export type HookBeforeTokenizeCallback = (ctx: HookBeforeTokenizeCallbackContext) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syntax_styler.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/syntax_styler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,KAAK,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAGtE,MAAM,MAAM,gBAAgB,GAAG,CAAC,aAAa,EAAE,YAAY,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"syntax_styler.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/syntax_styler.ts"],"names":[],"mappings":"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"}
|