
High-performance code editor with Monaco-like API and VS Code keybindings: incremental syntax highlighting, multiple cursors, LSP integration, folding, minimap, sticky scroll, customizable themes.
A Compose Multiplatform code editor with VS Code's keys and Monaco's API shape: multiple cursors, incremental highlighting, folding, find and replace, diagnostics, completions, a minimap and sticky scroll. It runs on Android, desktop (JVM) and the browser (Wasm) from one codebase, and draws only the lines on screen, so a keystroke costs the same in a 5,000-line file as in a 50-line one.
Upgrading? From 0.6,
docs/MIGRATION_0.7.md; from 0.5, which had a different text field underneath the editor,docs/MIGRATION_0.6.mdfirst. Each has every change with a before and after. What comes next is indocs/AARDINK_ROADMAP.md.
| Android | Desktop | Web | |
|---|---|---|---|
| Syntax highlighting, incremental, for Kotlin, TypeScript, JSON, TOML, XML, HTML, CSS, Markdown | ✓ | ✓ | ✓ |
Your own language: a tokenizer in Kotlin, or a Monarch grammar as data (DeclarativeGrammar) |
✓ | ✓ | ✓ |
| … or registered from JavaScript, with completions, hover and diagnostics in JavaScript | ✓ | ||
An XML dialect from a schema: completions by nesting, hover, checks (XmlSchemaLanguageService) |
✓ | ✓ | ✓ |
A dialect over a built-in language, keeping its checks and formatting (CompositeLanguageService) |
✓ | ✓ | ✓ |
| Folding, find and replace, go to line | ✓ | ✓ | ✓ |
| Multiple cursors and column selection | ✓ | ✓ | ✓ |
Undo grouped as Monaco does, alternativeVersionId for dirty tracking |
✓ | ✓ | ✓ |
Content and selection events with ranges; commands for a host's buttons (EditorCommand) |
✓ | ✓ | ✓ |
| Completions, hover, signature help and quick fixes at the caret | ✓ | ✓ | ✓ |
| Completions filtered and ordered by the editor, quick suggestions, commit characters | ✓ | ✓ | ✓ |
| Diagnostics (squiggles and gutter markers), from the language or the host, with codes, tags and hints | ✓ | ✓ | ✓ |
| F8 through the problems; a squiggle's message under the mouse or a tap | ✓ | ✓ | ✓ |
| Go to definition, find references, format document or selection | ✓ | ✓ | ✓ |
| Bracket-pair colours and matching, current-line highlight | ✓ | ✓ | ✓ |
| Minimap and sticky scroll | ✓ | ✓ | ✓ |
| Document symbols: Go to Symbol, breadcrumbs, the symbols around the caret for a preview | ✓ | ✓ | ✓ |
| Decorations from the host, an overview ruler, occurrence highlights | ✓ | ✓ | ✓ |
| Colour swatches with a picker; linked editing of tag pairs; links opened with Ctrl+click | ✓ | ✓ | ✓ |
| Keybindings as data, with the host's overrides; Ctrl+K chords; region markers and indentation folding | ✓ | ✓ | ✓ |
| Zoom: pinch, Ctrl+wheel, Ctrl+= and Ctrl+-; Material 3 colours from the app's scheme | ✓ | ✓ | ✓ |
| Diff lane against a saved baseline | ✓ | ✓ | ✓ |
| VS Code's keyboard shortcuts (Cmd on macOS) | with a keyboard | ✓ | ✓ |
| Input methods: composition, and a phone keyboard's autocorrect and voice input | ✓ | ✓ | ✓ |
| Touch: selection handles, text menu | ✓ | ✓ | |
| Magnifier while dragging a handle | ✓ | ||
| Keyboard toolbar above the soft keyboard: the language's characters, caret arrows, undo, redo, and your own buttons | ✓ | on touch devices | |
| Touch menu and right-click menu, with your own items | ✓ | ✓ | ✓ |
Language servers (aardink-languages-lsp) |
streams | streams | WebSocket |
Themes: EditorTheme data classes, six built in, VS Code theme JSON, or Monaco's defineTheme data |
✓ | ✓ | ✓ |
Aardink publishes four artifacts on Maven Central:
com.aardarch:aardink — the editor library: composables, state,
theming, and the language-service interfaces.com.aardarch:aardink-languages — built-in language definitions
(Kotlin, TypeScript, JSON, TOML, XML, HTML, CSS, Markdown, plain text).com.aardarch:aardink-languages-lsp — a bridge to an external
Language Server over JSON-RPC (stream transport on JVM/Android, WebSocket in the browser).com.aardarch:aardink-editor-web — browser only: mounts the editor into a DOM element
behind a small JavaScript-friendly API. See Using Aardink in a web app.and one package on npm, @aardarch/aardink-web: the ready-built browser editor for any
JavaScript app, with the same version number.
The editor module is intentionally language-agnostic. Pull in
aardink-languages for the bundled definitions, or implement your own via
LanguageDefinition + LanguageRegistry.
Each is a Kotlin Multiplatform publication: a Gradle Module Metadata root plus one
artifact per target. Declaring com.aardarch:aardink resolves to aardink-android,
aardink-jvm or aardink-wasm-js automatically, the same way kotlinx.coroutines
works — you do not name the target yourself.
| Platform | Requirement |
|---|---|
| Android |
minSdk 26, compileSdk 37 |
| Desktop (JVM) | JDK 21. Add kotlinx-coroutines-swing — CodeEditorState defaults its scope to Dispatchers.Main, which on the JVM has no implementation without it |
| Browser (Wasm) | Chrome/Edge 119+, Firefox 120+, Safari 18.2+ (WasmGC + exception handling) |
Kotlin with Compose Multiplatform (Material 3) on every target.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
// app/build.gradle.kts
dependencies {
// Use the current version from the Maven Central badge above.
implementation("com.aardarch:aardink:<version>")
implementation("com.aardarch:aardink-languages:<version>")
}import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.aardarch.aardink.core.rememberCodeEditorState
import com.aardarch.aardink.languages.LanguageRegistry
import com.aardarch.aardink.ui.CodeEditorLayout
import com.aardarch.aardink.ui.EditorOptions
import com.aardarch.aardink.ui.EditorThemes
import com.aardarch.aardink.ui.LocalEditorTheme
@Composable
fun MyEditor() {
val kotlinLanguage = LanguageRegistry.withBuiltIns().byId("kotlin")!!
val state = rememberCodeEditorState(
initialText = "fun main() = println(\"Hello, Aardink\")",
tokenizer = kotlinLanguage.tokenizer,
)
CompositionLocalProvider(LocalEditorTheme provides EditorThemes.VsCodeDark) {
CodeEditorLayout(
state = state,
languageService = kotlinLanguage.languageService,
foldingProvider = kotlinLanguage.foldingProvider,
options = EditorOptions(showMinimap = true, stickyScroll = true),
)
}
}With a language service and no diagnostics argument, the editor collects the service's
diagnostics itself. Read the text with state.text, the selections with state.selections, and
edit with state.applyTextEdits(...). state.addContentListener { changes -> … } reports every
edit with its range; state.execute(EditorCommand.Format) and the other commands drive a
toolbar, state.availableCommands enabling its buttons. See the sample/ module for
a runnable example.
The browser build is a WebAssembly module behind a Monaco-shaped API, published on npm as
@aardarch/aardink-web with the built-in
languages and themes:
pnpm add @aardarch/aardink-webimport { createEditor } from '@aardarch/aardink-web';
const editor = await createEditor(container, (text) => save(text), { value: source, language: 'xml' });For a grammar or language service of your own written in Kotlin, build the module yourself
instead: a small Gradle wasmJs module that depends on aardink-editor-web, with
sample-web/ as the reference. docs/WEB_INTEGRATION.md
covers both routes: the export template, npm packaging, Vite configuration, and a Monaco option
mapping.
A language of your own can be registered from JavaScript as a Monarch grammar, with completions,
hover and diagnostics answered by JavaScript functions, and a theme as Monaco's defineTheme
data. docs/AARDFLEX_WEB_SWITCHOVER.md moves a real Monaco
app over.
On the web: a keystroke settles in about 10 ms, and nearly always under 35 ms, whatever the size of the document (measured in
docs/WEB_INTEGRATION.md). A disposed editor is not fully released until Compose Multiplatform 1.13, so reuse one editor rather than mounting a new one per view.
Themes are plain data classes. Wrap your editor in a
CompositionLocalProvider(LocalEditorTheme provides …) to apply a theme,
or pass one directly to CodeEditorLayout. The library ships a set of
ready-made themes via EditorThemes (e.g. VsCodeDark, MidnightOcean).
Custom themes are just EditorTheme(...) instances.
To add a new language, implement these interfaces from the editor module:
IncrementalTokenizer — syntax highlighting, and commentSyntax for toggling commentsFoldingProvider — code foldingLanguageService (optional) — completions, diagnostics, hover, signature help, quick fixes,
definitions, references and formattingBundle them as a LanguageDefinition and register with LanguageRegistry.
The languages/ module is a working reference.
For a dialect of a built-in language, LanguageDefinition.extending(BuiltInLanguages.Xml, myService)
puts your service over the base's, keeping its checks, indentation and formatting. For an XML
dialect, describe its elements and attributes once as an XmlSchema (JSON) and
XmlSchemaLanguageService(XmlSchema.parse(json)) gives completions, hover and checks from it.
For highlighting alone, no tokenizer code is needed: DeclarativeTokenizer(DeclarativeGrammar.parse(json))
highlights from a Monarch grammar (Monaco's format) as JSON, a line at a time and incrementally.
Its tokens are NamedTokenTypes named by the grammar, which an EditorTheme colours by the
longest dotted prefix it has an entry for, as Monaco's themes do.
./gradlew :sample:installDebug # Android
./gradlew :sample-desktop:run # Desktop (JVM)
./gradlew :sample-web:wasmJsBrowserDevelopmentRun # BrowserEach GitHub release also attaches the Android APK and a static build of the browser sample.
To render screenshots of the start screen and every sample page, in every bundled theme,
into screenshots/ (no device or emulator needed):
./scripts/capture-screenshots.ps1./gradlew :editor:jvmTest :languages:jvmTest :languages-lsp:jvmTest # JVM unit tests
./gradlew :editor:wasmJsBrowserTest :editor-web:wasmJsBrowserTest # Browser tests (needs Chrome)
./gradlew checkAbiAll # Public ABI check
./gradlew :editor:spotlessApply :languages:spotlessApply :languages-lsp:spotlessApply :editor-web:spotlessApply # Auto-format
./gradlew publishToMavenLocal # Publish all libraries to ~/.m2
./gradlew :sample-web:npmPackage # Build the npm packageFor the full build/test/release workflow — including the scripts/pre-push.ps1
end-to-end check — see AGENTS.md.
Contributions are welcome. Please read:
Aardink follows Semantic Versioning. Breaking public-API changes require a major version bump. Release notes live in CHANGELOG.md.
Aardink is licensed under the Apache License, Version 2.0.
A Compose Multiplatform code editor with VS Code's keys and Monaco's API shape: multiple cursors, incremental highlighting, folding, find and replace, diagnostics, completions, a minimap and sticky scroll. It runs on Android, desktop (JVM) and the browser (Wasm) from one codebase, and draws only the lines on screen, so a keystroke costs the same in a 5,000-line file as in a 50-line one.
Upgrading? From 0.6,
docs/MIGRATION_0.7.md; from 0.5, which had a different text field underneath the editor,docs/MIGRATION_0.6.mdfirst. Each has every change with a before and after. What comes next is indocs/AARDINK_ROADMAP.md.
| Android | Desktop | Web | |
|---|---|---|---|
| Syntax highlighting, incremental, for Kotlin, TypeScript, JSON, TOML, XML, HTML, CSS, Markdown | ✓ | ✓ | ✓ |
Your own language: a tokenizer in Kotlin, or a Monarch grammar as data (DeclarativeGrammar) |
✓ | ✓ | ✓ |
| … or registered from JavaScript, with completions, hover and diagnostics in JavaScript | ✓ | ||
An XML dialect from a schema: completions by nesting, hover, checks (XmlSchemaLanguageService) |
✓ | ✓ | ✓ |
A dialect over a built-in language, keeping its checks and formatting (CompositeLanguageService) |
✓ | ✓ | ✓ |
| Folding, find and replace, go to line | ✓ | ✓ | ✓ |
| Multiple cursors and column selection | ✓ | ✓ | ✓ |
Undo grouped as Monaco does, alternativeVersionId for dirty tracking |
✓ | ✓ | ✓ |
Content and selection events with ranges; commands for a host's buttons (EditorCommand) |
✓ | ✓ | ✓ |
| Completions, hover, signature help and quick fixes at the caret | ✓ | ✓ | ✓ |
| Completions filtered and ordered by the editor, quick suggestions, commit characters | ✓ | ✓ | ✓ |
| Diagnostics (squiggles and gutter markers), from the language or the host, with codes, tags and hints | ✓ | ✓ | ✓ |
| F8 through the problems; a squiggle's message under the mouse or a tap | ✓ | ✓ | ✓ |
| Go to definition, find references, format document or selection | ✓ | ✓ | ✓ |
| Bracket-pair colours and matching, current-line highlight | ✓ | ✓ | ✓ |
| Minimap and sticky scroll | ✓ | ✓ | ✓ |
| Document symbols: Go to Symbol, breadcrumbs, the symbols around the caret for a preview | ✓ | ✓ | ✓ |
| Decorations from the host, an overview ruler, occurrence highlights | ✓ | ✓ | ✓ |
| Colour swatches with a picker; linked editing of tag pairs; links opened with Ctrl+click | ✓ | ✓ | ✓ |
| Keybindings as data, with the host's overrides; Ctrl+K chords; region markers and indentation folding | ✓ | ✓ | ✓ |
| Zoom: pinch, Ctrl+wheel, Ctrl+= and Ctrl+-; Material 3 colours from the app's scheme | ✓ | ✓ | ✓ |
| Diff lane against a saved baseline | ✓ | ✓ | ✓ |
| VS Code's keyboard shortcuts (Cmd on macOS) | with a keyboard | ✓ | ✓ |
| Input methods: composition, and a phone keyboard's autocorrect and voice input | ✓ | ✓ | ✓ |
| Touch: selection handles, text menu | ✓ | ✓ | |
| Magnifier while dragging a handle | ✓ | ||
| Keyboard toolbar above the soft keyboard: the language's characters, caret arrows, undo, redo, and your own buttons | ✓ | on touch devices | |
| Touch menu and right-click menu, with your own items | ✓ | ✓ | ✓ |
Language servers (aardink-languages-lsp) |
streams | streams | WebSocket |
Themes: EditorTheme data classes, six built in, VS Code theme JSON, or Monaco's defineTheme data |
✓ | ✓ | ✓ |
Aardink publishes four artifacts on Maven Central:
com.aardarch:aardink — the editor library: composables, state,
theming, and the language-service interfaces.com.aardarch:aardink-languages — built-in language definitions
(Kotlin, TypeScript, JSON, TOML, XML, HTML, CSS, Markdown, plain text).com.aardarch:aardink-languages-lsp — a bridge to an external
Language Server over JSON-RPC (stream transport on JVM/Android, WebSocket in the browser).com.aardarch:aardink-editor-web — browser only: mounts the editor into a DOM element
behind a small JavaScript-friendly API. See Using Aardink in a web app.and one package on npm, @aardarch/aardink-web: the ready-built browser editor for any
JavaScript app, with the same version number.
The editor module is intentionally language-agnostic. Pull in
aardink-languages for the bundled definitions, or implement your own via
LanguageDefinition + LanguageRegistry.
Each is a Kotlin Multiplatform publication: a Gradle Module Metadata root plus one
artifact per target. Declaring com.aardarch:aardink resolves to aardink-android,
aardink-jvm or aardink-wasm-js automatically, the same way kotlinx.coroutines
works — you do not name the target yourself.
| Platform | Requirement |
|---|---|
| Android |
minSdk 26, compileSdk 37 |
| Desktop (JVM) | JDK 21. Add kotlinx-coroutines-swing — CodeEditorState defaults its scope to Dispatchers.Main, which on the JVM has no implementation without it |
| Browser (Wasm) | Chrome/Edge 119+, Firefox 120+, Safari 18.2+ (WasmGC + exception handling) |
Kotlin with Compose Multiplatform (Material 3) on every target.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}
// app/build.gradle.kts
dependencies {
// Use the current version from the Maven Central badge above.
implementation("com.aardarch:aardink:<version>")
implementation("com.aardarch:aardink-languages:<version>")
}import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import com.aardarch.aardink.core.rememberCodeEditorState
import com.aardarch.aardink.languages.LanguageRegistry
import com.aardarch.aardink.ui.CodeEditorLayout
import com.aardarch.aardink.ui.EditorOptions
import com.aardarch.aardink.ui.EditorThemes
import com.aardarch.aardink.ui.LocalEditorTheme
@Composable
fun MyEditor() {
val kotlinLanguage = LanguageRegistry.withBuiltIns().byId("kotlin")!!
val state = rememberCodeEditorState(
initialText = "fun main() = println(\"Hello, Aardink\")",
tokenizer = kotlinLanguage.tokenizer,
)
CompositionLocalProvider(LocalEditorTheme provides EditorThemes.VsCodeDark) {
CodeEditorLayout(
state = state,
languageService = kotlinLanguage.languageService,
foldingProvider = kotlinLanguage.foldingProvider,
options = EditorOptions(showMinimap = true, stickyScroll = true),
)
}
}With a language service and no diagnostics argument, the editor collects the service's
diagnostics itself. Read the text with state.text, the selections with state.selections, and
edit with state.applyTextEdits(...). state.addContentListener { changes -> … } reports every
edit with its range; state.execute(EditorCommand.Format) and the other commands drive a
toolbar, state.availableCommands enabling its buttons. See the sample/ module for
a runnable example.
The browser build is a WebAssembly module behind a Monaco-shaped API, published on npm as
@aardarch/aardink-web with the built-in
languages and themes:
pnpm add @aardarch/aardink-webimport { createEditor } from '@aardarch/aardink-web';
const editor = await createEditor(container, (text) => save(text), { value: source, language: 'xml' });For a grammar or language service of your own written in Kotlin, build the module yourself
instead: a small Gradle wasmJs module that depends on aardink-editor-web, with
sample-web/ as the reference. docs/WEB_INTEGRATION.md
covers both routes: the export template, npm packaging, Vite configuration, and a Monaco option
mapping.
A language of your own can be registered from JavaScript as a Monarch grammar, with completions,
hover and diagnostics answered by JavaScript functions, and a theme as Monaco's defineTheme
data. docs/AARDFLEX_WEB_SWITCHOVER.md moves a real Monaco
app over.
On the web: a keystroke settles in about 10 ms, and nearly always under 35 ms, whatever the size of the document (measured in
docs/WEB_INTEGRATION.md). A disposed editor is not fully released until Compose Multiplatform 1.13, so reuse one editor rather than mounting a new one per view.
Themes are plain data classes. Wrap your editor in a
CompositionLocalProvider(LocalEditorTheme provides …) to apply a theme,
or pass one directly to CodeEditorLayout. The library ships a set of
ready-made themes via EditorThemes (e.g. VsCodeDark, MidnightOcean).
Custom themes are just EditorTheme(...) instances.
To add a new language, implement these interfaces from the editor module:
IncrementalTokenizer — syntax highlighting, and commentSyntax for toggling commentsFoldingProvider — code foldingLanguageService (optional) — completions, diagnostics, hover, signature help, quick fixes,
definitions, references and formattingBundle them as a LanguageDefinition and register with LanguageRegistry.
The languages/ module is a working reference.
For a dialect of a built-in language, LanguageDefinition.extending(BuiltInLanguages.Xml, myService)
puts your service over the base's, keeping its checks, indentation and formatting. For an XML
dialect, describe its elements and attributes once as an XmlSchema (JSON) and
XmlSchemaLanguageService(XmlSchema.parse(json)) gives completions, hover and checks from it.
For highlighting alone, no tokenizer code is needed: DeclarativeTokenizer(DeclarativeGrammar.parse(json))
highlights from a Monarch grammar (Monaco's format) as JSON, a line at a time and incrementally.
Its tokens are NamedTokenTypes named by the grammar, which an EditorTheme colours by the
longest dotted prefix it has an entry for, as Monaco's themes do.
./gradlew :sample:installDebug # Android
./gradlew :sample-desktop:run # Desktop (JVM)
./gradlew :sample-web:wasmJsBrowserDevelopmentRun # BrowserEach GitHub release also attaches the Android APK and a static build of the browser sample.
To render screenshots of the start screen and every sample page, in every bundled theme,
into screenshots/ (no device or emulator needed):
./scripts/capture-screenshots.ps1./gradlew :editor:jvmTest :languages:jvmTest :languages-lsp:jvmTest # JVM unit tests
./gradlew :editor:wasmJsBrowserTest :editor-web:wasmJsBrowserTest # Browser tests (needs Chrome)
./gradlew checkAbiAll # Public ABI check
./gradlew :editor:spotlessApply :languages:spotlessApply :languages-lsp:spotlessApply :editor-web:spotlessApply # Auto-format
./gradlew publishToMavenLocal # Publish all libraries to ~/.m2
./gradlew :sample-web:npmPackage # Build the npm packageFor the full build/test/release workflow — including the scripts/pre-push.ps1
end-to-end check — see AGENTS.md.
Contributions are welcome. Please read:
Aardink follows Semantic Versioning. Breaking public-API changes require a major version bump. Release notes live in CHANGELOG.md.
Aardink is licensed under the Apache License, Version 2.0.