@rcarls/rc-textarea-adapters 0.1.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 ADDED
@@ -0,0 +1,25 @@
1
+ # @rcarls/rc-textarea-adapters
2
+
3
+ Adapter factories for lezer, unified, and shiki tokenizers for
4
+ [@rcarls/rc-textarea](../rc-textarea/).
5
+
6
+ Part of the [rc-webcomponents](https://github.com/richardcarls/rc-webcomponents) library.
7
+
8
+ ## Documentation
9
+
10
+ Full API, usage examples, and keyboard/accessibility notes are on the
11
+ [rc-webcomponents docs site](https://richardcarls.github.io/rc-webcomponents/).
12
+
13
+ ## Peer dependencies
14
+
15
+ All adapters are optional. Install only the peer(s) you use:
16
+
17
+ | Adapter | Peer dependency |
18
+ | --- | --- |
19
+ | Lezer | `@lezer/common` |
20
+ | Unified | `unified` |
21
+ | Shiki | `shiki` |
22
+
23
+ ## License
24
+
25
+ MIT
@@ -0,0 +1,57 @@
1
+ function p(o, s) {
2
+ return {
3
+ update(r, i) {
4
+ const e = o.parse(r).cursor(), n = [];
5
+ do {
6
+ const t = s[e.type.name];
7
+ t && n.push({ type: "mark", from: e.from, to: e.to, ...t });
8
+ } while (e.next());
9
+ i.setDecorations(n);
10
+ }
11
+ };
12
+ }
13
+ function l(o, s) {
14
+ if (s(o), o.children)
15
+ for (const r of o.children) l(r, s);
16
+ }
17
+ function d(o, s) {
18
+ return {
19
+ update(r, i) {
20
+ const f = o.parse(r), e = [];
21
+ l(f, (n) => {
22
+ const t = s[n.type], a = n.position?.start.offset, c = n.position?.end.offset;
23
+ t && a !== void 0 && c !== void 0 && e.push({ type: "mark", from: a, to: c, ...t });
24
+ }), i.setDecorations(e);
25
+ }
26
+ };
27
+ }
28
+ function h(o, s, r) {
29
+ return {
30
+ async update(i, f) {
31
+ const e = await o.codeToTokens(i, {
32
+ lang: s,
33
+ ...r ? { theme: r } : {}
34
+ }), n = [];
35
+ let t = 0;
36
+ for (const a of e.tokens) {
37
+ for (const c of a) {
38
+ const u = c.content.length;
39
+ c.color && n.push({
40
+ type: "mark",
41
+ from: t,
42
+ to: t + u,
43
+ color: c.color
44
+ }), t += u;
45
+ }
46
+ t += 1;
47
+ }
48
+ f.setDecorations(n);
49
+ }
50
+ };
51
+ }
52
+ export {
53
+ p as createLezerPlugin,
54
+ h as createShikiPlugin,
55
+ d as createUnifiedPlugin
56
+ };
57
+ //# sourceMappingURL=rc-textarea-adapters.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-textarea-adapters.js","sources":["../src/lezer.ts","../src/unified.ts","../src/shiki.ts"],"sourcesContent":["import type { RCTextareaPlugin, MarkDecoration, DecorationInput } from '@rcarls/rc-textarea';\n\n/** Minimal structural shape of a lezer `Parser` / language's `.parser`. */\ninterface LezerParser {\n parse(input: string): {\n cursor(): LezerCursor;\n };\n}\n\ninterface LezerCursor {\n type: { name: string };\n from: number;\n to: number;\n next(): boolean;\n}\n\n/**\n * Create an `rc-textarea` plugin that uses a lezer parser (e.g. any `@codemirror/lang-*` package)\n * to produce syntax decorations.\n *\n * Lezer node offsets (`.from` / `.to`) are already absolute character offsets, identical to\n * rc-textarea's coordinate space — no conversion is needed.\n *\n * ```ts\n * import { javascript } from '@codemirror/lang-javascript';\n * import { createLezerPlugin } from '@rcarls/rc-textarea-adapters';\n *\n * editor.usePlugin(createLezerPlugin(javascript().language.parser, {\n * VariableDeclaration: { color: 'var(--color-keyword)' },\n * String: { color: 'var(--color-string)' },\n * LineComment: { italic: true, color: 'var(--color-comment)' },\n * }));\n * ```\n */\nexport function createLezerPlugin(\n parser: LezerParser,\n nodeTypeToStyle: Record<string, Omit<MarkDecoration, 'id' | 'type' | 'from' | 'to'>>,\n): RCTextareaPlugin {\n return {\n update(value, api) {\n const tree = parser.parse(value);\n const cursor = tree.cursor();\n const decorations: DecorationInput[] = [];\n do {\n const style = nodeTypeToStyle[cursor.type.name];\n if (style) {\n decorations.push({ type: 'mark', from: cursor.from, to: cursor.to, ...style });\n }\n } while (cursor.next());\n api.setDecorations(decorations);\n },\n };\n}\n","import type { RCTextareaPlugin, MarkDecoration, DecorationInput } from '@rcarls/rc-textarea';\n\n/** Minimal structural shape of a unist `Node` (shared by mdast, hast, etc.). */\ninterface UnistNode {\n type: string;\n position?: {\n start: { offset?: number };\n end: { offset?: number };\n };\n children?: UnistNode[];\n}\n\n/** Minimal structural shape of a unified `Processor`. */\ninterface UnifiedProcessor {\n parse(value: string): UnistNode;\n}\n\nfunction visit(node: UnistNode, visitor: (n: UnistNode) => void): void {\n visitor(node);\n if (node.children) {\n for (const child of node.children) visit(child, visitor);\n }\n}\n\n/**\n * Create an `rc-textarea` plugin that uses a unified processor (e.g. `remark-parse` for\n * Markdown, `rehype-parse` for HTML) to produce syntax decorations.\n *\n * Node `position.start.offset` / `position.end.offset` values are absolute character offsets,\n * identical to rc-textarea's coordinate space — no conversion is needed.\n *\n * ```ts\n * import { unified } from 'unified';\n * import remarkParse from 'remark-parse';\n * import { createUnifiedPlugin } from '@rcarls/rc-textarea-adapters';\n *\n * const processor = unified().use(remarkParse);\n *\n * editor.usePlugin(createUnifiedPlugin(processor, {\n * strong: { bold: true },\n * emphasis: { italic: true },\n * inlineCode: { className: 'md-inline-code' },\n * heading: { bold: true, color: 'var(--color-heading)' },\n * }));\n * ```\n */\nexport function createUnifiedPlugin(\n processor: UnifiedProcessor,\n nodeTypeToStyle: Record<string, Omit<MarkDecoration, 'id' | 'type' | 'from' | 'to'>>,\n): RCTextareaPlugin {\n return {\n update(value, api) {\n const tree = processor.parse(value);\n const decorations: DecorationInput[] = [];\n visit(tree, (node) => {\n const style = nodeTypeToStyle[node.type];\n const start = node.position?.start.offset;\n const end = node.position?.end.offset;\n if (style && start !== undefined && end !== undefined) {\n decorations.push({ type: 'mark', from: start, to: end, ...style });\n }\n });\n api.setDecorations(decorations);\n },\n };\n}\n","import type { RCTextareaPlugin, DecorationInput } from '@rcarls/rc-textarea';\n\ninterface ShikiToken {\n content: string;\n color?: string;\n}\n\n/** Minimal structural shape of a shiki `Highlighter`. */\ninterface ShikiHighlighter {\n codeToTokens(\n code: string,\n opts: { lang: string; theme?: string },\n ): Promise<{ tokens: ShikiToken[][] }>;\n}\n\n/**\n * Create an `rc-textarea` plugin that uses a [shiki](https://shiki.style/) `Highlighter`\n * to produce syntax decorations.\n *\n * Shiki tokens carry line-relative offsets; this adapter converts them to the absolute\n * character offsets that rc-textarea expects by tracking position across lines.\n *\n * ```ts\n * import { createHighlighter } from 'shiki';\n * import { createShikiPlugin } from '@rcarls/rc-textarea-adapters';\n *\n * const highlighter = await createHighlighter({\n * themes: ['github-dark'],\n * langs: ['typescript'],\n * });\n *\n * editor.usePlugin(createShikiPlugin(highlighter, 'typescript', 'github-dark'));\n * ```\n */\nexport function createShikiPlugin(\n highlighter: ShikiHighlighter,\n lang: string,\n theme?: string,\n): RCTextareaPlugin {\n return {\n async update(value, api) {\n const result = await highlighter.codeToTokens(value, {\n lang,\n ...(theme ? { theme } : {}),\n });\n\n const decorations: DecorationInput[] = [];\n let offset = 0;\n\n for (const line of result.tokens) {\n for (const token of line) {\n const len = token.content.length;\n if (token.color) {\n decorations.push({\n type: 'mark',\n from: offset,\n to: offset + len,\n color: token.color,\n });\n }\n offset += len;\n }\n offset += 1; // '\\n'\n }\n\n api.setDecorations(decorations);\n },\n };\n}\n"],"names":["createLezerPlugin","parser","nodeTypeToStyle","value","api","cursor","decorations","style","visit","node","visitor","child","createUnifiedPlugin","processor","tree","start","end","createShikiPlugin","highlighter","lang","theme","result","offset","line","token","len"],"mappings":"AAkCO,SAASA,EACdC,GACAC,GACkB;AAClB,SAAO;AAAA,IACL,OAAOC,GAAOC,GAAK;AAEjB,YAAMC,IADOJ,EAAO,MAAME,CAAK,EACX,OAAA,GACdG,IAAiC,CAAA;AACvC,SAAG;AACD,cAAMC,IAAQL,EAAgBG,EAAO,KAAK,IAAI;AAC9C,QAAIE,KACFD,EAAY,KAAK,EAAE,MAAM,QAAQ,MAAMD,EAAO,MAAM,IAAIA,EAAO,IAAI,GAAGE,EAAA,CAAO;AAAA,MAEjF,SAASF,EAAO,KAAA;AAChB,MAAAD,EAAI,eAAeE,CAAW;AAAA,IAChC;AAAA,EAAA;AAEJ;ACnCA,SAASE,EAAMC,GAAiBC,GAAuC;AAErE,MADAA,EAAQD,CAAI,GACRA,EAAK;AACP,eAAWE,KAASF,EAAK,SAAU,CAAAD,EAAMG,GAAOD,CAAO;AAE3D;AAwBO,SAASE,EACdC,GACAX,GACkB;AAClB,SAAO;AAAA,IACL,OAAOC,GAAOC,GAAK;AACjB,YAAMU,IAAOD,EAAU,MAAMV,CAAK,GAC5BG,IAAiC,CAAA;AACvC,MAAAE,EAAMM,GAAM,CAACL,MAAS;AACpB,cAAMF,IAAQL,EAAgBO,EAAK,IAAI,GACjCM,IAAQN,EAAK,UAAU,MAAM,QAC7BO,IAAMP,EAAK,UAAU,IAAI;AAC/B,QAAIF,KAASQ,MAAU,UAAaC,MAAQ,UAC1CV,EAAY,KAAK,EAAE,MAAM,QAAQ,MAAMS,GAAO,IAAIC,GAAK,GAAGT,GAAO;AAAA,MAErE,CAAC,GACDH,EAAI,eAAeE,CAAW;AAAA,IAChC;AAAA,EAAA;AAEJ;AC/BO,SAASW,EACdC,GACAC,GACAC,GACkB;AAClB,SAAO;AAAA,IACL,MAAM,OAAOjB,GAAOC,GAAK;AACvB,YAAMiB,IAAS,MAAMH,EAAY,aAAaf,GAAO;AAAA,QACnD,MAAAgB;AAAA,QACA,GAAIC,IAAQ,EAAE,OAAAA,MAAU,CAAA;AAAA,MAAC,CAC1B,GAEKd,IAAiC,CAAA;AACvC,UAAIgB,IAAS;AAEb,iBAAWC,KAAQF,EAAO,QAAQ;AAChC,mBAAWG,KAASD,GAAM;AACxB,gBAAME,IAAMD,EAAM,QAAQ;AAC1B,UAAIA,EAAM,SACRlB,EAAY,KAAK;AAAA,YACf,MAAM;AAAA,YACN,MAAMgB;AAAA,YACN,IAAIA,IAASG;AAAA,YACb,OAAOD,EAAM;AAAA,UAAA,CACd,GAEHF,KAAUG;AAAA,QACZ;AACA,QAAAH,KAAU;AAAA,MACZ;AAEA,MAAAlB,EAAI,eAAeE,CAAW;AAAA,IAChC;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,3 @@
1
+ export { createLezerPlugin } from './lezer.ts';
2
+ export { createUnifiedPlugin } from './unified.ts';
3
+ export { createShikiPlugin } from './shiki.ts';
@@ -0,0 +1,35 @@
1
+ import { RCTextareaPlugin, MarkDecoration } from '@rcarls/rc-textarea';
2
+ /** Minimal structural shape of a lezer `Parser` / language's `.parser`. */
3
+ interface LezerParser {
4
+ parse(input: string): {
5
+ cursor(): LezerCursor;
6
+ };
7
+ }
8
+ interface LezerCursor {
9
+ type: {
10
+ name: string;
11
+ };
12
+ from: number;
13
+ to: number;
14
+ next(): boolean;
15
+ }
16
+ /**
17
+ * Create an `rc-textarea` plugin that uses a lezer parser (e.g. any `@codemirror/lang-*` package)
18
+ * to produce syntax decorations.
19
+ *
20
+ * Lezer node offsets (`.from` / `.to`) are already absolute character offsets, identical to
21
+ * rc-textarea's coordinate space — no conversion is needed.
22
+ *
23
+ * ```ts
24
+ * import { javascript } from '@codemirror/lang-javascript';
25
+ * import { createLezerPlugin } from '@rcarls/rc-textarea-adapters';
26
+ *
27
+ * editor.usePlugin(createLezerPlugin(javascript().language.parser, {
28
+ * VariableDeclaration: { color: 'var(--color-keyword)' },
29
+ * String: { color: 'var(--color-string)' },
30
+ * LineComment: { italic: true, color: 'var(--color-comment)' },
31
+ * }));
32
+ * ```
33
+ */
34
+ export declare function createLezerPlugin(parser: LezerParser, nodeTypeToStyle: Record<string, Omit<MarkDecoration, 'id' | 'type' | 'from' | 'to'>>): RCTextareaPlugin;
35
+ export {};
@@ -0,0 +1,35 @@
1
+ import { RCTextareaPlugin } from '@rcarls/rc-textarea';
2
+ interface ShikiToken {
3
+ content: string;
4
+ color?: string;
5
+ }
6
+ /** Minimal structural shape of a shiki `Highlighter`. */
7
+ interface ShikiHighlighter {
8
+ codeToTokens(code: string, opts: {
9
+ lang: string;
10
+ theme?: string;
11
+ }): Promise<{
12
+ tokens: ShikiToken[][];
13
+ }>;
14
+ }
15
+ /**
16
+ * Create an `rc-textarea` plugin that uses a [shiki](https://shiki.style/) `Highlighter`
17
+ * to produce syntax decorations.
18
+ *
19
+ * Shiki tokens carry line-relative offsets; this adapter converts them to the absolute
20
+ * character offsets that rc-textarea expects by tracking position across lines.
21
+ *
22
+ * ```ts
23
+ * import { createHighlighter } from 'shiki';
24
+ * import { createShikiPlugin } from '@rcarls/rc-textarea-adapters';
25
+ *
26
+ * const highlighter = await createHighlighter({
27
+ * themes: ['github-dark'],
28
+ * langs: ['typescript'],
29
+ * });
30
+ *
31
+ * editor.usePlugin(createShikiPlugin(highlighter, 'typescript', 'github-dark'));
32
+ * ```
33
+ */
34
+ export declare function createShikiPlugin(highlighter: ShikiHighlighter, lang: string, theme?: string): RCTextareaPlugin;
35
+ export {};
@@ -0,0 +1,42 @@
1
+ import { RCTextareaPlugin, MarkDecoration } from '@rcarls/rc-textarea';
2
+ /** Minimal structural shape of a unist `Node` (shared by mdast, hast, etc.). */
3
+ interface UnistNode {
4
+ type: string;
5
+ position?: {
6
+ start: {
7
+ offset?: number;
8
+ };
9
+ end: {
10
+ offset?: number;
11
+ };
12
+ };
13
+ children?: UnistNode[];
14
+ }
15
+ /** Minimal structural shape of a unified `Processor`. */
16
+ interface UnifiedProcessor {
17
+ parse(value: string): UnistNode;
18
+ }
19
+ /**
20
+ * Create an `rc-textarea` plugin that uses a unified processor (e.g. `remark-parse` for
21
+ * Markdown, `rehype-parse` for HTML) to produce syntax decorations.
22
+ *
23
+ * Node `position.start.offset` / `position.end.offset` values are absolute character offsets,
24
+ * identical to rc-textarea's coordinate space — no conversion is needed.
25
+ *
26
+ * ```ts
27
+ * import { unified } from 'unified';
28
+ * import remarkParse from 'remark-parse';
29
+ * import { createUnifiedPlugin } from '@rcarls/rc-textarea-adapters';
30
+ *
31
+ * const processor = unified().use(remarkParse);
32
+ *
33
+ * editor.usePlugin(createUnifiedPlugin(processor, {
34
+ * strong: { bold: true },
35
+ * emphasis: { italic: true },
36
+ * inlineCode: { className: 'md-inline-code' },
37
+ * heading: { bold: true, color: 'var(--color-heading)' },
38
+ * }));
39
+ * ```
40
+ */
41
+ export declare function createUnifiedPlugin(processor: UnifiedProcessor, nodeTypeToStyle: Record<string, Omit<MarkDecoration, 'id' | 'type' | 'from' | 'to'>>): RCTextareaPlugin;
42
+ export {};
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@rcarls/rc-textarea-adapters",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "Adapter factories for lezer, unified, and shiki tokenizers for rc-textarea",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-textarea-adapters"
12
+ },
13
+ "homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-textarea-adapters.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-textarea-adapters/src/index.d.ts",
24
+ "default": "./dist/rc-textarea-adapters.js"
25
+ }
26
+ }
27
+ },
28
+ "types": "./dist/types/packages/rc-textarea-adapters/src/index.d.ts",
29
+ "sideEffects": false,
30
+ "scripts": {
31
+ "build": "tsc && vite build"
32
+ },
33
+ "dependencies": {
34
+ "@rcarls/rc-textarea": "workspace:^"
35
+ },
36
+ "peerDependencies": {
37
+ "@lezer/common": ">=1.0.0",
38
+ "shiki": ">=1.0.0",
39
+ "unified": ">=11.0.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@lezer/common": {
43
+ "optional": true
44
+ },
45
+ "shiki": {
46
+ "optional": true
47
+ },
48
+ "unified": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "devDependencies": {
53
+ "typescript": "~5.9.3",
54
+ "vite": "^7.1.7",
55
+ "vite-plugin-dts": "^4.5.4"
56
+ }
57
+ }