@fuzdev/fuz_code 0.37.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.
Files changed (76) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +185 -0
  3. package/dist/Code.svelte +146 -0
  4. package/dist/Code.svelte.d.ts +79 -0
  5. package/dist/Code.svelte.d.ts.map +1 -0
  6. package/dist/CodeHighlight.svelte +205 -0
  7. package/dist/CodeHighlight.svelte.d.ts +101 -0
  8. package/dist/CodeHighlight.svelte.d.ts.map +1 -0
  9. package/dist/code_sample.d.ts +8 -0
  10. package/dist/code_sample.d.ts.map +1 -0
  11. package/dist/code_sample.js +2 -0
  12. package/dist/grammar_clike.d.ts +12 -0
  13. package/dist/grammar_clike.d.ts.map +1 -0
  14. package/dist/grammar_clike.js +43 -0
  15. package/dist/grammar_css.d.ts +11 -0
  16. package/dist/grammar_css.d.ts.map +1 -0
  17. package/dist/grammar_css.js +70 -0
  18. package/dist/grammar_js.d.ts +11 -0
  19. package/dist/grammar_js.d.ts.map +1 -0
  20. package/dist/grammar_js.js +180 -0
  21. package/dist/grammar_json.d.ts +11 -0
  22. package/dist/grammar_json.d.ts.map +1 -0
  23. package/dist/grammar_json.js +35 -0
  24. package/dist/grammar_markdown.d.ts +8 -0
  25. package/dist/grammar_markdown.d.ts.map +1 -0
  26. package/dist/grammar_markdown.js +228 -0
  27. package/dist/grammar_markup.d.ts +31 -0
  28. package/dist/grammar_markup.d.ts.map +1 -0
  29. package/dist/grammar_markup.js +192 -0
  30. package/dist/grammar_svelte.d.ts +12 -0
  31. package/dist/grammar_svelte.d.ts.map +1 -0
  32. package/dist/grammar_svelte.js +150 -0
  33. package/dist/grammar_ts.d.ts +11 -0
  34. package/dist/grammar_ts.d.ts.map +1 -0
  35. package/dist/grammar_ts.js +95 -0
  36. package/dist/highlight_manager.d.ts +25 -0
  37. package/dist/highlight_manager.d.ts.map +1 -0
  38. package/dist/highlight_manager.js +139 -0
  39. package/dist/highlight_priorities.d.ts +3 -0
  40. package/dist/highlight_priorities.d.ts.map +1 -0
  41. package/dist/highlight_priorities.gen.d.ts +4 -0
  42. package/dist/highlight_priorities.gen.d.ts.map +1 -0
  43. package/dist/highlight_priorities.gen.js +58 -0
  44. package/dist/highlight_priorities.js +55 -0
  45. package/dist/syntax_styler.d.ts +277 -0
  46. package/dist/syntax_styler.d.ts.map +1 -0
  47. package/dist/syntax_styler.js +426 -0
  48. package/dist/syntax_styler_global.d.ts +3 -0
  49. package/dist/syntax_styler_global.d.ts.map +1 -0
  50. package/dist/syntax_styler_global.js +18 -0
  51. package/dist/syntax_token.d.ts +34 -0
  52. package/dist/syntax_token.d.ts.map +1 -0
  53. package/dist/syntax_token.js +27 -0
  54. package/dist/theme.css +98 -0
  55. package/dist/theme_highlight.css +160 -0
  56. package/dist/theme_variables.css +20 -0
  57. package/dist/tokenize_syntax.d.ts +28 -0
  58. package/dist/tokenize_syntax.d.ts.map +1 -0
  59. package/dist/tokenize_syntax.js +194 -0
  60. package/package.json +117 -0
  61. package/src/lib/code_sample.ts +10 -0
  62. package/src/lib/grammar_clike.ts +48 -0
  63. package/src/lib/grammar_css.ts +84 -0
  64. package/src/lib/grammar_js.ts +215 -0
  65. package/src/lib/grammar_json.ts +38 -0
  66. package/src/lib/grammar_markdown.ts +289 -0
  67. package/src/lib/grammar_markup.ts +225 -0
  68. package/src/lib/grammar_svelte.ts +165 -0
  69. package/src/lib/grammar_ts.ts +114 -0
  70. package/src/lib/highlight_manager.ts +182 -0
  71. package/src/lib/highlight_priorities.gen.ts +71 -0
  72. package/src/lib/highlight_priorities.ts +110 -0
  73. package/src/lib/syntax_styler.ts +583 -0
  74. package/src/lib/syntax_styler_global.ts +20 -0
  75. package/src/lib/syntax_token.ts +49 -0
  76. package/src/lib/tokenize_syntax.ts +270 -0
