@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 +112 -86
- package/index.d.ts +126 -3
- package/index.js +134 -6
- package/kotlin/aardink-web.import-object.mjs +7 -0
- package/kotlin/aardink-web.mjs +15 -0
- package/kotlin/aardink-web.wasm +0 -0
- package/package.json +27 -2
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
|
|
5
|
-
|
|
6
|
-
find and replace, diagnostics, undo
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
editor.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
##
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
package/kotlin/aardink-web.mjs
CHANGED
|
@@ -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,
|
package/kotlin/aardink-web.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aardarch/aardink-web",
|
|
3
|
-
"version": "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
|
}
|