
Language Server and Debug Adapter Protocol implementations with JSON-RPC 2.0 core, full lsp4j model parity, Content-Length stream transport, service launchers, custom transports and stdio/socket examples.
A Kotlin Multiplatform implementation of the Language Server Protocol (LSP) and the Debug Adapter Protocol (DAP), modeled after eclipse-lsp4j/lsp4j.
Content-Length framed transport (shared by LSP and DAP)Protocol.xtend, including client/server capabilities, completion, hover, diagnostics, semantic tokens, inlay hints, inline values, notebook documents, and pull diagnostics (LSP 3.18)LanguageServer, TextDocumentService, WorkspaceService, LanguageClient interfaces mirroring lsp4j, wired to the wire by LanguageServerLauncher
initialize, setBreakpoints, threads, stackTrace, scopes, variables, evaluate, …), DebugAdapter interface, and DebugAdapterLauncher
| Path | Description |
|---|---|
: (root) |
Library: cn.enaium.lsp (LSP + JSON-RPC) and cn.enaium.lsp.dap (DAP) |
:examples:stdio |
Language server over stdin/stdout |
:examples:socket |
Language server over a TCP socket |
cn.enaium.lsp
├── jsonrpc # JSON-RPC 2.0 core (messages, server, transport, launcher)
├── model # LSP data models (lsp4j parity)
├── dap # Debug Adapter Protocol
│ ├── model # DAP data models
│ ├── DebugAdapter
│ └── DebugAdapterLauncher
├── LanguageServer
├── LanguageServerLauncher
└── LanguageServerServices
The library is not yet published to a Maven repository. Consume it as a local project dependency or wait for publication.
Implement LanguageServer (only the methods you support need real bodies; every service method has a default), then run it over a MessageTransport:
class MyServer : LanguageServer {
override fun initialize(params: InitializeParams): InitializeResult =
InitializeResult(
capabilities = ServerCapabilities(
textDocumentSync = JsonPrimitive(TextDocumentSyncKind.Full),
hoverProvider = JsonPrimitive(true),
),
serverInfo = ServerInfo(name = "my-server", version = "1.0.0"),
)
override fun shutdown(): Any? = null
override fun exit() { Runtime.getRuntime().halt(0) }
override fun textDocumentService(): TextDocumentService? = MyTextDocumentService()
override fun workspaceService(): WorkspaceService? = null
override fun windowService(): WindowService? = null
}
class MyTextDocumentService : TextDocumentService {
override fun hover(params: HoverParams): Hover? =
Hover(contents = JsonPrimitive("Hello from ${params.textDocument.uri}"))
}
fun main() {
// stdio transport: content-length framed messages on stdin/stdout
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = LanguageServerLauncher(transport, MyServer())
launcher.listen()
}class MyAdapter : DebugAdapter {
override fun initialize(request: InitializeRequestArguments): Capabilities =
Capabilities(supportsConfigurationDoneRequest = true)
override fun launch(args: JsonElement?): JsonElement? = null
override fun threads(): ThreadsResponseBody = ThreadsResponseBody(listOf(Thread(1, "main")))
// ... implement the remaining commands your adapter supports
}
fun main() {
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = DebugAdapterLauncher(transport, MyAdapter())
launcher.listen()
}MessageTransport is a two-method interface (send / receive); StreamMessageTransport implements the standard Content-Length framing over any InputStream/OutputStream — including sockets, pipes, or WebSocket byte streams. Use the socket example as a reference for a TCP server.
Build and run:
# stdio language server (LSP client integration, e.g. VS Code language client)
./gradlew :examples:stdio:run
# socket language server on port 8080 (pass a port as the first argument)
./gradlew :examples:socket:runBoth examples accept the standard LSP handshake (initialize → requests → shutdown → exit).
./gradlew :jvmTest # JVM tests
./gradlew :macosArm64Test # native (macOS arm64) tests
./gradlew :linuxX64Test # native (Linux x64) testsSimulator-based tests (iOS/tvOS/watchOS simulators) require the corresponding simulator runtime installed via Xcode; they are skipped when no runtime is available. Apple x64 test tasks are disabled (Rosetta not required for compilation, only for running tests).
Dependencies are declared through a Gradle version catalog at
gradle/libs.versions.toml. Android builds need an SDK location; provide it
via ANDROID_HOME or a local local.properties (sdk.dir=...).
The test suite covers JSON-RPC dispatch/correlation, typed launcher round-trips, stream framing, end-to-end pipe communication, and LSP lifecycle wiring.
A Kotlin Multiplatform implementation of the Language Server Protocol (LSP) and the Debug Adapter Protocol (DAP), modeled after eclipse-lsp4j/lsp4j.
Content-Length framed transport (shared by LSP and DAP)Protocol.xtend, including client/server capabilities, completion, hover, diagnostics, semantic tokens, inlay hints, inline values, notebook documents, and pull diagnostics (LSP 3.18)LanguageServer, TextDocumentService, WorkspaceService, LanguageClient interfaces mirroring lsp4j, wired to the wire by LanguageServerLauncher
initialize, setBreakpoints, threads, stackTrace, scopes, variables, evaluate, …), DebugAdapter interface, and DebugAdapterLauncher
| Path | Description |
|---|---|
: (root) |
Library: cn.enaium.lsp (LSP + JSON-RPC) and cn.enaium.lsp.dap (DAP) |
:examples:stdio |
Language server over stdin/stdout |
:examples:socket |
Language server over a TCP socket |
cn.enaium.lsp
├── jsonrpc # JSON-RPC 2.0 core (messages, server, transport, launcher)
├── model # LSP data models (lsp4j parity)
├── dap # Debug Adapter Protocol
│ ├── model # DAP data models
│ ├── DebugAdapter
│ └── DebugAdapterLauncher
├── LanguageServer
├── LanguageServerLauncher
└── LanguageServerServices
The library is not yet published to a Maven repository. Consume it as a local project dependency or wait for publication.
Implement LanguageServer (only the methods you support need real bodies; every service method has a default), then run it over a MessageTransport:
class MyServer : LanguageServer {
override fun initialize(params: InitializeParams): InitializeResult =
InitializeResult(
capabilities = ServerCapabilities(
textDocumentSync = JsonPrimitive(TextDocumentSyncKind.Full),
hoverProvider = JsonPrimitive(true),
),
serverInfo = ServerInfo(name = "my-server", version = "1.0.0"),
)
override fun shutdown(): Any? = null
override fun exit() { Runtime.getRuntime().halt(0) }
override fun textDocumentService(): TextDocumentService? = MyTextDocumentService()
override fun workspaceService(): WorkspaceService? = null
override fun windowService(): WindowService? = null
}
class MyTextDocumentService : TextDocumentService {
override fun hover(params: HoverParams): Hover? =
Hover(contents = JsonPrimitive("Hello from ${params.textDocument.uri}"))
}
fun main() {
// stdio transport: content-length framed messages on stdin/stdout
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = LanguageServerLauncher(transport, MyServer())
launcher.listen()
}class MyAdapter : DebugAdapter {
override fun initialize(request: InitializeRequestArguments): Capabilities =
Capabilities(supportsConfigurationDoneRequest = true)
override fun launch(args: JsonElement?): JsonElement? = null
override fun threads(): ThreadsResponseBody = ThreadsResponseBody(listOf(Thread(1, "main")))
// ... implement the remaining commands your adapter supports
}
fun main() {
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = DebugAdapterLauncher(transport, MyAdapter())
launcher.listen()
}MessageTransport is a two-method interface (send / receive); StreamMessageTransport implements the standard Content-Length framing over any InputStream/OutputStream — including sockets, pipes, or WebSocket byte streams. Use the socket example as a reference for a TCP server.
Build and run:
# stdio language server (LSP client integration, e.g. VS Code language client)
./gradlew :examples:stdio:run
# socket language server on port 8080 (pass a port as the first argument)
./gradlew :examples:socket:runBoth examples accept the standard LSP handshake (initialize → requests → shutdown → exit).
./gradlew :jvmTest # JVM tests
./gradlew :macosArm64Test # native (macOS arm64) tests
./gradlew :linuxX64Test # native (Linux x64) testsSimulator-based tests (iOS/tvOS/watchOS simulators) require the corresponding simulator runtime installed via Xcode; they are skipped when no runtime is available. Apple x64 test tasks are disabled (Rosetta not required for compilation, only for running tests).
Dependencies are declared through a Gradle version catalog at
gradle/libs.versions.toml. Android builds need an SDK location; provide it
via ANDROID_HOME or a local local.properties (sdk.dir=...).
The test suite covers JSON-RPC dispatch/correlation, typed launcher round-trips, stream framing, end-to-end pipe communication, and LSP lifecycle wiring.