@@ -0,0 +1,225 @@
1
+ import type {
2
+ SyntaxStyler,
3
+ AddSyntaxGrammar,
4
+ SyntaxGrammarRaw,
5
+ SyntaxGrammarToken,
6
+ SyntaxGrammar,
7
+ } from './syntax_styler.js';
8
+
9
+ /**
10
+ * Based on Prism (https://github.com/PrismJS/prism)
11
+ * by Lea Verou (https://lea.verou.me/)
12
+ *
13
+ * MIT license
14
+ *
15
+ * @see LICENSE
16
+ */
17
+ export const add_grammar_markup: AddSyntaxGrammar = (syntax_styler) => {
18
+ const grammar_markup = {
19
+ comment: {
20
+ pattern: /<!--(?:(?!<!--)[\s\S])*?-->/,
21
+ greedy: true,
22
+ },
23
+ processing_instruction: {
24
+ pattern: /<\?[\s\S]+?\?>/,
25
+ greedy: true,
26
+ },
27
+ // https://www.w3.org/TR/xml/#NT-doctypedecl
28
+ doctype: {
29
+ pattern: /<!DOCTYPE[^>]*>/i,
30
+ greedy: true,
31
+ },
32
+ cdata: {
33
+ pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
34
+ greedy: true,
35
+ },
36
+ tag: {
37
+ pattern:
38
+ /<\/?(?!\d)[^\s>/=$<%]+(?:\s(?:\s*[^\s>/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,
39
+ greedy: true,
40
+ inside: {
41
+ tag: {
42
+ pattern: /^<\/?[^\s>/]+/,
43
+ inside: {
44
+ punctuation: {
45
+ pattern: /^<\/?/,
46
+ alias: 'tag_punctuation',
47
+ },
48
+ namespace: /^[^\s>/:]+:/,
49
+ },
50
+ },
51
+ special_attr: [],
52
+ attr_value: {
53
+ pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
54
+ inside: {
55
+ punctuation: [
56
+ {
57
+ pattern: /^=/,
58
+ alias: 'attr_equals',
59
+ },
60
+ {
61
+ pattern: /^(\s*)["']|["']$/,
62
+ lookbehind: true,
63
+ alias: 'attr_quote',
64
+ },
65
+ ],
66
+ entity: undefined as any, // see below
67
+ },
68
+ },
69
+ punctuation: {
70
+ pattern: /\/?>/,
71
+ alias: 'tag_punctuation',
72
+ },
73
+ attr_name: {
74
+ pattern: /[^\s>/]+/,
75
+ inside: {
76
+ namespace: /^[^\s>/:]+:/,
77
+ },
78
+ },
79
+ },
80
+ },
81
+ entity: [
82
+ {
83
+ pattern: /&[\da-z]{1,8};/i,
84
+ alias: 'named_entity',
85
+ },
86
+ /&#x?[\da-f]{1,8};/i,
87
+ ],
88
+ } satisfies SyntaxGrammarRaw;
89
+
90
+ grammar_markup.tag.inside.attr_value.inside.entity = grammar_markup.entity;
91
+
92
+ syntax_styler.add_lang('markup', grammar_markup, ['html', 'mathml', 'svg']);
93
+ syntax_styler.add_extended_lang('markup', 'xml', {}, ['ssml', 'atom', 'rss']);
94
+ };
95
+
96
+ /**
97
+ * Adds an inlined language to markup.
98
+ *
99
+ * An example of an inlined language is CSS with `<style>` tags.
100
+ *
101
+ * @param tag_name - The name of the tag that contains the inlined language. This name will be treated as
102
+ * case insensitive.
103
+ * @param lang - The language key.
104
+ */
105
+ export const grammar_markup_add_inlined = (
106
+ syntax_styler: SyntaxStyler,
107
+ tag_name: string,
108
+ lang: string,
109
+ inside_lang = 'markup',
110
+ ): void => {
111
+ const lang_key = 'lang_' + lang;
112
+
113
+ syntax_styler.grammar_insert_before(inside_lang, 'cdata', {
114
+ [tag_name]: {
115
+ pattern: RegExp(
116
+ /(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(
117
+ /__/g,
118
+ () => {
119
+ return tag_name;
120
+ },
121
+ ),
122
+ 'i',
123
+ ),
124
+ lookbehind: true,
125
+ greedy: true,
126
+ inside: {
127
+ included_cdata: {
128
+ pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i,
129
+ inside: {
130
+ cdata: /^<!\[CDATA\[|\]\]>$/i,
131
+ [lang_key]: {
132
+ pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
133
+ lookbehind: true,
134
+ inside: syntax_styler.get_lang(lang),
135
+ },
136
+ },
137
+ },
138
+ [lang_key]: {
139
+ pattern: /[\s\S]+/,
140
+ inside: syntax_styler.get_lang(lang),
141
+ },
142
+ },
143
+ },
144
+ });
145
+ };
146
+
147
+ /**
148
+ * Adds an pattern to style languages embedded in HTML attributes.
149
+ *
150
+ * An example of an inlined language is CSS with `style` attributes.
151
+ *
152
+ * @param attr_name - The name of the tag that contains the inlined language. This name will be treated as
153
+ * case insensitive.
154
+ * @param lang - The language key.
155
+ */
156
+ export const grammar_markup_add_attribute = (
157
+ syntax_styler: SyntaxStyler,
158
+ attr_name: string,
159
+ lang: string,
160
+ ): void => {
161
+ // After normalization, grammar.tag is an array of SyntaxGrammarToken
162
+ const markup_grammar = syntax_styler.get_lang('markup');
163
+ const tag_patterns = markup_grammar.tag;
164
+ const tag_inside = tag_patterns![0]!.inside!;
165
+
166
+ tag_inside.special_attr!.push({
167
+ pattern: RegExp(
168
+ /(^|["'\s])/.source +
169
+ '(?:' +
170
+ attr_name +
171
+ ')' +
172
+ /\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,
173
+ 'i',
174
+ ),
175
+ lookbehind: true,
176
+ greedy: false,
177
+ alias: [],
178
+ inside: {
179
+ attr_name: [
180
+ {
181
+ pattern: /^[^\s=]+/,
182
+ lookbehind: false,
183
+ greedy: false,
184
+ alias: [],
185
+ inside: null,
186
+ },
187
+ ],
188
+ attr_value: [
189
+ {
190
+ pattern: /=[\s\S]+/,
191
+ lookbehind: false,
192
+ greedy: false,
193
+ alias: [],
194
+ inside: {
195
+ value: [
196
+ {
197
+ pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,
198
+ lookbehind: true,
199
+ greedy: false,
200
+ alias: [lang, 'lang_' + lang], // TODO remove this alias?
201
+ inside: syntax_styler.get_lang(lang),
202
+ },
203
+ ],
204
+ punctuation: [
205
+ {
206
+ pattern: /^=/,
207
+ lookbehind: false,
208
+ greedy: false,
209
+ alias: ['attr_equals'],
210
+ inside: null,
211
+ },
212
+ {
213
+ pattern: /"|'/,
214
+ lookbehind: false,
215
+ greedy: false,
216
+ alias: ['attr_quote'],
217
+ inside: null,
218
+ },
219
+ ],
220
+ } as SyntaxGrammar,
221
+ },
222
+ ],
223
+ } as SyntaxGrammar,
224
+ } satisfies SyntaxGrammarToken);
225
+ };
@@ -0,0 +1,165 @@
1
+ import type {AddSyntaxGrammar, SyntaxStyler} from './syntax_styler.js';
2
+ import {grammar_markup_add_inlined} from './grammar_markup.js';
3
+
4
+ const blocks = '(if|else if|else|await|then|catch|each|html|debug|snippet)';
5
+
6
+ /**
7
+ * Based on `prism-svelte` (https://github.com/pngwn/prism-svelte)
8
+ * by pngwn (https://github.com/pngwn)
9
+ *
10
+ * MIT license
11
+ *
12
+ * @see LICENSE
13
+ */
14
+ export const add_grammar_svelte: AddSyntaxGrammar = (syntax_styler) => {
15
+ const grammar_ts = syntax_styler.get_lang('ts');
16
+
17
+ // Define the at_directive pattern once for reuse (matches any @word)
18
+ const at_directive_pattern = {
19
+ pattern: /^{(@\w+)(\s+[\s\S]*)?}$/,
20
+ inside: {
21
+ lang_ts: {
22
+ pattern: /(@\w+\s+)([\s\S]+)/, // Fixed: removed incorrect (?=}$)
23
+ lookbehind: true,
24
+ inside: grammar_ts,
25
+ },
26
+ at_keyword: /@\w+/,
27
+ punctuation: /[{}]/,
28
+ },
29
+ };
30
+
31
+ // Full expression patterns for top-level contexts
32
+ const svelte_expression_inside_full = {
33
+ // Generic @ directive - matches any @word
34
+ at_directive: at_directive_pattern,
35
+ // {#each items as item} with special syntax
36
+ each: {
37
+ pattern: /^{([#/]each)\s+([\s\S]*)}$/,
38
+ inside: {
39
+ special_keyword: /[#/]each/,
40
+ lang_ts: [
41
+ {
42
+ pattern: /(#each\s+)[\s\S]+(?=\s+as)/, // Expression before 'as'
43
+ lookbehind: true,
44
+ inside: grammar_ts,
45
+ },
46
+ {
47
+ pattern: /(as\s+)[\s\S]+/, // Everything after 'as' (including key)
48
+ lookbehind: true,
49
+ inside: grammar_ts,
50
+ },
51
+ ],
52
+ keyword: /as/,
53
+ punctuation: /[{}]/,
54
+ },
55
+ },
56
+ // {#block ...} and {/block}
57
+ block: {
58
+ pattern: new RegExp('^\\{([#:/@]\\s*' + blocks + ')\\s*([\\s\\S]*)\\}$'),
59
+ inside: {
60
+ special_keyword: new RegExp('[#:/@]' + blocks),
61
+ keyword: [/as/, /then/],
62
+ lang_ts: {
63
+ pattern: /[\s\S]+(?=}$)/,
64
+ inside: grammar_ts,
65
+ },
66
+ punctuation: /[{}]/,
67
+ },
68
+ },
69
+ // Default: plain TS expression
70
+ punctuation: /[{}]/,
71
+ lang_ts: {
72
+ pattern: /[\s\S]+/,
73
+ inside: grammar_ts,
74
+ },
75
+ };
76
+
77
+ // Simplified patterns for tag contexts (no block directives)
78
+ const svelte_expression_inside_simple = {
79
+ // Generic @ directive
80
+ at_directive: at_directive_pattern,
81
+ // Default: plain TS expression
82
+ punctuation: /[{}]/,
83
+ lang_ts: {
84
+ pattern: /[\s\S]+/,
85
+ inside: grammar_ts,
86
+ },
87
+ };
88
+
89
+ const grammar_svelte = syntax_styler.add_extended_lang('markup', 'svelte', {
90
+ svelte_expression: {
91
+ pattern: /\{(?:[^{}]|\{[^}]*\})*\}/,
92
+ greedy: true,
93
+ inside: svelte_expression_inside_full,
94
+ },
95
+ tag: {
96
+ pattern:
97
+ /<\/?(?!\d)[^\s>/=$<%]+(?:\s(?:\s*(?:\{(?:[^{}]|\{[^}]*\})*\}|[^\s>/=]+)(?:\s*=\s*(?:(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?:"[^"]*"|'[^']*'|{[\s\S]+?}(?=[\s/>])))|(?=[\s/>])))+)?\s*\/?>/i,
98
+ greedy: true,
99
+ inside: {
100
+ tag: {
101
+ pattern: /^<\/?[^\s>/]+/i,
102
+ inside: {
103
+ punctuation: {
104
+ pattern: /^<\/?/,
105
+ alias: 'tag_punctuation',
106
+ },
107
+ namespace: /^[^\s>/:]+:/,
108
+ },
109
+ },
110
+ svelte_expression: {
111
+ pattern: /\{(?:[^{}]|\{[^}]*\})*\}/,
112
+ inside: svelte_expression_inside_simple,
113
+ },
114
+ attr_value: {
115
+ pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,
116
+ inside: {
117
+ punctuation: [
118
+ {
119
+ pattern: /^=/,
120
+ alias: 'attr_equals',
121
+ },
122
+ {
123
+ pattern: /^(\s*)["']|["']$/,
124
+ lookbehind: true,
125
+ alias: 'attr_quote',
126
+ },
127
+ ],
128
+ svelte_expression: {
129
+ pattern: /\{(?:[^{}]|\{[^}]*\})*\}/,
130
+ inside: svelte_expression_inside_simple,
131
+ },
132
+ },
133
+ },
134
+ punctuation: {
135
+ pattern: /\/?>/,
136
+ alias: 'tag_punctuation',
137
+ },
138
+ attr_name: {
139
+ pattern: /[^\s>/]+/,
140
+ inside: {
141
+ namespace: /^[^\s>/:]+:/,
142
+ },
143
+ },
144
+ },
145
+ },
146
+ });
147
+
148
+ // oof lol
149
+ // After normalization, grammar.tag is an array of SyntaxGrammarToken
150
+ const tag_patterns = grammar_svelte.tag as any;
151
+ const tag_inside = tag_patterns[0].inside;
152
+ tag_inside.attr_value[0].inside.entity = grammar_svelte.entity;
153
+
154
+ grammar_svelte_add_inlined(syntax_styler, 'style', 'css');
155
+ // Assume TypeScript for all Svelte script tags (no plain JS)
156
+ grammar_svelte_add_inlined(syntax_styler, 'script', 'ts');
157
+ };
158
+
159
+ export const grammar_svelte_add_inlined = (
160
+ syntax_styler: SyntaxStyler,
161
+ tag_name: string,
162
+ lang: string,
163
+ ): void => {
164
+ grammar_markup_add_inlined(syntax_styler, tag_name, lang, 'svelte');
165
+ };
@@ -0,0 +1,114 @@
1
+ import type {AddSyntaxGrammar} from './syntax_styler.js';
2
+ import {class_keywords} from './grammar_clike.js';
3
+
4
+ /**
5
+ * Based on Prism (https://github.com/PrismJS/prism)
6
+ * by Lea Verou (https://lea.verou.me/)
7
+ *
8
+ * MIT license
9
+ *
10
+ * @see LICENSE
11
+ */
12
+ export const add_grammar_ts: AddSyntaxGrammar = (syntax_styler) => {
13
+ const grammar_ts = syntax_styler.add_extended_lang(
14
+ 'js',
15
+ 'ts',
16
+ {
17
+ class_name: {
18
+ pattern: new RegExp(
19
+ `(\\b(?:${class_keywords}|type)\\s+)(?!keyof\\b)(?!\\s)[_$a-zA-Z\\xA0-\\uFFFF](?:(?!\\s)[$\\w\\xA0-\\uFFFF])*(?:\\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?`,
20
+ ),
21
+ lookbehind: true,
22
+ greedy: true,
23
+ inside: null, // see below
24
+ },
25
+ builtin:
26
+ /\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/,
27
+ // TypeScript arrow functions with type annotations
28
+ function_variable: {
29
+ pattern:
30
+ /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)(?:\s*:\s*(?:(?!=>).)+)?\s*=>|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*=>)))/,
31
+ alias: 'function',
32
+ },
33
+ },
34
+ ['typescript'],
35
+ );
36
+
37
+ // The keywords TypeScript adds to JS
38
+ (grammar_ts.keyword as any).push(
39
+ /\b(?:abstract|declare|is|keyof|readonly|require|satisfies)\b/,
40
+ // keywords that have to be followed by an identifier
41
+ /\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,
42
+ // This is for `import type *, {}`
43
+ /\btype\b(?=\s*(?:[{*]|$))/,
44
+ );
45
+
46
+ // doesn't work with TS because TS is too complex
47
+ delete grammar_ts.parameter;
48
+ delete grammar_ts.literal_property;
49
+
50
+ // a version of TS specifically for styling types
51
+ var type_inside = syntax_styler.extend_grammar('ts', {
52
+ // Recognize type names in type contexts
53
+ type_name: {
54
+ pattern: /\b[A-Z]\w*/,
55
+ alias: 'class_name',
56
+ },
57
+ });
58
+ // Prevent double-wrapping of class names
59
+ (type_inside as any).class_name = undefined;
60
+
61
+ // After normalization, grammar_ts.class_name is an array
62
+ grammar_ts.class_name![0]!.inside = type_inside;
63
+
64
+ syntax_styler.grammar_insert_before('ts', 'function', {
65
+ type_assertion: {
66
+ pattern: /(\b(?:as|satisfies)\s+)(?!\s)[_$A-Za-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,
67
+ lookbehind: true,
68
+ alias: 'class_name',
69
+ },
70
+ import_type_keyword: {
71
+ pattern: /(\b(?:import|export)\s+)type\b|(\b(?:import|export)\s*\{[^}]*,\s*)type\b/,
72
+ lookbehind: true,
73
+ alias: 'special_keyword',
74
+ },
75
+ type_annotation: {
76
+ pattern: /:(?:\s*)((?:[^<>=;,)}\s]|<[^>]*>|\[[^\]]*\]|\s)+)(?=\s*=)/,
77
+ greedy: true,
78
+ inside: {
79
+ ':': /^:/,
80
+ type: {
81
+ pattern: /.+/,
82
+ inside: type_inside,
83
+ },
84
+ },
85
+ },
86
+ decorator: {
87
+ pattern: /@[$\w\xA0-\uFFFF]+/,
88
+ inside: {
89
+ at: {
90
+ pattern: /^@/,
91
+ alias: 'operator',
92
+ },
93
+ function: {
94
+ pattern: /^[\s\S]+/,
95
+ alias: 'decorator_name',
96
+ },
97
+ },
98
+ },
99
+ generic_function: {
100
+ // e.g. foo<T extends "bar" | "baz">( ...
101
+ pattern:
102
+ /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,
103
+ greedy: true,
104
+ inside: {
105
+ function: /^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,
106
+ generic: {
107
+ pattern: /<[\s\S]+/, // everything after the first <
108
+ alias: 'class_name',
109
+ inside: type_inside,
110
+ },
111
+ },
112
+ },
113
+ });
114
+ };
@@ -0,0 +1,182 @@
1
+ import type {SyntaxTokenStream} from './syntax_token.js';
2
+ import {highlight_priorities} from './highlight_priorities.js';
3
+
4
+ export type HighlightMode = 'auto' | 'ranges' | 'html';
5
+
6
+ /**
7
+ * Check for CSS Highlights API support.
8
+ */
9
+ export const supports_css_highlight_api = (): boolean =>
10
+ !!(globalThis.CSS?.highlights && globalThis.Highlight); // eslint-disable-line @typescript-eslint/no-unnecessary-condition
11
+
12
+ /**
13
+ * Manages highlights for a single element.
14
+ * Tracks ranges per element and only removes its own ranges when clearing.
15
+ */
16
+ export class HighlightManager {
17
+ element_ranges: Map<string, Array<Range>>;
18
+
19
+ constructor() {
20
+ if (!supports_css_highlight_api()) {
21
+ throw Error('CSS Highlights API not supported');
22
+ }
23
+ this.element_ranges = new Map();
24
+ }
25
+
26
+ /**
27
+ * Highlight from syntax styler token stream.
28
+ */
29
+ highlight_from_syntax_tokens(element: Element, tokens: SyntaxTokenStream): void {
30
+ // Find the text node (it might not be firstChild due to Svelte comment nodes)
31
+ let text_node: Node | null = null;
32
+ for (const node of element.childNodes) {
33
+ if (node.nodeType === Node.TEXT_NODE) {
34
+ text_node = node;
35
+ break;
36
+ }
37
+ }
38
+
39
+ if (!text_node) {
40
+ throw new Error('no text node to highlight');
41
+ }
42
+
43
+ this.clear_element_ranges();
44
+
45
+ const ranges_by_type: Map<string, Array<Range>> = new Map();
46
+ const final_pos = this.#create_all_ranges(tokens, text_node, ranges_by_type, 0);
47
+
48
+ // Validate that token positions matched text node length
49
+ const text_length = text_node.textContent?.length ?? 0;
50
+ if (final_pos !== text_length) {
51
+ throw new Error(
52
+ `Token stream length mismatch: tokens covered ${final_pos} chars but text node has ${text_length} chars`,
53
+ );
54
+ }
55
+
56
+ // Apply highlights
57
+ for (const [type, ranges] of ranges_by_type) {
58
+ const prefixed_type = `token_${type}`;
59
+ // Track ranges for this element
60
+ this.element_ranges.set(prefixed_type, ranges);
61
+
62
+ // Get or create the shared highlight
63
+ let highlight = CSS.highlights.get(prefixed_type);
64
+ if (!highlight) {
65
+ highlight = new Highlight();
66
+ // Set priority based on CSS cascade order (higher = later in CSS = wins)
67
+ highlight.priority =
68
+ highlight_priorities[prefixed_type as keyof typeof highlight_priorities] ?? 0;
69
+ CSS.highlights.set(prefixed_type, highlight);
70
+ }
71
+
72
+ // Add all ranges to the highlight
73
+ for (const range of ranges) {
74
+ highlight.add(range);
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Clear only this element's ranges from highlights.
81
+ */
82
+ clear_element_ranges(): void {
83
+ for (const [name, ranges] of this.element_ranges) {
84
+ const highlight = CSS.highlights.get(name);
85
+ if (!highlight) {
86
+ throw new Error('Expected to find CSS highlight: ' + name);
87
+ }
88
+
89
+ for (const range of ranges) {
90
+ highlight.delete(range);
91
+ }
92
+
93
+ if (highlight.size === 0) {
94
+ CSS.highlights.delete(name);
95
+ }
96
+ }
97
+
98
+ this.element_ranges.clear();
99
+ }
100
+
101
+ destroy(): void {
102
+ this.clear_element_ranges();
103
+ }
104
+
105
+ /**
106
+ * Create ranges for all tokens in the tree.
107
+ */
108
+ #create_all_ranges(
109
+ tokens: SyntaxTokenStream,
110
+ text_node: Node,
111
+ ranges_by_type: Map<string, Array<Range>>,
112
+ offset: number,
113
+ ): number {
114
+ const text_length = text_node.textContent?.length ?? 0;
115
+ let pos = offset;
116
+
117
+ for (const token of tokens) {
118
+ if (typeof token === 'string') {
119
+ pos += token.length;
120
+ continue;
121
+ }
122
+
123
+ const length = token.length;
124
+ const end_pos = pos + length;
125
+
126
+ // Validate positions are within text node bounds before creating ranges
127
+ if (end_pos > text_length) {
128
+ throw new Error(
129
+ `Token ${token.type} extends beyond text node: position ${end_pos} > length ${text_length}`,
130
+ );
131
+ }
132
+
133
+ try {
134
+ const range = new Range();
135
+ range.setStart(text_node, pos);
136
+ range.setEnd(text_node, end_pos);
137
+
138
+ // Add range for the token type
139
+ const type = token.type;
140
+ if (!ranges_by_type.has(type)) {
141
+ ranges_by_type.set(type, []);
142
+ }
143
+ ranges_by_type.get(type)!.push(range);
144
+
145
+ // Also add range for any aliases (alias is always an array)
146
+ for (const alias of token.alias) {
147
+ if (!ranges_by_type.has(alias)) {
148
+ ranges_by_type.set(alias, []);
149
+ }
150
+ // Create a new range for each alias (ranges can't be reused)
151
+ const alias_range = new Range();
152
+ alias_range.setStart(text_node, pos);
153
+ alias_range.setEnd(text_node, end_pos);
154
+ ranges_by_type.get(alias)!.push(alias_range);
155
+ }
156
+ } catch (e) {
157
+ throw new Error(`Failed to create range for ${token.type}: ${e}`);
158
+ }
159
+
160
+ // Process nested tokens
161
+ if (Array.isArray(token.content)) {
162
+ const actual_end_pos = this.#create_all_ranges(
163
+ token.content,
164
+ text_node,
165
+ ranges_by_type,
166
+ pos,
167
+ );
168
+ // Validate that nested tokens match the parent token's claimed length
169
+ if (actual_end_pos !== end_pos) {
170
+ throw new Error(
171
+ `Token ${token.type} length mismatch: claimed ${length} chars (${pos}-${end_pos}) but nested content covered ${actual_end_pos - pos} chars (${pos}-${actual_end_pos})`,
172
+ );
173
+ }
174
+ pos = actual_end_pos;
175
+ } else {
176
+ pos = end_pos;
177
+ }
178
+ }
179
+
180
+ return pos;
181
+ }
182
+ }