Skip to content

Debug mode #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 22, 2025
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,28 @@ This repository contains four projects:
The easiest way to use this library is to:

1. add this repository to the sources where gradle looks for plugins
2. add the `com.strumenta.kolasu.language-server-plugin` version `1.0.0` to the list of gradle plugins
2. add the `com.strumenta.kolasu.language-server-plugin` version `x.y.z` to the list of gradle plugins
3. optionally configure the language server by adding a gradle extension called `languageServer`
4. run the `createVscodeExtension` gradle task
5. run the `launchVscodeEditor` gradle task

## Debugging the language server

By default, the generated language client code, launches the language server with `jvm` with debugger attaching enabled on port 5706.
To enable debugging, specify the `debugPort` property in the `languageServer` gradle extension.
By default, it is set to `null` to run in production mode.

If using IntelliJ IDEA, one can create a `Remote JVM attach` task that attaches to `localhost:5706`.
When set to a valid port number, the underlying java process will listen on that port for debugger attachments.

Now, when the editor initializes the server, one may attach the debugger to the server process using this task, and intellij will pop up when a breakpoint is hit while using the editor.
To attach a debugger from `idea`, create a `Remote JVM Debug` debug configuration with the default configuration, but pointing to the port specified.

If interested in debugging the initializing code, one can enable the `suspend` flag in the jvm execution flags. That way the server process will stop until a debugger is attached.
Now, run the `launchVscodeEditor` task to open the editor and start the language server process.
While the editor is running, one can attach the debugger using the `idea` task and the execution will stop in the language server breakpoints.
The debugger can be detached and reattached any number of times.

When the language server extension deactivates, for example because the editor is closed, the underlying java process is stopped and also the debugger task if attached.

If interested in debugging the initializing code, one can enable the `suspendExecutionUntilDebuggerAttached` flag in the `languageServer` gradle extension.
This way, the server process won't start until a debugger is attached.

## Features

Expand Down
2 changes: 1 addition & 1 deletion examples/entity
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ open class Configuration {
lateinit var packageDefinitionPath: Path
lateinit var licensePath: Path
lateinit var outputPath: Path
var debugPort: Int? = null
var suspendExecutionUntilDebuggerAttached: Boolean = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class LanguageServerPlugin : Plugin<Project?> {
configuration.packageDefinitionPath = Paths.get(projectPath, "src", "main", "resources", "package.json")
configuration.licensePath = Paths.get(projectPath, "src", "main", "resources", "LICENSE.md")
configuration.outputPath = Paths.get(projectPath, "build", "vscode")
configuration.debugPort = null
configuration.suspendExecutionUntilDebuggerAttached = false

val shadowJar = project.tasks.getByName("shadowJar") as ShadowJar
shadowJar.manifest.attributes["Main-Class"] = "com.strumenta.$language.languageserver.MainKt"
Expand Down Expand Up @@ -270,22 +272,34 @@ class LanguageServerPlugin : Plugin<Project?> {
StandardCopyOption.REPLACE_EXISTING
)
} else {
var javaProcessArguments = """["-jar", context.asAbsolutePath("server.jar")]"""
if (configuration.debugPort != null) {
val suspend = if (configuration.suspendExecutionUntilDebuggerAttached) "y" else "n"
javaProcessArguments = """["-agentlib:jdwp=transport=dt_socket,server=y,suspend=$suspend,quiet=y,address=*:${configuration.debugPort}", "-jar", context.asAbsolutePath("server.jar")]"""
}
Files.writeString(
Paths.get(configuration.outputPath.toString(), "client.js"),
"""
let {LanguageClient} = require("./node_modules/vscode-languageclient/node");

let languageClient;

async function activate (context)
{
let productionServer = {run: {command: "java", args: ["-jar", context.asAbsolutePath("server.jar")]}};
let javaProcessInfo = {run: {command: "java", args: $javaProcessArguments}};

let languageClient = new LanguageClient("${configuration.language}", "${configuration.language} language server", productionServer, {documentSelector: ["${configuration.language}"]});
languageClient = new LanguageClient("${configuration.language}", "${configuration.language} language server", javaProcessInfo, {documentSelector: ["${configuration.language}"]});
await languageClient.start();

context.subscriptions.push(languageClient);
}

module.exports = {activate};
async function deactivate()
{
await languageClient?.stop();
}

module.exports = {activate, deactivate};
""".trimIndent()
)
}
Expand Down