@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.
- package/LICENSE +25 -0
- package/README.md +185 -0
- package/dist/Code.svelte +146 -0
- package/dist/Code.svelte.d.ts +79 -0
- package/dist/Code.svelte.d.ts.map +1 -0
- package/dist/CodeHighlight.svelte +205 -0
- package/dist/CodeHighlight.svelte.d.ts +101 -0
- package/dist/CodeHighlight.svelte.d.ts.map +1 -0
- package/dist/code_sample.d.ts +8 -0
- package/dist/code_sample.d.ts.map +1 -0
- package/dist/code_sample.js +2 -0
- package/dist/grammar_clike.d.ts +12 -0
- package/dist/grammar_clike.d.ts.map +1 -0
- package/dist/grammar_clike.js +43 -0
- package/dist/grammar_css.d.ts +11 -0
- package/dist/grammar_css.d.ts.map +1 -0
- package/dist/grammar_css.js +70 -0
- package/dist/grammar_js.d.ts +11 -0
- package/dist/grammar_js.d.ts.map +1 -0
- package/dist/grammar_js.js +180 -0
- package/dist/grammar_json.d.ts +11 -0
- package/dist/grammar_json.d.ts.map +1 -0
- package/dist/grammar_json.js +35 -0
- package/dist/grammar_markdown.d.ts +8 -0
- package/dist/grammar_markdown.d.ts.map +1 -0
- package/dist/grammar_markdown.js +228 -0
- package/dist/grammar_markup.d.ts +31 -0
- package/dist/grammar_markup.d.ts.map +1 -0
- package/dist/grammar_markup.js +192 -0
- package/dist/grammar_svelte.d.ts +12 -0
- package/dist/grammar_svelte.d.ts.map +1 -0
- package/dist/grammar_svelte.js +150 -0
- package/dist/grammar_ts.d.ts +11 -0
- package/dist/grammar_ts.d.ts.map +1 -0
- package/dist/grammar_ts.js +95 -0
- package/dist/highlight_manager.d.ts +25 -0
- package/dist/highlight_manager.d.ts.map +1 -0
- package/dist/highlight_manager.js +139 -0
- package/dist/highlight_priorities.d.ts +3 -0
- package/dist/highlight_priorities.d.ts.map +1 -0
- package/dist/highlight_priorities.gen.d.ts +4 -0
- package/dist/highlight_priorities.gen.d.ts.map +1 -0
- package/dist/highlight_priorities.gen.js +58 -0
- package/dist/highlight_priorities.js +55 -0
- package/dist/syntax_styler.d.ts +277 -0
- package/dist/syntax_styler.d.ts.map +1 -0
- package/dist/syntax_styler.js +426 -0
- package/dist/syntax_styler_global.d.ts +3 -0
- package/dist/syntax_styler_global.d.ts.map +1 -0
- package/dist/syntax_styler_global.js +18 -0
- package/dist/syntax_token.d.ts +34 -0
- package/dist/syntax_token.d.ts.map +1 -0
- package/dist/syntax_token.js +27 -0
- package/dist/theme.css +98 -0
- package/dist/theme_highlight.css +160 -0
- package/dist/theme_variables.css +20 -0
- package/dist/tokenize_syntax.d.ts +28 -0
- package/dist/tokenize_syntax.d.ts.map +1 -0
- package/dist/tokenize_syntax.js +194 -0
- package/package.json +117 -0
- package/src/lib/code_sample.ts +10 -0
- package/src/lib/grammar_clike.ts +48 -0
- package/src/lib/grammar_css.ts +84 -0
- package/src/lib/grammar_js.ts +215 -0
- package/src/lib/grammar_json.ts +38 -0
- package/src/lib/grammar_markdown.ts +289 -0
- package/src/lib/grammar_markup.ts +225 -0
- package/src/lib/grammar_svelte.ts +165 -0
- package/src/lib/grammar_ts.ts +114 -0
- package/src/lib/highlight_manager.ts +182 -0
- package/src/lib/highlight_priorities.gen.ts +71 -0
- package/src/lib/highlight_priorities.ts +110 -0
- package/src/lib/syntax_styler.ts +583 -0
- package/src/lib/syntax_styler_global.ts +20 -0
- package/src/lib/syntax_token.ts +49 -0
- package/src/lib/tokenize_syntax.ts +270 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Ryan Atkinson <mail@ryanatkn.com> <https://ryanatkn.com/>
|
|
4
|
+
|
|
5
|
+
prism-svelte - Copyright (c) 2020 pngwn (https://github.com/pngwn)
|
|
6
|
+
|
|
7
|
+
Prism - Copyright (c) 2012 Lea Verou (https://lea.verou.me/)
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# @ryanatkn/fuz_code
|
|
2
|
+
|
|
3
|
+
[<img src="static/logo.svg" alt="a friendly pink spider facing you" align="right" width="192" height="192">](https://code.fuz.dev/)
|
|
4
|
+
|
|
5
|
+
> syntax styling utilities and components for TypeScript, Svelte, and Markdown 🎨
|
|
6
|
+
|
|
7
|
+
**[code.fuz.dev](https://code.fuz.dev/)**
|
|
8
|
+
|
|
9
|
+
fuz_code is a rework of [Prism](https://github.com/PrismJS/prism)
|
|
10
|
+
([prismjs.com](https://prismjs.com/)).
|
|
11
|
+
The main changes:
|
|
12
|
+
|
|
13
|
+
- has a minimal and explicit API to generate stylized HTML, and knows nothing about the DOM
|
|
14
|
+
- uses stateless ES modules, instead of globals with side effects and pseudo-module behaviors
|
|
15
|
+
- has various incompatible changes, so using Prism grammars requires some tweaks
|
|
16
|
+
- smaller (by 7kB minified and 3kB gzipped, ~1/3 less)
|
|
17
|
+
- written in TypeScript
|
|
18
|
+
- is a fork, see the [MIT license](https://github.com/ryanatkn/fuz_code/blob/main/LICENSE)
|
|
19
|
+
|
|
20
|
+
Like Prism, there are zero dependencies (unless you count Prism's `@types/prismjs`),
|
|
21
|
+
but there are two optional dependencies:
|
|
22
|
+
|
|
23
|
+
- fuz_code provides optional builtin [Svelte](https://svelte.dev/) support
|
|
24
|
+
with a [Svelte grammar](src/lib/grammar_svelte.ts)
|
|
25
|
+
based on [`prism-svelte`](https://github.com/pngwn/prism-svelte)
|
|
26
|
+
and a [Svelte component](src/lib/Code.svelte) for convenient usage.
|
|
27
|
+
- The [default theme](src/lib/theme.css) integrates
|
|
28
|
+
with my CSS library [Moss](https://github.com/ryanatkn/moss) for colors that adapt to the user's runtime `color-scheme` preference.
|
|
29
|
+
Non-Moss users should import [theme_variables.css](src/lib/theme_variables.css)
|
|
30
|
+
or otherwise define those variables.
|
|
31
|
+
|
|
32
|
+
Compared to [Shiki](https://github.com/shikijs/shiki),
|
|
33
|
+
this library is much lighter
|
|
34
|
+
(with its faster `shiki/engine/javascript`, 503kB minified to 16kB, 63kb gzipped to 5.6kB),
|
|
35
|
+
and [vastly faster](./benchmark/compare/results.md)
|
|
36
|
+
for runtime usage because it uses JS regexps instead of
|
|
37
|
+
the [Onigurama regexp engine](https://shiki.matsu.io/guide/regex-engines)
|
|
38
|
+
used by TextMate grammars.
|
|
39
|
+
Shiki also has 38 dependencies instead of 0.
|
|
40
|
+
However this is not a fair comparison because
|
|
41
|
+
Prism grammars are much simpler and less powerful than TextMate's,
|
|
42
|
+
and Shiki is designed mainly for buildtime usage.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm i -D @ryanatkn/fuz_code
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```svelte
|
|
51
|
+
<script lang="ts">
|
|
52
|
+
import Code from '@ryanatkn/fuz_code/Code.svelte';
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<!-- defaults to Svelte -->
|
|
56
|
+
<Code content={svelte_code} />
|
|
57
|
+
<!-- select a lang -->
|
|
58
|
+
<Code content={ts_code} lang="ts" />
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import {syntax_styler_global} from '@ryanatkn/fuz_code/syntax_styler_global.js';
|
|
63
|
+
|
|
64
|
+
// Generate HTML with syntax highlighting
|
|
65
|
+
const html = syntax_styler_global.stylize(code, 'ts');
|
|
66
|
+
|
|
67
|
+
// Get raw tokens for custom processing
|
|
68
|
+
import {tokenize_syntax} from '@ryanatkn/fuz_code/tokenize_syntax.js';
|
|
69
|
+
const tokens = tokenize_syntax(code, syntax_styler_global.get_lang('ts'));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Themes are just CSS files, so they work with any JS framework.
|
|
73
|
+
|
|
74
|
+
With SvelteKit:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// +layout.svelte
|
|
78
|
+
import '@ryanatkn/fuz_code/theme.css';
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The primary themes (currently just [one](src/lib/theme.css)) have a dependency
|
|
82
|
+
on my CSS library [Moss](https://github.com/ryanatkn/moss)
|
|
83
|
+
for [color-scheme](https://moss.ryanatkn.com/docs/themes) awareness.
|
|
84
|
+
See the [Moss docs](https://moss.ryanatkn.com/) for its usage.
|
|
85
|
+
|
|
86
|
+
If you're not using Moss, import `theme_variables.css` alongside `theme.css`:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// Without Moss:
|
|
90
|
+
import '@ryanatkn/fuz_code/theme.css';
|
|
91
|
+
import '@ryanatkn/fuz_code/theme_variables.css';
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Modules
|
|
95
|
+
|
|
96
|
+
- [@ryanatkn/fuz_code/syntax_styler_global.js](src/lib/syntax_styler_global.ts) - pre-configured instance with all grammars
|
|
97
|
+
- [@ryanatkn/fuz_code/syntax_styler.js](src/lib/syntax_styler.ts) - base class for custom grammars
|
|
98
|
+
- [@ryanatkn/fuz_code/theme.css](src/lib/theme.css) -
|
|
99
|
+
default theme that depends on [Moss](https://github.com/ryanatkn/moss)
|
|
100
|
+
- [@ryanatkn/fuz_code/theme_variables.css](src/lib/theme_variables.css) -
|
|
101
|
+
CSS variables for non-Moss users
|
|
102
|
+
- [@ryanatkn/fuz_code/Code.svelte](src/lib/Code.svelte) -
|
|
103
|
+
Svelte component for syntax highlighting with HTML generation
|
|
104
|
+
|
|
105
|
+
I encourage you to poke around [`src/lib`](src/lib) if you're interested in using fuz_code.
|
|
106
|
+
|
|
107
|
+
### Grammars
|
|
108
|
+
|
|
109
|
+
Enabled by default in `syntax_styler_global`:
|
|
110
|
+
|
|
111
|
+
- [`markup`](src/lib/grammar_markup.ts) (html, xml, etc)
|
|
112
|
+
- [`svelte`](src/lib/grammar_svelte.ts)
|
|
113
|
+
- [`markdown`](src/lib/grammar_markdown.ts)
|
|
114
|
+
- [`ts`](src/lib/grammar_ts.ts)
|
|
115
|
+
- [`css`](src/lib/grammar_css.ts)
|
|
116
|
+
- [`js`](src/lib/grammar_js.ts)
|
|
117
|
+
- [`json`](src/lib/grammar_json.ts)
|
|
118
|
+
- [`clike`](src/lib/grammar_clike.ts)
|
|
119
|
+
|
|
120
|
+
### More
|
|
121
|
+
|
|
122
|
+
Docs are a work in progress:
|
|
123
|
+
|
|
124
|
+
- this readme has basic usage instructions
|
|
125
|
+
- [CLAUDE.md](./CLAUDE.md) has more high-level docs including benchmarks
|
|
126
|
+
- [code.fuz.dev](https://code.fuz.dev/) has usage examples with the Svelte component
|
|
127
|
+
- [samples](https://code.fuz.dev/samples) on the website
|
|
128
|
+
(also see the [sample files](src/lib/samples/))
|
|
129
|
+
- [tests](src/lib/syntax_styler.test.ts)
|
|
130
|
+
|
|
131
|
+
Please open issues if you need any help.
|
|
132
|
+
|
|
133
|
+
## Experimental highlight support
|
|
134
|
+
|
|
135
|
+
For browsers that support the
|
|
136
|
+
[CSS Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API),
|
|
137
|
+
fuz_code provides an experimental component that can use native browser highlighting
|
|
138
|
+
as an alternative to HTML generation.
|
|
139
|
+
|
|
140
|
+
This feature is experimental, browser support is limited,
|
|
141
|
+
and there can be subtle differences because some CSS like bold/italics are not supported.
|
|
142
|
+
(nor are font sizes and other layout-affecting styles, in case your theme uses those)
|
|
143
|
+
The standard `Code.svelte` component
|
|
144
|
+
using HTML generation is recommended for most use cases.
|
|
145
|
+
|
|
146
|
+
```svelte
|
|
147
|
+
<script lang="ts">
|
|
148
|
+
import CodeHighlight from '@ryanatkn/fuz_code/CodeHighlight.svelte';
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<!-- auto-detect and use CSS Highlight API when available -->
|
|
152
|
+
<CodeHighlight content={code} mode="auto" />
|
|
153
|
+
<!-- force HTML mode -->
|
|
154
|
+
<CodeHighlight content={code} mode="html" />
|
|
155
|
+
<!-- force ranges mode (requires browser support) -->
|
|
156
|
+
<CodeHighlight content={code} mode="ranges" />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
When using the experimental highlight component, import the corresponding theme:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// instead of theme.css, import theme_highlight.css in +layout.svelte:
|
|
163
|
+
import '@ryanatkn/fuz_code/theme_highlight.css';
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Experimental modules:
|
|
167
|
+
|
|
168
|
+
- [@ryanatkn/fuz_code/CodeHighlight.svelte](src/lib/CodeHighlight.svelte) -
|
|
169
|
+
component supporting both HTML generation and CSS Custom Highlight API
|
|
170
|
+
- [@ryanatkn/fuz_code/highlight_manager.js](src/lib/highlight_manager.ts) -
|
|
171
|
+
manages browser [`Highlight`](https://developer.mozilla.org/en-US/docs/Web/API/Highlight)
|
|
172
|
+
and [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range) APIs
|
|
173
|
+
- [@ryanatkn/fuz_code/theme_highlight.css](src/lib/theme_highlight.css) -
|
|
174
|
+
theme with `::highlight()` pseudo-elements for CSS Custom Highlight API
|
|
175
|
+
|
|
176
|
+
## License [🐦](https://wikipedia.org/wiki/Free_and_open-source_software)
|
|
177
|
+
|
|
178
|
+
based on [Prism](https://github.com/PrismJS/prism)
|
|
179
|
+
by [Lea Verou](https://lea.verou.me/)
|
|
180
|
+
|
|
181
|
+
the [Svelte grammar](src/lib/grammar_svelte.ts)
|
|
182
|
+
is based on [`prism-svelte`](https://github.com/pngwn/prism-svelte)
|
|
183
|
+
by [@pngwn](https://github.com/pngwn)
|
|
184
|
+
|
|
185
|
+
[MIT](LICENSE)
|
package/dist/Code.svelte
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {Snippet} from 'svelte';
|
|
3
|
+
import {DEV} from 'esm-env';
|
|
4
|
+
import type {SvelteHTMLElements} from 'svelte/elements';
|
|
5
|
+
|
|
6
|
+
import {syntax_styler_global} from './syntax_styler_global.js';
|
|
7
|
+
import type {SyntaxStyler, SyntaxGrammar} from './syntax_styler.js';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
content,
|
|
11
|
+
lang = 'svelte',
|
|
12
|
+
grammar,
|
|
13
|
+
inline = false,
|
|
14
|
+
wrap = false,
|
|
15
|
+
syntax_styler = syntax_styler_global,
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}: SvelteHTMLElements['code'] & {
|
|
19
|
+
/** The source code to syntax highlight. */
|
|
20
|
+
content: string;
|
|
21
|
+
/**
|
|
22
|
+
* Language identifier (e.g., 'ts', 'css', 'html', 'json', 'svelte', 'md').
|
|
23
|
+
*
|
|
24
|
+
* **Purpose:**
|
|
25
|
+
* - When `grammar` is not provided, used to look up the grammar via `syntax_styler.get_lang(lang)`
|
|
26
|
+
* - Used for metadata: sets the `data-lang` attribute and determines `language_supported`
|
|
27
|
+
*
|
|
28
|
+
* **Special values:**
|
|
29
|
+
* - `null` - Explicitly disables syntax highlighting (content rendered as plain text)
|
|
30
|
+
* - `undefined` - Falls back to default ('svelte')
|
|
31
|
+
*
|
|
32
|
+
* **Relationship with `grammar`:**
|
|
33
|
+
* - If both `lang` and `grammar` are provided, `grammar` takes precedence for tokenization
|
|
34
|
+
* - However, `lang` is still used for the `data-lang` attribute and language detection
|
|
35
|
+
*
|
|
36
|
+
* @default 'svelte'
|
|
37
|
+
*/
|
|
38
|
+
lang?: string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Optional custom grammar object for syntax tokenization.
|
|
41
|
+
*
|
|
42
|
+
* **When to use:**
|
|
43
|
+
* - To provide a custom language definition not registered in `syntax_styler.langs`
|
|
44
|
+
* - To use a modified/extended version of an existing grammar
|
|
45
|
+
* - For one-off grammar variations without registering globally
|
|
46
|
+
*
|
|
47
|
+
* **Behavior:**
|
|
48
|
+
* - When provided, this grammar is used for tokenization instead of looking up via `lang`
|
|
49
|
+
* - Enables highlighting even if `lang` is not in the registry (useful for custom languages)
|
|
50
|
+
* - The `lang` parameter is still used for metadata (data-lang attribute)
|
|
51
|
+
* - When undefined, the grammar is automatically looked up via `syntax_styler.get_lang(lang)`
|
|
52
|
+
*
|
|
53
|
+
* @default undefined (uses grammar from `syntax_styler.langs[lang]`)
|
|
54
|
+
*/
|
|
55
|
+
grammar?: SyntaxGrammar | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Whether to render as inline code or block code.
|
|
58
|
+
* Controls display via CSS classes.
|
|
59
|
+
*
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
inline?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to wrap long lines in block code.
|
|
65
|
+
* Sets `white-space: pre-wrap` instead of `white-space: pre`.
|
|
66
|
+
*
|
|
67
|
+
* **Behavior:**
|
|
68
|
+
* - Wraps at whitespace (spaces, newlines)
|
|
69
|
+
* - Long tokens without spaces (URLs, hashes) will still scroll horizontally
|
|
70
|
+
* - Default `false` provides traditional code block behavior
|
|
71
|
+
*
|
|
72
|
+
* Only affects block code (ignored for inline mode).
|
|
73
|
+
*
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
76
|
+
wrap?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Custom SyntaxStyler instance to use for highlighting.
|
|
79
|
+
* Allows using a different styler with custom grammars or configuration.
|
|
80
|
+
*
|
|
81
|
+
* @default syntax_styler_global
|
|
82
|
+
*/
|
|
83
|
+
syntax_styler?: SyntaxStyler;
|
|
84
|
+
/**
|
|
85
|
+
* Optional snippet to customize how the highlighted markup is rendered.
|
|
86
|
+
* Receives the generated HTML string as a parameter.
|
|
87
|
+
*/
|
|
88
|
+
children?: Snippet<[markup: string]>;
|
|
89
|
+
} = $props();
|
|
90
|
+
|
|
91
|
+
const language_supported = $derived(lang !== null && !!syntax_styler.langs[lang]);
|
|
92
|
+
|
|
93
|
+
const highlighting_disabled = $derived(lang === null || (!language_supported && !grammar));
|
|
94
|
+
|
|
95
|
+
// DEV-only validation warnings
|
|
96
|
+
if (DEV) {
|
|
97
|
+
$effect(() => {
|
|
98
|
+
if (lang && !language_supported && !grammar) {
|
|
99
|
+
const langs = Object.keys(syntax_styler.langs).join(', ');
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error(
|
|
102
|
+
`[Code] Language "${lang}" is not supported and no custom grammar provided. ` +
|
|
103
|
+
`Highlighting disabled. Supported: ${langs}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Generate HTML markup for syntax highlighting
|
|
110
|
+
const html_content = $derived.by(() => {
|
|
111
|
+
if (!content || highlighting_disabled) {
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return syntax_styler.stylize(content, lang!, grammar); // ! is safe bc of the `highlighting_disabled` calculation
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// TODO do syntax styling at compile-time in the normal case, and don't import these at runtime
|
|
119
|
+
// TODO @html making me nervous
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
123
|
+
|
|
124
|
+
<code {...rest} class:inline class:wrap data-lang={lang}
|
|
125
|
+
>{#if highlighting_disabled}{content}{:else if children}{@render children(
|
|
126
|
+
html_content,
|
|
127
|
+
)}{:else}{@html html_content}{/if}</code
|
|
128
|
+
>
|
|
129
|
+
|
|
130
|
+
<style>
|
|
131
|
+
/* inline code inherits Moss defaults: pre-wrap, inline-block, baseline alignment */
|
|
132
|
+
|
|
133
|
+
code:not(.inline) {
|
|
134
|
+
/* block code: traditional no-wrap, horizontal scroll */
|
|
135
|
+
white-space: pre;
|
|
136
|
+
padding: var(--space_xs3) var(--space_xs);
|
|
137
|
+
display: block;
|
|
138
|
+
overflow: auto;
|
|
139
|
+
max-width: 100%;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
code.wrap:not(.inline) {
|
|
143
|
+
/* unset what we set above, otherwise rely on Moss base styles */
|
|
144
|
+
white-space: pre-wrap;
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { SvelteHTMLElements } from 'svelte/elements';
|
|
3
|
+
import type { SyntaxStyler, SyntaxGrammar } from './syntax_styler.js';
|
|
4
|
+
type $$ComponentProps = SvelteHTMLElements['code'] & {
|
|
5
|
+
/** The source code to syntax highlight. */
|
|
6
|
+
content: string;
|
|
7
|
+
/**
|
|
8
|
+
* Language identifier (e.g., 'ts', 'css', 'html', 'json', 'svelte', 'md').
|
|
9
|
+
*
|
|
10
|
+
* **Purpose:**
|
|
11
|
+
* - When `grammar` is not provided, used to look up the grammar via `syntax_styler.get_lang(lang)`
|
|
12
|
+
* - Used for metadata: sets the `data-lang` attribute and determines `language_supported`
|
|
13
|
+
*
|
|
14
|
+
* **Special values:**
|
|
15
|
+
* - `null` - Explicitly disables syntax highlighting (content rendered as plain text)
|
|
16
|
+
* - `undefined` - Falls back to default ('svelte')
|
|
17
|
+
*
|
|
18
|
+
* **Relationship with `grammar`:**
|
|
19
|
+
* - If both `lang` and `grammar` are provided, `grammar` takes precedence for tokenization
|
|
20
|
+
* - However, `lang` is still used for the `data-lang` attribute and language detection
|
|
21
|
+
*
|
|
22
|
+
* @default 'svelte'
|
|
23
|
+
*/
|
|
24
|
+
lang?: string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Optional custom grammar object for syntax tokenization.
|
|
27
|
+
*
|
|
28
|
+
* **When to use:**
|
|
29
|
+
* - To provide a custom language definition not registered in `syntax_styler.langs`
|
|
30
|
+
* - To use a modified/extended version of an existing grammar
|
|
31
|
+
* - For one-off grammar variations without registering globally
|
|
32
|
+
*
|
|
33
|
+
* **Behavior:**
|
|
34
|
+
* - When provided, this grammar is used for tokenization instead of looking up via `lang`
|
|
35
|
+
* - Enables highlighting even if `lang` is not in the registry (useful for custom languages)
|
|
36
|
+
* - The `lang` parameter is still used for metadata (data-lang attribute)
|
|
37
|
+
* - When undefined, the grammar is automatically looked up via `syntax_styler.get_lang(lang)`
|
|
38
|
+
*
|
|
39
|
+
* @default undefined (uses grammar from `syntax_styler.langs[lang]`)
|
|
40
|
+
*/
|
|
41
|
+
grammar?: SyntaxGrammar | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Whether to render as inline code or block code.
|
|
44
|
+
* Controls display via CSS classes.
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
inline?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether to wrap long lines in block code.
|
|
51
|
+
* Sets `white-space: pre-wrap` instead of `white-space: pre`.
|
|
52
|
+
*
|
|
53
|
+
* **Behavior:**
|
|
54
|
+
* - Wraps at whitespace (spaces, newlines)
|
|
55
|
+
* - Long tokens without spaces (URLs, hashes) will still scroll horizontally
|
|
56
|
+
* - Default `false` provides traditional code block behavior
|
|
57
|
+
*
|
|
58
|
+
* Only affects block code (ignored for inline mode).
|
|
59
|
+
*
|
|
60
|
+
* @default false
|
|
61
|
+
*/
|
|
62
|
+
wrap?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Custom SyntaxStyler instance to use for highlighting.
|
|
65
|
+
* Allows using a different styler with custom grammars or configuration.
|
|
66
|
+
*
|
|
67
|
+
* @default syntax_styler_global
|
|
68
|
+
*/
|
|
69
|
+
syntax_styler?: SyntaxStyler;
|
|
70
|
+
/**
|
|
71
|
+
* Optional snippet to customize how the highlighted markup is rendered.
|
|
72
|
+
* Receives the generated HTML string as a parameter.
|
|
73
|
+
*/
|
|
74
|
+
children?: Snippet<[markup: string]>;
|
|
75
|
+
};
|
|
76
|
+
declare const Code: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
77
|
+
type Code = ReturnType<typeof Code>;
|
|
78
|
+
export default Code;
|
|
79
|
+
//# sourceMappingURL=Code.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Code.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/Code.svelte"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAEpC,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAC,YAAY,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEnE,KAAK,gBAAgB,GAAI,kBAAkB,CAAC,MAAM,CAAC,GAAG;IACrD,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACpC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrC,CAAC;AA6DH,QAAA,MAAM,IAAI,sDAAwC,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Uses the CSS Custom Highlight API when available --
|
|
4
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API
|
|
5
|
+
*
|
|
6
|
+
* Requires importing theme_highlight.css instead of theme.css.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {onDestroy, type Snippet} from 'svelte';
|
|
10
|
+
import {DEV} from 'esm-env';
|
|
11
|
+
import type {SvelteHTMLElements} from 'svelte/elements';
|
|
12
|
+
|
|
13
|
+
import {syntax_styler_global} from './syntax_styler_global.js';
|
|
14
|
+
import type {SyntaxStyler, SyntaxGrammar} from './syntax_styler.js';
|
|
15
|
+
import {tokenize_syntax} from './tokenize_syntax.js';
|
|
16
|
+
import {
|
|
17
|
+
HighlightManager,
|
|
18
|
+
supports_css_highlight_api,
|
|
19
|
+
type HighlightMode,
|
|
20
|
+
} from './highlight_manager.js';
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
content,
|
|
24
|
+
lang = 'svelte',
|
|
25
|
+
mode = 'auto',
|
|
26
|
+
grammar,
|
|
27
|
+
inline = false,
|
|
28
|
+
wrap = false,
|
|
29
|
+
syntax_styler = syntax_styler_global,
|
|
30
|
+
children,
|
|
31
|
+
...rest
|
|
32
|
+
}: SvelteHTMLElements['code'] & {
|
|
33
|
+
/** The source code to syntax highlight. */
|
|
34
|
+
content: string;
|
|
35
|
+
/**
|
|
36
|
+
* Language identifier (e.g., 'ts', 'css', 'html', 'json', 'svelte', 'md').
|
|
37
|
+
*
|
|
38
|
+
* **Purpose:**
|
|
39
|
+
* - When `grammar` is not provided, used to look up the grammar via `syntax_styler.get_lang(lang)`
|
|
40
|
+
* - Used for metadata: sets the `data-lang` attribute and determines `language_supported`
|
|
41
|
+
*
|
|
42
|
+
* **Special values:**
|
|
43
|
+
* - `null` - Explicitly disables syntax highlighting (content rendered as plain text)
|
|
44
|
+
* - `undefined` - Falls back to default ('svelte')
|
|
45
|
+
*
|
|
46
|
+
* **Relationship with `grammar`:**
|
|
47
|
+
* - If both `lang` and `grammar` are provided, `grammar` takes precedence for tokenization
|
|
48
|
+
* - However, `lang` is still used for the `data-lang` attribute and language detection
|
|
49
|
+
*
|
|
50
|
+
* @default 'svelte'
|
|
51
|
+
*/
|
|
52
|
+
lang?: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Highlighting mode for this component.
|
|
55
|
+
*
|
|
56
|
+
* **Options:**
|
|
57
|
+
* - `'auto'` - Uses CSS Custom Highlight API if supported, falls back to HTML mode
|
|
58
|
+
* - `'ranges'` - Forces CSS Custom Highlight API (requires browser support)
|
|
59
|
+
* - `'html'` - Forces HTML generation with CSS classes
|
|
60
|
+
*
|
|
61
|
+
* **Note:** CSS Custom Highlight API has limitations and limited browser support.
|
|
62
|
+
* Requires importing `theme_highlight.css` instead of `theme.css`.
|
|
63
|
+
*
|
|
64
|
+
* @default 'auto'
|
|
65
|
+
*/
|
|
66
|
+
mode?: HighlightMode;
|
|
67
|
+
/**
|
|
68
|
+
* Optional custom grammar object for syntax tokenization.
|
|
69
|
+
*
|
|
70
|
+
* **When to use:**
|
|
71
|
+
* - To provide a custom language definition not registered in `syntax_styler.langs`
|
|
72
|
+
* - To use a modified/extended version of an existing grammar
|
|
73
|
+
* - For one-off grammar variations without registering globally
|
|
74
|
+
*
|
|
75
|
+
* **Behavior:**
|
|
76
|
+
* - When provided, this grammar is used for tokenization instead of looking up via `lang`
|
|
77
|
+
* - Enables highlighting even if `lang` is not in the registry (useful for custom languages)
|
|
78
|
+
* - The `lang` parameter is still used for metadata (data-lang attribute)
|
|
79
|
+
* - When undefined, the grammar is automatically looked up via `syntax_styler.get_lang(lang)`
|
|
80
|
+
*
|
|
81
|
+
* @default undefined (uses grammar from `syntax_styler.langs[lang]`)
|
|
82
|
+
*/
|
|
83
|
+
grammar?: SyntaxGrammar | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Whether to render as inline code or block code.
|
|
86
|
+
* Controls display via CSS classes.
|
|
87
|
+
*
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
inline?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether to wrap long lines in block code.
|
|
93
|
+
* Sets `white-space: pre-wrap` instead of `white-space: pre`.
|
|
94
|
+
*
|
|
95
|
+
* **Behavior:**
|
|
96
|
+
* - Wraps at whitespace (spaces, newlines)
|
|
97
|
+
* - Long tokens without spaces (URLs, hashes) will still scroll horizontally
|
|
98
|
+
* - Default `false` provides traditional code block behavior
|
|
99
|
+
*
|
|
100
|
+
* Only affects block code (ignored for inline mode).
|
|
101
|
+
*
|
|
102
|
+
* @default false
|
|
103
|
+
*/
|
|
104
|
+
wrap?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Custom SyntaxStyler instance to use for highlighting.
|
|
107
|
+
* Allows using a different styler with custom grammars or configuration.
|
|
108
|
+
*
|
|
109
|
+
* @default syntax_styler_global
|
|
110
|
+
*/
|
|
111
|
+
syntax_styler?: SyntaxStyler;
|
|
112
|
+
/**
|
|
113
|
+
* Optional snippet to customize how the highlighted markup is rendered.
|
|
114
|
+
* - In HTML mode: receives the generated HTML string
|
|
115
|
+
* - In range mode: receives the plain text content
|
|
116
|
+
*/
|
|
117
|
+
children?: Snippet<[markup: string]>;
|
|
118
|
+
} = $props();
|
|
119
|
+
|
|
120
|
+
let code_element: HTMLElement | undefined = $state();
|
|
121
|
+
|
|
122
|
+
const supports_ranges = supports_css_highlight_api();
|
|
123
|
+
|
|
124
|
+
const highlight_manager = supports_ranges ? new HighlightManager() : null;
|
|
125
|
+
|
|
126
|
+
const use_ranges = $derived(supports_ranges && (mode === 'ranges' || mode === 'auto'));
|
|
127
|
+
|
|
128
|
+
const language_supported = $derived(lang !== null && !!syntax_styler.langs[lang]);
|
|
129
|
+
|
|
130
|
+
const highlighting_disabled = $derived(lang === null || (!language_supported && !grammar));
|
|
131
|
+
|
|
132
|
+
// DEV-only validation warnings
|
|
133
|
+
if (DEV) {
|
|
134
|
+
$effect(() => {
|
|
135
|
+
if (lang && !language_supported && !grammar) {
|
|
136
|
+
const langs = Object.keys(syntax_styler.langs).join(', ');
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.error(
|
|
139
|
+
`[CodeHighlight] Language "${lang}" is not supported and no custom grammar provided. ` +
|
|
140
|
+
`Highlighting disabled. Supported: ${langs}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Generate HTML markup for syntax highlighting in non-range mode
|
|
147
|
+
const html_content = $derived.by(() => {
|
|
148
|
+
if (use_ranges || !content || highlighting_disabled) {
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return syntax_styler.stylize(content, lang!, grammar); // ! is safe bc of the `highlighting_disabled` calculation
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Apply highlights for range mode
|
|
156
|
+
if (highlight_manager) {
|
|
157
|
+
$effect(() => {
|
|
158
|
+
if (!code_element || !content || !use_ranges || highlighting_disabled) {
|
|
159
|
+
highlight_manager.clear_element_ranges();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Get tokens from syntax styler
|
|
164
|
+
const tokens = tokenize_syntax(content, grammar || syntax_styler.get_lang(lang!)); // ! is safe bc of the `highlighting_disabled` calculation
|
|
165
|
+
|
|
166
|
+
// Apply highlights
|
|
167
|
+
highlight_manager.highlight_from_syntax_tokens(code_element, tokens);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onDestroy(() => {
|
|
172
|
+
highlight_manager?.destroy();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// TODO do syntax styling at compile-time in the normal case, and don't import these at runtime
|
|
176
|
+
// TODO @html making me nervous
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
180
|
+
|
|
181
|
+
<code {...rest} class:inline class:wrap data-lang={lang} bind:this={code_element}
|
|
182
|
+
>{#if use_ranges && children}{@render children(
|
|
183
|
+
content,
|
|
184
|
+
)}{:else if use_ranges || highlighting_disabled}{content}{:else if children}{@render children(
|
|
185
|
+
html_content,
|
|
186
|
+
)}{:else}{@html html_content}{/if}</code
|
|
187
|
+
>
|
|
188
|
+
|
|
189
|
+
<style>
|
|
190
|
+
/* inline code inherits Moss defaults: pre-wrap, inline-block, baseline alignment */
|
|
191
|
+
|
|
192
|
+
code:not(.inline) {
|
|
193
|
+
/* block code: traditional no-wrap, horizontal scroll */
|
|
194
|
+
white-space: pre;
|
|
195
|
+
padding: var(--space_xs3) var(--space_xs);
|
|
196
|
+
display: block;
|
|
197
|
+
overflow: auto;
|
|
198
|
+
max-width: 100%;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
code.wrap:not(.inline) {
|
|
202
|
+
/* unset what we set above, otherwise rely on Moss base styles */
|
|
203
|
+
white-space: pre-wrap;
|
|
204
|
+
}
|
|
205
|
+
</style>
|