Skip to content

Commit

Permalink
feat: enable debugging of servers in the playground
Browse files Browse the repository at this point in the history
  • Loading branch information
alcarney committed Dec 20, 2023
1 parent 5b74064 commit e19a459
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 15 deletions.
20 changes: 20 additions & 0 deletions examples/servers/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"configurations": [
{
"name": "pygls: Debug Server",
"type": "python",
"request": "attach",
"connect": {
"host": "${config:pygls.server.debugHost}",
"port": "${config:pygls.server.debugPort}"
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "."
}
],
"justMyCode": false
}
]
}
3 changes: 3 additions & 0 deletions examples/servers/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
// Uncomment to override Python interpreter used.
// "pygls.server.pythonPath": "/path/to/python",
"pygls.server.debug": false,
// "pygls.server.debugHost": "localhost",
// "pygls.server.debugPort": 5678,
"pygls.server.launchScript": "json_server.py",
"pygls.trace.server": "off",
"pygls.client.documentSelector": [
Expand Down
7 changes: 7 additions & 0 deletions examples/vscode-playground/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ The `code_actions.py` example is intended to be used with text files (e.g. the p
```

You can find the full list of known language identifiers [here](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers).

#### Debugging the server

To debug the language server set the `pygls.server.debug` option to `true`.
The server should be restarted and the debugger connect automatically.

You can control the host and port that the debugger uses through the `pygls.server.debugHost` and `pygls.server.debugPort` options.
18 changes: 18 additions & 0 deletions examples/vscode-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,24 @@
"description": "The working directory from which to launch the server.",
"markdownDescription": "The working directory from which to launch the server.\nIf blank, this will default to the `examples/servers` directory."
},
"pygls.server.debug": {
"scope": "resource",
"default": false,
"type": "boolean",
"description": "Enable debugging of the server process."
},
"pygls.server.debugHost": {
"scope": "resource",
"default": "localhost",
"type": "string",
"description": "The host on which the server process to debug is running."
},
"pygls.server.debugPort": {
"scope": "resource",
"default": 5678,
"type": "integer",
"description": "The port number on which the server process to debug is listening."
},
"pygls.server.launchScript": {
"scope": "resource",
"type": "string",
Expand Down
85 changes: 70 additions & 15 deletions examples/vscode-playground/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as vscode from "vscode";
import * as semver from "semver";

import { PythonExtension } from "@vscode/python-extension";
import { LanguageClient, LanguageClientOptions, ServerOptions, State } from "vscode-languageclient/node";
import { LanguageClient, LanguageClientOptions, ServerOptions, State, integer } from "vscode-languageclient/node";

const MIN_PYTHON = semver.parse("3.7.9")

Expand Down Expand Up @@ -144,33 +144,41 @@ async function startLangServer() {
if (client) {
await stopLangServer()
}

const config = vscode.workspace.getConfiguration("pygls.server")
const cwd = getCwd()
const serverPath = getServerPath()

logger.info(`cwd: '${cwd}'`)
logger.info(`server: '${serverPath}'`)

const resource = vscode.Uri.joinPath(vscode.Uri.file(cwd), serverPath)
const pythonPath = await getPythonPath(resource)
if (!pythonPath) {
const pythonCommand = await getPythonCommand(resource)
if (!pythonCommand) {
clientStarting = false
return
}

logger.debug(`python: ${pythonCommand.join(" ")}`)
const serverOptions: ServerOptions = {
command: pythonPath,
args: [serverPath],
command: pythonCommand[0],
args: [...pythonCommand.slice(1), serverPath],
options: { cwd },
};

client = new LanguageClient('pygls', serverOptions, getClientOptions());
try {
await client.start()
clientStarting = false
} catch (err) {
clientStarting = false
logger.error(`Unable to start server: ${err}`)
const promises = [client.start()]

if (config.get<boolean>("debug")) {
promises.push(startDebugging())
}

const results = await Promise.allSettled(promises)
clientStarting = false

for (const result of results) {
if (result.status === "rejected") {
logger.error(`There was a error starting the server: ${result.reason}`)
}
}
}

Expand All @@ -187,6 +195,17 @@ async function stopLangServer(): Promise<void> {
client = undefined
}

function startDebugging(): Promise<void> {
if (!vscode.workspace.workspaceFolders) {
logger.error("Unable to start debugging, there is no workspace.")
return Promise.reject("Unable to start debugging, there is no workspace.")
}
// TODO: Is there a more reliable way to ensure the debug adapter is ready?
setTimeout(async () => {
await vscode.debug.startDebugging(vscode.workspace.workspaceFolders[0], "pygls: Debug Server")
}, 2000)
}

function getClientOptions(): LanguageClientOptions {
const config = vscode.workspace.getConfiguration('pygls.client')
const options = {
Expand Down Expand Up @@ -270,7 +289,7 @@ function getCwd(): string {

/**
*
* @returns The python script to launch the server with
* @returns The python script that implements the server.
*/
function getServerPath(): string {
const config = vscode.workspace.getConfiguration("pygls.server")
Expand All @@ -279,13 +298,49 @@ function getServerPath(): string {
}

/**
* Return the python command to use when starting the server.
*
* If debugging is enabled, this will also included the arguments to required
* to wrap the server in a debug adapter.
*
* @returns The full python command needed in order to start the server.
*/
async function getPythonCommand(resource?: vscode.Uri): Promise<string[] | undefined> {
const config = vscode.workspace.getConfiguration("pygls.server", resource)
const pythonPath = await getPythonInterpreter(resource)
if (!pythonPath) {
return
}
const command = [pythonPath]
const enableDebugger = config.get<boolean>('debug')

if (!enableDebugger) {
return command
}

const debugHost = config.get<string>('debugHost')
const debugPort = config.get<integer>('debugPort')
try {
const debugArgs = await python.debug.getRemoteLauncherCommand(debugHost, debugPort, true)
// Debugpy recommends we disable frozen modules
command.push("-Xfrozen_modules=off", ...debugArgs)
} catch (err) {
logger.error(`Unable to get debugger command: ${err}`)
logger.error("Debugger will not be available.")
}

return command
}

/**
* Return the python interpreter to use when starting the server.
*
* This uses the official python extension to grab the user's currently
* configured environment.
*
* @returns The python interpreter to use to launch the server
*/
async function getPythonPath(resource?: vscode.Uri): Promise<string | undefined> {

async function getPythonInterpreter(resource?: vscode.Uri): Promise<string | undefined> {
const config = vscode.workspace.getConfiguration("pygls.server", resource)
const pythonPath = config.get<string>('pythonPath')
if (pythonPath) {
Expand Down

0 comments on commit e19a459

Please sign in to comment.