@aardarch/aardink-web 0.5.0 → 0.6.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,86 +1,112 @@
1
- # @aardarch/aardink-web
2
-
3
- The [Aardink](https://github.com/aardarch/aardink) code editor for the browser: Compose
4
- Multiplatform compiled to WebAssembly, behind a small Monaco-shaped API. It covers syntax
5
- highlighting for Kotlin, TypeScript, JSON, TOML, XML, HTML, CSS and Markdown, plus folding,
6
- find and replace, diagnostics, undo/redo and six themes.
7
-
8
- ## Install
9
-
10
- ```sh
11
- pnpm add @aardarch/aardink-web # or: npm install @aardarch/aardink-web
12
- ```
13
-
14
- The package is plain ES modules plus two `.wasm` files and a font. It needs a bundler that
15
- understands `new URL('./file', import.meta.url)`, which Vite, webpack 5 and Rollup all do.
16
-
17
- ## Use
18
-
19
- ```ts
20
- import { createEditor, preloadAardink } from '@aardarch/aardink-web';
21
-
22
- preloadAardink(); // optional: start loading the WebAssembly module early
23
-
24
- const editor = await createEditor(document.getElementById('editor')!, (text) => save(text), {
25
- value: source,
26
- language: 'xml',
27
- theme: 'vscode-dark',
28
- wordWrap: 'on',
29
- });
30
-
31
- editor.updateOptions({ readOnly: true });
32
- editor.setDiagnostics([
33
- { line: 3, startColumn: 5, endColumn: 9, message: 'Unknown tag', severity: 'error' },
34
- ]);
35
- const stop = editor.onDidChangeCursor((line, column) => showPosition(line, column));
36
- editor.dispose();
37
- ```
38
-
39
- The editor fills its container, so give the container a size. `createEditor` is async because
40
- the first call loads the WebAssembly module (about 13 MB, 4.7 MB gzipped); every editor on the
41
- page shares that one module. See [`index.d.ts`](./index.d.ts) for the full API.
42
-
43
- ## Vite
44
-
45
- ```js
46
- // vite.config.js
47
- import { defineConfig } from 'vite';
48
-
49
- export default defineConfig({
50
- // The dev-server pre-bundler would move the package into .vite/deps and break the
51
- // new URL(..., import.meta.url) references the loader relies on.
52
- optimizeDeps: { exclude: ['@aardarch/aardink-web'] },
53
- build: { target: 'es2022', chunkSizeWarningLimit: 3500 },
54
- });
55
- ```
56
-
57
- `vite build` prints two harmless warnings about `node:fs` and `node:url` being externalised:
58
- the Kotlin loader has Node and Deno branches that never run in a browser. Serve `.wasm` with
59
- `Content-Type: application/wasm`.
60
-
61
- ## Browser support
62
-
63
- Chrome and Edge 119+, Firefox 120+, Safari 18.2+: the module needs WebAssembly garbage
64
- collection and exception handling.
65
-
66
- ## Known limitations
67
-
68
- Read these before adopting the package; the
69
- [integration guide](https://github.com/aardarch/aardink/blob/main/docs/WEB_INTEGRATION.md) has
70
- the measurements and the device checklist.
71
-
72
- - **Typing latency grows with document size, and fast typing can lose keys in large files.**
73
- Up to a few hundred lines of highlighted code it is comfortable. At 1,000 lines each keystroke
74
- keeps the editor busy for about half a second, and typing at 10 keys a second can drop around
75
- half the keys. At 5,000 lines each keystroke keeps it busy for several seconds. 0.5.x is for
76
- small files such as configuration or markup documents; a virtualised renderer that fixes this
77
- is planned for 0.6.0.
78
- - **A disposed editor is not fully released** (about 275 KB each), because Compose
79
- Multiplatform cannot yet tear down its viewport. Reuse one editor with `setValue` and
80
- `updateOptions` rather than mounting one per view.
81
- - There is no minimap, bracket-pair colouring or multi-cursor yet.
82
-
83
- ## Licence
84
-
85
- Apache-2.0. The bundled JetBrains Mono font is under the SIL Open Font License 1.1; see
86
- `JETBRAINS_MONO_OFL.txt`.
1
+ # @aardarch/aardink-web
2
+
3
+ The [Aardink](https://github.com/aardarch/aardink) code editor for the browser: Compose
4
+ Multiplatform compiled to WebAssembly, behind a small Monaco-shaped API. It highlights Kotlin,
5
+ TypeScript, JSON, TOML, XML, HTML, CSS and Markdown, and any language you register as a Monarch
6
+ grammar. It has folding, find and replace, diagnostics, undo and redo, multiple cursors and column
7
+ selection, bracket-pair colours, a minimap and sticky scroll, and six themes. It draws only the
8
+ lines on screen, so a keystroke costs the same in a 5,000-line file as in a 50-line one.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ pnpm add @aardarch/aardink-web # or: npm install @aardarch/aardink-web
14
+ pnpm add @aardarch/aardink-web@next # the latest pre-release
15
+ ```
16
+
17
+ The package is plain ES modules plus two `.wasm` files and a font. It needs a bundler that
18
+ understands `new URL('./file', import.meta.url)`, which Vite, webpack 5 and Rollup all do. Each
19
+ version is published from GitHub Actions with npm provenance.
20
+
21
+ ## Use
22
+
23
+ ```ts
24
+ import { createEditor, preloadAardink } from '@aardarch/aardink-web';
25
+
26
+ preloadAardink(); // optional: start loading the WebAssembly module and the font early
27
+
28
+ const editor = await createEditor(document.getElementById('editor')!, (text) => save(text), {
29
+ value: source,
30
+ language: 'xml',
31
+ theme: 'vscode-dark',
32
+ wordWrap: 'on',
33
+ minimap: { enabled: true },
34
+ });
35
+
36
+ editor.updateOptions({ readOnly: true });
37
+ editor.setDiagnostics([
38
+ { line: 3, startColumn: 5, endColumn: 9, message: 'Unknown tag', severity: 'error' },
39
+ ]);
40
+ const stop = editor.onDidChangeCursor((line, column) => showPosition(line, column));
41
+ // The language's own diagnostics (shown until you call setDiagnostics; null goes back to them).
42
+ const off = editor.onDidChangeDiagnostics((markers) => showProblems(markers));
43
+ const savedAt = editor.getAlternativeVersionId(); // unsaved while it differs
44
+ editor.dispose();
45
+ ```
46
+
47
+ A language of your own is a Monarch grammar registered before use, optionally extending a built-in
48
+ language's completions, diagnostics and folding, with Monaco's `defineTheme` for its colours:
49
+
50
+ ```ts
51
+ import { defineTheme, registerLanguage } from '@aardarch/aardink-web';
52
+
53
+ await registerLanguage(
54
+ { id: 'toy', extends: 'xml', grammar: { tokenizer: { root: [[/\bshout\b/, 'keyword']] } } },
55
+ { provideCompletionItems: (text, line, column) => [{ label: 'shout', kind: 'element' }] },
56
+ );
57
+ await defineTheme('toy-dark', { base: 'vs-dark', inherit: true, rules: [{ token: 'keyword', foreground: 'ff00ff' }] });
58
+ ```
59
+
60
+ The editor fills its container, so give the container a size. `createEditor` is async because
61
+ the first call loads the WebAssembly module (about 13 MB, 4.7 MB gzipped); every editor on the
62
+ page shares that one module. See [`index.d.ts`](./index.d.ts) for the full API, and the
63
+ [integration guide](https://github.com/aardarch/aardink/blob/main/docs/WEB_INTEGRATION.md) for how
64
+ Monaco's options and calls map onto it.
65
+
66
+ ## Vite
67
+
68
+ ```js
69
+ // vite.config.js
70
+ import { defineConfig } from 'vite';
71
+
72
+ export default defineConfig({
73
+ // The dev-server pre-bundler would move the package into .vite/deps and break the
74
+ // new URL(..., import.meta.url) references the loader relies on.
75
+ optimizeDeps: { exclude: ['@aardarch/aardink-web'] },
76
+ build: { target: 'es2022', chunkSizeWarningLimit: 3500 },
77
+ });
78
+ ```
79
+
80
+ `vite build` prints two harmless warnings about `node:fs` and `node:url` being externalised:
81
+ the Kotlin loader has Node and Deno branches that never run in a browser. Serve `.wasm` with
82
+ `Content-Type: application/wasm`.
83
+
84
+ ## The page
85
+
86
+ - Mount an editor where it stays: do not move its container to another place in the DOM, as a
87
+ Svelte `{#key}` block or a React portal can. Dispose it and mount a new one instead.
88
+ - On phones, `<meta name="viewport" content="width=device-width, initial-scale=1, interactive-widget=resizes-content">`
89
+ keeps the editor above the soft keyboard.
90
+
91
+ ## Browser support
92
+
93
+ Chrome and Edge 119+, Firefox 120+, Safari 18.2+: the module needs WebAssembly garbage
94
+ collection and exception handling.
95
+
96
+ ## Known limitations
97
+
98
+ Read these before adopting the package; the
99
+ [integration guide](https://github.com/aardarch/aardink/blob/main/docs/WEB_INTEGRATION.md) has
100
+ the measurements and the device checklist.
101
+
102
+ - **A disposed editor is not fully released** (about 275 KB each) until Aardink moves to Compose
103
+ Multiplatform 1.13. Reuse one editor with `setValue` and `updateOptions` rather than mounting
104
+ one per view.
105
+ - Themes are colours only: bold and italic in theme rules are ignored.
106
+ - Whitespace is not rendered (`renderWhitespace`), and a completion inserts plain text: snippet
107
+ placeholders such as `$1` are not expanded.
108
+
109
+ ## Licence
110
+
111
+ Apache-2.0. The bundled JetBrains Mono font is under the SIL Open Font License 1.1; see
112
+ `JETBRAINS_MONO_OFL.txt`.
package/index.d.ts CHANGED
@@ -29,6 +29,18 @@ export interface AardinkOptions {
29
29
  showGutter?: boolean;
30
30
  showLineNumbers?: boolean;
31
31
  showFoldMarkers?: boolean;
32
+ /** Width of an indent, in spaces. 4 by default. */
33
+ tabSize?: number;
34
+ /** Tab indents with spaces rather than a tab character. On by default. */
35
+ insertSpaces?: boolean;
36
+ /** Monaco's `renderLineHighlight`: anything but `'none'` highlights each caret's line. */
37
+ renderLineHighlight?: 'none' | 'line' | 'gutter' | 'all';
38
+ /** Colours brackets by nesting depth. On by default. */
39
+ bracketPairColorization?: { enabled?: boolean } | boolean;
40
+ /** A picture of the whole text at the side, with a slider over what is shown. Off by default. */
41
+ minimap?: { enabled?: boolean } | boolean;
42
+ /** The first lines of the blocks the top of the view is inside stay pinned at the top. Off by default. */
43
+ stickyScroll?: { enabled?: boolean } | boolean;
32
44
  }
33
45
 
34
46
  /** Monaco-marker shape: 1-based line and columns, `endColumn` exclusive. */
@@ -40,24 +52,132 @@ export interface AardinkDiagnostic {
40
52
  severity: 'error' | 'warning' | 'info';
41
53
  }
42
54
 
55
+ /** Monaco's `Selection`: from where it started to where the caret is, 1-based, with the range from start to end. */
56
+ export interface AardinkSelection {
57
+ selectionStartLineNumber: number;
58
+ selectionStartColumn: number;
59
+ positionLineNumber: number;
60
+ positionColumn: number;
61
+ readonly startLineNumber?: number;
62
+ readonly startColumn?: number;
63
+ readonly endLineNumber?: number;
64
+ readonly endColumn?: number;
65
+ }
66
+
67
+ /** What a content change was, as Monaco's `IModelContentChangedEvent` says it. */
68
+ export interface AardinkContentChange {
69
+ /** Goes up with every change. */
70
+ versionId: number;
71
+ isUndoing: boolean;
72
+ isRedoing: boolean;
73
+ /** The whole text was replaced (`setValue`). */
74
+ isFlush: boolean;
75
+ }
76
+
43
77
  export interface AardinkEditor {
44
78
  getValue(): string;
45
79
  /** Replaces the whole text and clears undo history. Fires the change listeners. */
46
80
  setValue(text: string): void;
47
81
  updateOptions(options: Partial<AardinkOptions>): void;
48
- /** Full text after every change, at most once per frame. Returns an unsubscribe function. */
49
- onDidChangeContent(listener: (text: string) => void): () => void;
82
+ /** Full text after every change, at most once per frame, and what the change was. Returns an unsubscribe function. */
83
+ onDidChangeContent(listener: (text: string, change: AardinkContentChange) => void): () => void;
50
84
  /** 1-based caret position. Returns an unsubscribe function. */
51
85
  onDidChangeCursor(listener: (line: number, column: number) => void): () => void;
52
- setDiagnostics(diagnostics: AardinkDiagnostic[]): void;
86
+ /**
87
+ * Shows these diagnostics in place of the language's own; `null` goes back to the language's
88
+ * own. Either way they move along with the text as it is edited, until the next list.
89
+ */
90
+ setDiagnostics(diagnostics: AardinkDiagnostic[] | null): void;
91
+ /**
92
+ * The language's own diagnostics each time they are collected: 500 ms after the editor appears,
93
+ * and after each pause in typing. Not called while a list from `setDiagnostics` is shown.
94
+ * Returns an unsubscribe function.
95
+ */
96
+ onDidChangeDiagnostics(listener: (diagnostics: AardinkDiagnostic[]) => void): () => void;
53
97
  revealPosition(line: number, column: number): void;
54
98
  showFind(): void;
55
99
  undo(): boolean;
56
100
  redo(): boolean;
101
+ canUndo(): boolean;
102
+ canRedo(): boolean;
103
+ /** Ends the current undo step, so the next edit starts a new one. */
104
+ pushUndoStop(): void;
105
+ /** Comes back when undo returns the text to an earlier state: keep it on save, and the text is unsaved while it differs. */
106
+ getAlternativeVersionId(): number;
107
+ /** The text the diff lane in the gutter compares with, typically what was last saved; `''` turns it off. */
108
+ setBaseline(text: string): void;
109
+ /** Formats the document with the language's formatter, as one undo step; resolves with whether anything changed. */
110
+ format(): Promise<boolean>;
111
+ focus(): void;
112
+ /** Every selection, the primary one first. */
113
+ getSelections(): AardinkSelection[];
114
+ /** Replaces the selections; the first becomes the primary one. */
115
+ setSelections(selections: AardinkSelection[]): void;
57
116
  /** Stops the editor. Safe to call twice. Remove or reuse the container yourself. */
58
117
  dispose(): void;
59
118
  }
60
119
 
120
+ /** A language highlighted by a grammar, for `registerLanguage`. */
121
+ export interface AardinkLanguageDefinition {
122
+ /** What `createEditor`'s `language` option names it by. Registering an id again replaces it. */
123
+ id: string;
124
+ /**
125
+ * A subset of Monaco's Monarch: states in `tokenizer` (`root` first), rules of
126
+ * `[regex, action, next?]`, `cases` with `@array`/`@default`/`@eos`, capture groups (each
127
+ * with a token name, `{ token, next }` or `cases` of its own), `include`, `@rematch`,
128
+ * `defaultToken`, `ignoreCase`, `tokenPostfix`. RegExp literals are fine. Lookbehind is not
129
+ * supported, and is refused.
130
+ */
131
+ grammar: object;
132
+ /** A built-in or registered language whose completions, diagnostics, hover and folding this one takes too, e.g. `'xml'`. */
133
+ extends?: string;
134
+ displayName?: string;
135
+ extensions?: string[];
136
+ /** As in Monaco's language configuration: what Ctrl/Cmd+/ comments with. */
137
+ comments?: { lineComment?: string; blockComment?: [string, string] };
138
+ }
139
+
140
+ export interface AardinkCompletionItem {
141
+ label: string;
142
+ /** What accepting it types; the label when absent. */
143
+ insertText?: string;
144
+ kind?: 'element' | 'attribute' | 'value' | 'snippet' | 'module' | 'property';
145
+ detail?: string;
146
+ documentation?: string;
147
+ }
148
+
149
+ /**
150
+ * What a registered language knows beyond its grammar. Each gets the whole text and, where it
151
+ * asks about a place, a 1-based line and column; each may answer at once or with a Promise. With
152
+ * `extends`, the built-in language's answers come after these.
153
+ */
154
+ export interface AardinkLanguageProviders {
155
+ provideCompletionItems?(text: string, line: number, column: number): AardinkCompletionItem[] | null | Promise<AardinkCompletionItem[] | null>;
156
+ provideHover?(text: string, line: number, column: number): { title?: string; contents: string } | null | Promise<{ title?: string; contents: string } | null>;
157
+ provideDiagnostics?(text: string): AardinkDiagnostic[] | Promise<AardinkDiagnostic[]>;
158
+ }
159
+
160
+ /** Adds a language for `createEditor`'s `language` option. Rejects with what is wrong with the definition. */
161
+ export function registerLanguage(definition: AardinkLanguageDefinition, providers?: AardinkLanguageProviders): Promise<void>;
162
+
163
+ /**
164
+ * Monaco's `defineTheme` data. Token rules name a grammar's tokens; `tag` also colours
165
+ * `tag.aardflex`. Themes are colours only: a rule's `fontStyle` and `background` are accepted, so
166
+ * Monaco theme data passes as it is, and ignored.
167
+ */
168
+ export interface AardinkThemeData {
169
+ base: 'vs' | 'vs-dark' | 'hc-black' | 'hc-light';
170
+ inherit?: boolean;
171
+ rules: { token: string; foreground?: string; fontStyle?: string; background?: string }[];
172
+ colors?: Record<string, string>;
173
+ }
174
+
175
+ /** Adds a theme for `createEditor`'s `theme` option: Monaco's `defineTheme` data, or a VS Code theme's JSON. */
176
+ export function registerTheme(name: string, theme: AardinkThemeData | object): Promise<void>;
177
+
178
+ /** Monaco's name for `registerTheme`. */
179
+ export const defineTheme: typeof registerTheme;
180
+
61
181
  /**
62
182
  * Mounts an editor that fills `container`. Loads the WebAssembly module on first use (see
63
183
  * `preloadAardink`). `onChange` is registered as the first content listener.
@@ -68,6 +188,9 @@ export function createEditor(
68
188
  options?: AardinkOptions,
69
189
  ): Promise<AardinkEditor>;
70
190
 
191
+ /** For debugging a grammar: the tokens of `text` in `languageId`, per line, as Monaco's `tokenize` gives them. */
192
+ export function tokenize(text: string, languageId: string): Promise<{ offset: number; type: string }[][]>;
193
+
71
194
  /** Starts loading the WebAssembly module without mounting anything. */
72
195
  export function preloadAardink(): Promise<void>;
73
196
 
package/index.js CHANGED
@@ -17,7 +17,7 @@
17
17
  // Adapts the Kotlin module's integer-handle exports (aardinkCreate, aardinkGetValue, ...) into
18
18
  // the Monaco-shaped object described in index.d.ts.
19
19
 
20
- export const version = '0.5.0';
20
+ export const version = '0.6.0-rc1';
21
21
 
22
22
  let kotlinModule;
23
23
 
@@ -35,18 +35,114 @@ function load() {
35
35
  }
36
36
 
37
37
  export function preloadAardink() {
38
- return load().then(() => undefined);
38
+ // The font too: an editor waits up to half a second for it rather than showing in another first.
39
+ return load().then((k) => k.aardinkPreloadFont());
39
40
  }
40
41
 
41
42
  let generatedIds = 0;
42
43
 
43
- /** Keeps only the fields the Kotlin side knows, translating Monaco's wordWrap 'on'/'off'. */
44
+ /** A selection with Monaco's range fields too: start is whichever end comes first. */
45
+ function withRange(s) {
46
+ const forward = s.selectionStartLineNumber < s.positionLineNumber ||
47
+ (s.selectionStartLineNumber === s.positionLineNumber && s.selectionStartColumn <= s.positionColumn);
48
+ return {
49
+ ...s,
50
+ startLineNumber: forward ? s.selectionStartLineNumber : s.positionLineNumber,
51
+ startColumn: forward ? s.selectionStartColumn : s.positionColumn,
52
+ endLineNumber: forward ? s.positionLineNumber : s.selectionStartLineNumber,
53
+ endColumn: forward ? s.positionColumn : s.selectionStartColumn,
54
+ };
55
+ }
56
+
57
+ /** For debugging a grammar: the tokens of `text` in `languageId`, per line, as Monaco's tokenize gives them. */
58
+ export async function tokenize(text, languageId) {
59
+ const k = await load();
60
+ return JSON.parse(k.aardinkTokenize(languageId, text));
61
+ }
62
+
63
+ /**
64
+ * A Monarch grammar as JSON: RegExp literals (/.../) become their source, as the Kotlin side reads
65
+ * regexes from strings; everything else is kept.
66
+ */
67
+ function monarchToJson(value) {
68
+ if (value instanceof RegExp) return value.source;
69
+ if (Array.isArray(value)) return value.map(monarchToJson);
70
+ if (value && typeof value === 'object') {
71
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, monarchToJson(item)]));
72
+ }
73
+ return value;
74
+ }
75
+
76
+ /** A provider's answer (a value or a Promise of one) as the Promise of JSON the Kotlin side awaits. */
77
+ function answer(provider, ...args) {
78
+ if (!provider) return Promise.resolve(null);
79
+ return Promise.resolve()
80
+ .then(() => provider(...args))
81
+ .then((value) => (value == null ? null : JSON.stringify(value)));
82
+ }
83
+
84
+ /**
85
+ * Adds a language highlighted by a Monarch grammar, for createEditor's `language` option. See
86
+ * index.d.ts for the definition and the providers.
87
+ */
88
+ export async function registerLanguage(definition, providers = {}) {
89
+ const k = await load();
90
+ const grammar = monarchToJson(definition.grammar ?? {});
91
+ if (definition.comments) grammar.comments = definition.comments;
92
+ const error = k.aardinkRegisterLanguage(
93
+ JSON.stringify({
94
+ id: definition.id,
95
+ extends: definition.extends,
96
+ displayName: definition.displayName,
97
+ extensions: definition.extensions,
98
+ grammar,
99
+ }),
100
+ (text, line, column) => answer(providers.provideCompletionItems, text, line, column),
101
+ (text, line, column) => answer(providers.provideHover, text, line, column),
102
+ (text) => answer(providers.provideDiagnostics, text),
103
+ );
104
+ if (error) throw new Error(`Aardink: ${error}`);
105
+ }
106
+
107
+ /** Monaco's defineTheme data ({ base, inherit, rules, colors }) as VS Code theme JSON; VS Code JSON as it is. */
108
+ function toVsCodeTheme(theme) {
109
+ if (!theme.rules) return theme;
110
+ const base = { vs: 'vscode-light', 'hc-light': 'vscode-light' }[theme.base] ?? 'vscode-dark';
111
+ return {
112
+ type: base === 'vscode-light' ? 'light' : 'dark',
113
+ base: theme.inherit === false ? undefined : base,
114
+ colors: theme.colors ?? {},
115
+ tokenColors: theme.rules
116
+ .filter((rule) => rule.foreground)
117
+ .map((rule) => ({ scope: rule.token, settings: { foreground: `#${rule.foreground.replace(/^#/, '')}` } })),
118
+ };
119
+ }
120
+
121
+ /** Adds a theme for createEditor's `theme` option, from Monaco's defineTheme data or VS Code theme JSON. */
122
+ export async function registerTheme(name, theme) {
123
+ const k = await load();
124
+ const error = k.aardinkRegisterTheme(name, JSON.stringify(toVsCodeTheme(theme)));
125
+ if (error) throw new Error(`Aardink: ${error}`);
126
+ }
127
+
128
+ /** Monaco's name for registerTheme. */
129
+ export const defineTheme = registerTheme;
130
+
131
+ /**
132
+ * Keeps only the fields the Kotlin side knows, translating Monaco's wordWrap 'on'/'off' and its
133
+ * { enabled } objects for minimap and stickyScroll (a plain boolean works too).
134
+ */
44
135
  function toKotlinOptions(options) {
45
136
  const out = {};
46
- for (const key of ['language', 'theme', 'fontSize', 'readOnly', 'showGutter', 'showLineNumbers', 'showFoldMarkers']) {
137
+ for (const key of ['language', 'theme', 'fontSize', 'readOnly', 'showGutter', 'showLineNumbers', 'showFoldMarkers', 'tabSize', 'insertSpaces', 'highlightCurrentLine']) {
47
138
  if (options[key] !== undefined) out[key] = options[key];
48
139
  }
49
140
  if (options.wordWrap !== undefined) out.wordWrap = options.wordWrap === 'on';
141
+ if (options.renderLineHighlight !== undefined) out.highlightCurrentLine = options.renderLineHighlight !== 'none';
142
+ for (const key of ['minimap', 'stickyScroll', 'bracketPairColorization']) {
143
+ const value = options[key];
144
+ if (value !== undefined) out[key] = typeof value === 'object' && value !== null ? value.enabled !== false : !!value;
145
+ }
50
146
  return out;
51
147
  }
52
148
 
@@ -58,8 +154,17 @@ export async function createEditor(container, onChange, options = {}) {
58
154
  // The Kotlin side holds one listener of each kind; fan out to any number here.
59
155
  const contentListeners = new Set();
60
156
  const cursorListeners = new Set();
61
- k.aardinkOnChange(id, (text) => contentListeners.forEach((listener) => listener(text)));
157
+ const diagnosticsListeners = new Set();
158
+ k.aardinkOnContentChange(id, (text, versionId, kind) => {
159
+ const event = { versionId, isUndoing: kind === 'undo', isRedoing: kind === 'redo', isFlush: kind === 'flush' };
160
+ contentListeners.forEach((listener) => listener(text, event));
161
+ });
62
162
  k.aardinkOnCursorChange(id, (line, column) => cursorListeners.forEach((listener) => listener(line, column)));
163
+ k.aardinkOnDiagnosticsChange(id, (json) => {
164
+ if (diagnosticsListeners.size === 0) return;
165
+ const diagnostics = JSON.parse(json);
166
+ diagnosticsListeners.forEach((listener) => listener(diagnostics));
167
+ });
63
168
  if (onChange) contentListeners.add(onChange);
64
169
 
65
170
  let disposed = false;
@@ -76,8 +181,30 @@ export async function createEditor(container, onChange, options = {}) {
76
181
  cursorListeners.add(listener);
77
182
  return () => cursorListeners.delete(listener);
78
183
  },
79
- setDiagnostics: (diagnostics) => k.aardinkSetDiagnostics(id, JSON.stringify(diagnostics)),
184
+ setDiagnostics: (diagnostics) => k.aardinkSetDiagnostics(id, JSON.stringify(diagnostics ?? null)),
185
+ onDidChangeDiagnostics(listener) {
186
+ diagnosticsListeners.add(listener);
187
+ return () => diagnosticsListeners.delete(listener);
188
+ },
80
189
  revealPosition: (line, column) => k.aardinkRevealPosition(id, line, column),
190
+ setBaseline: (text) => k.aardinkSetBaseline(id, text ?? ''),
191
+ format: () => new Promise((resolve) => k.aardinkFormat(id, resolve)),
192
+ focus: () => k.aardinkFocus(id),
193
+ getSelections: () => JSON.parse(k.aardinkGetSelections(id)).map(withRange),
194
+ setSelections: (selections) =>
195
+ k.aardinkSetSelections(
196
+ id,
197
+ JSON.stringify(selections.map((s) => ({
198
+ selectionStartLineNumber: s.selectionStartLineNumber,
199
+ selectionStartColumn: s.selectionStartColumn,
200
+ positionLineNumber: s.positionLineNumber,
201
+ positionColumn: s.positionColumn,
202
+ }))),
203
+ ),
204
+ canUndo: () => k.aardinkCanUndo(id),
205
+ canRedo: () => k.aardinkCanRedo(id),
206
+ pushUndoStop: () => k.aardinkPushUndoStop(id),
207
+ getAlternativeVersionId: () => k.aardinkGetAlternativeVersionId(id),
81
208
  showFind: () => k.aardinkShowFind(id),
82
209
  undo: () => k.aardinkUndo(id),
83
210
  redo: () => k.aardinkRedo(id),
@@ -86,6 +213,7 @@ export async function createEditor(container, onChange, options = {}) {
86
213
  disposed = true;
87
214
  contentListeners.clear();
88
215
  cursorListeners.clear();
216
+ diagnosticsListeners.clear();
89
217
  k.aardinkDispose(id);
90
218
  },
91
219
  };
@@ -149,6 +149,8 @@ const js_code = {
149
149
  'kotlin.js.__convertKotlinClosureToJsClosure_1' : (f, trampoline) => getCachedJsObject(f, (p0) => trampoline(f, p0)),
150
150
  'kotlin.js.then_$external_fun_1' : (_this, p0, p1) => _this.then(p0, p1),
151
151
  'kotlin.js.catch_$external_fun' : (_this, p0) => _this.catch(p0),
152
+ 'kotlin.js.Promise_$external_class_instanceof' : (x) => x instanceof Promise,
153
+ 'kotlin.js.Promise_$external_class_get' : () => Promise,
152
154
  'kotlin.random.initialSeed' : () => ((Math.random() * Math.pow(2, 32)) | 0),
153
155
  'kotlin.wasm.internal.getJsClassName' : (jsKlass) => jsKlass.name,
154
156
  'kotlin.wasm.internal.instanceOf' : (ref, jsKlass) => ref instanceof jsKlass,
@@ -558,8 +560,13 @@ const js_code = {
558
560
  'androidx.compose.material3.internal.weakMap_js_code' : () => (new WeakMap()),
559
561
  'androidx.compose.material3.internal.set_$external_fun' : (_this, p0, p1) => _this.set(p0, p1),
560
562
  'androidx.compose.material3.internal.get_$external_fun' : (_this, p0) => _this.get(p0),
563
+ 'com.aardarch.aardink.platform.readClipboardText' : () => navigator.clipboard.readText(),
561
564
  'com.aardarch.aardink.sampleweb.__callJsClosure_((String)->Unit)' : (f, p0) => f(p0),
562
565
  'com.aardarch.aardink.sampleweb.__callJsClosure_((Int,Int)->Unit)' : (f, p0, p1) => f(p0, p1),
566
+ 'com.aardarch.aardink.sampleweb.__callJsClosure_((String,Int,Int)->Js)' : (f, p0, p1, p2) => f(p0, p1, p2),
567
+ 'com.aardarch.aardink.sampleweb.__callJsClosure_((String)->Js)' : (f, p0) => f(p0),
568
+ 'com.aardarch.aardink.sampleweb.__callJsClosure_((String,Int,String)->Unit)' : (f, p0, p1, p2) => f(p0, p1, p2),
569
+ 'com.aardarch.aardink.sampleweb.__callJsClosure_((Boolean)->Unit)' : (f, p0) => f(p0),
563
570
  'com.aardarch.aardink.sampleweb.usedJsHeapBytes' : () => (performance.memory ? performance.memory.usedJSHeapSize : -1)
564
571
  }
565
572
 
@@ -77,6 +77,7 @@ export { wasmMemory as memory }
77
77
 
78
78
  export const {
79
79
  aardinkSetBundledFontUrl,
80
+ aardinkPreloadFont,
80
81
  aardinkCreate,
81
82
  aardinkGetValue,
82
83
  aardinkSetValue,
@@ -84,6 +85,20 @@ export const {
84
85
  aardinkOnChange,
85
86
  aardinkOnCursorChange,
86
87
  aardinkSetDiagnostics,
88
+ aardinkOnDiagnosticsChange,
89
+ aardinkRegisterLanguage,
90
+ aardinkRegisterTheme,
91
+ aardinkOnContentChange,
92
+ aardinkSetBaseline,
93
+ aardinkFormat,
94
+ aardinkFocus,
95
+ aardinkGetSelections,
96
+ aardinkSetSelections,
97
+ aardinkCanUndo,
98
+ aardinkCanRedo,
99
+ aardinkPushUndoStop,
100
+ aardinkGetAlternativeVersionId,
101
+ aardinkTokenize,
87
102
  aardinkRevealPosition,
88
103
  aardinkShowFind,
89
104
  aardinkUndo,
Binary file
package/package.json CHANGED
@@ -1,11 +1,27 @@
1
1
  {
2
2
  "name": "@aardarch/aardink-web",
3
- "version": "0.5.0",
3
+ "version": "0.6.0-rc1",
4
4
  "description": "The Aardink code editor for the browser: Compose Multiplatform on WebAssembly behind a Monaco-shaped API.",
5
+ "keywords": [
6
+ "code-editor",
7
+ "editor",
8
+ "syntax-highlighting",
9
+ "monaco",
10
+ "webassembly",
11
+ "wasm",
12
+ "kotlin",
13
+ "compose-multiplatform"
14
+ ],
15
+ "homepage": "https://aardarch.github.io/aardink/",
16
+ "bugs": {
17
+ "url": "https://github.com/aardarch/aardink/issues"
18
+ },
5
19
  "license": "Apache-2.0",
20
+ "author": "Aardarch",
6
21
  "repository": {
7
22
  "type": "git",
8
- "url": "git+https://github.com/aardarch/aardink.git"
23
+ "url": "git+https://github.com/aardarch/aardink.git",
24
+ "directory": "sample-web"
9
25
  },
10
26
  "type": "module",
11
27
  "main": "index.js",
@@ -20,8 +36,17 @@
20
36
  "index.js",
21
37
  "index.d.ts",
22
38
  "kotlin/",
39
+ "README.md",
40
+ "LICENSE",
23
41
  "JETBRAINS_MONO_OFL.txt"
24
42
  ],
25
43
  "sideEffects": false,
44
+ "engines": {
45
+ "node": ">=20"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public",
49
+ "provenance": true
50
+ },
26
51
  "dependencies": { "@js-joda/core": "3.2.0" }
27
52
  }