Skip to content

languages: Add built-in Kotlin support via kotlin-language-server - #29

Merged
jasonsmithio merged 1 commit into
mainfrom
feat/lang-kotlin
May 16, 2026
Merged

languages: Add built-in Kotlin support via kotlin-language-server#29
jasonsmithio merged 1 commit into
mainfrom
feat/lang-kotlin

Conversation

@jasonsmithio

Copy link
Copy Markdown
Collaborator

Summary

Adds Kotlin as a built-in language. .kt and .kts files get syntax highlighting, indent rules, outline, and completion / hover / go-to-def via kotlin-language-server — found on $PATH first, otherwise downloaded as server.zip from fwcd/kotlin-language-server GitHub releases. Same zip works on all platforms because the launcher is a shell script and the implementation is a JVM JAR.

Second of four planned bake-in PRs (Swift → Kotlin → Java → PHP). Independent of #28 — both base on main and don't conflict.

Grammar crate selection (the hard part)

Three published Kotlin tree-sitter crates, only one usable:

Crate tree-sitter constraint Resolves against workspace 0.26? Node shape
tree-sitter-kotlin 0.3.8 <0.23 ❌ links conflict simple_identifier (matches Zed extension queries)
tree-sitter-kotlin-ng 1.1.0 ^0.24 ❌ minor-version conflict identifier / qualified_identifier (would force rewriting every .scm)
tree-sitter-kotlin-codanna 0.3.9 >=0.21 (open-ended) simple_identifier (preserves original)

Went with codanna. Rationale is recorded in a multi-line comment on the ("kotlin", tree_sitter_kotlin_codanna::language()) entry in crates/grammars/src/grammars.rs so the next reader doesn't try to switch back to "the canonical" crate.

What's here

  • Grammar. tree-sitter-kotlin-codanna = "0.3.9" (MIT). Added to workspace Cargo.toml, crates/grammars/Cargo.toml (deps + load-grammars feature), and registered in crates/grammars/src/grammars.rs::native_grammars().
  • Query files. Pulled from zed-extensions/kotlin (MIT, compatible with the GPL-3.0 languages crate) into crates/grammars/src/kotlin/: brackets.scm, config.toml, highlights.scm (one patch), indents.scm, injections.scm, outline.scm (one patch), overrides.scm.
  • LSP adapter. New crates/languages/src/kotlin.rs (~190 LOC). Modeled on c.rs::CLspAdapter (GitHub-zip pattern). check_if_user_installed does delegate.which("kotlin-language-server"); fetch_latest_server_version queries fwcd/kotlin-language-server for the single server.zip asset (constant name across releases); fetch_server_binary downloads, unzips into <container>/kotlin-language-server_<tag>/server/, returns server/bin/kotlin-language-server[.bat]. Sets 0o755 on the launcher post-unzip on Unix. Skips the --version validity probe used by c.rs because the launcher shells out to java — every probe would boot a JVM; file-existence check + digest comparison is enough.
  • Wiring. crates/languages/src/lib.rs::init gains a mod kotlin;, an Arc::new(kotlin::KotlinLspAdapter) instantiation, and a LanguageInfo { name: "kotlin", adapters: vec![kotlin_lsp_adapter], .. } entry between jsonc and markdown. All four touch points tagged // PaddleBoard: for upstream-merge resolution.

Query fixups discovered during smoke testing

The zed-extensions/kotlin queries were authored against a slightly different grammar shape than codanna's. Two .scm files needed minor patches:

  • highlights.scm line 139: anonymous "null" token → (null_literal) named node. Codanna grammar models null as a named node, the quoted form failed with Invalid node type "null".
  • outline.scm lines 24-39: dropped the ["val" "var"] @context matchers inside property_declaration. Codanna's property_declaration only exposes named children to queries (no anonymous val/var tokens), so the original pattern was Impossible. Property names still appear in the outline, just without the val/var prefix.

Both patches have explanatory ; PaddleBoard: comments inline.

Test plan

  • cargo check -p grammars --features load-grammars clean
  • cargo check -p paddleboard clean
  • Language registers cleanly in the running binary (zero query-loader errors after the .scm patches)
  • Live LSP spawn — needs a .kt buffer to trigger the ~85 MB download. Mechanically identical to c.rs's clangd flow which is known to work; recommend the first reviewer verify by git checkout + open a .kt file with kotlin-language-server not on $PATH (force the download path).

Known limitations

  • JDK requirement is runtime-detected, not pre-checked. The launcher script bails clearly if java isn't on $PATH. fwcd's recent releases want JDK 17+; older Java will surface a stderr error from the launcher rather than a friendly notification. Adding a java -version probe is a worthwhile follow-up.
  • Property val/var no longer appears as outline context (see fixup above).
  • No Gradle / buildSrc multi-module integration — Kotlin LSP needs path-style classpath hints for non-trivial projects. Out of scope here; users can set up settings.json overrides.

Out of scope (follow-ups)

  • Java / PHP — next two PRs in the bake-in series
  • JDK version pre-check + clearer error notification
  • Gradle multi-module classpath integration
  • Custom completion ranking for Kotlin

Release Notes:

  • Added built-in support for Kotlin, including syntax highlighting and language-server features (completion, hover, go-to-definition) via kotlin-language-server from fwcd/kotlin-language-server. The server is downloaded automatically on first use, or picked up from $PATH if installed system-wide. Requires a JDK at runtime.

Adds Kotlin as a first-class built-in: tree-sitter grammar registered,
.kt/.kts files recognized, syntax highlighting + outline + indents on,
and kotlin-language-server (fwcd) driven for completion/hover/go-to-def.

- Grammar: tree-sitter-kotlin-codanna 0.3.9 (MIT). Picked specifically
  because canonical tree-sitter-kotlin pins tree-sitter <0.23 (conflicts
  with workspace 0.26) and the modern tree-sitter-kotlin-ng rewrite
  renamed nodes (would require rewriting every .scm from
  zed-extensions/kotlin). Codanna preserves the original
  simple_identifier node shape while relaxing the tree-sitter version
  constraint. Rationale recorded in a multi-line comment on the
  native_grammars() entry.
- Queries: pulled from zed-extensions/kotlin (MIT) into
  crates/grammars/src/kotlin/. Two patches needed during smoke testing:
  (a) highlights.scm: anonymous "null" token replaced with the
      (null_literal) named node — codanna models null as a named node.
  (b) outline.scm: dropped ["val" "var"] @context matchers inside
      property_declaration — codanna only exposes named children, so
      the original pattern was "Impossible". Property names still
      appear in the outline, just without the val/var prefix.
- Adapter: new crates/languages/src/kotlin.rs (~190 LOC). Modeled on
  c.rs::CLspAdapter — $PATH lookup first, otherwise downloads
  server.zip from fwcd/kotlin-language-server (one asset per release,
  no platform suffix; launcher is a shell script, implementation is a
  JVM JAR). Sets 0o755 on the launcher post-unzip on Unix.
- Wired into crates/languages/src/lib.rs::init with PaddleBoard tags on
  every upstream-shaped edit.

Second of four planned bake-in PRs (Swift -> Kotlin -> Java -> PHP).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jasonsmithio
jasonsmithio merged commit 9581eec into main May 16, 2026
@jasonsmithio
jasonsmithio deleted the feat/lang-kotlin branch May 16, 2026 16:56
jasonsmithio added a commit that referenced this pull request May 31, 2026
languages: Add built-in Kotlin support via kotlin-language-server
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant