Skip to content
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

Add support for debugging servers in the VSCode Playground #425

Merged
merged 2 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
22 changes: 21 additions & 1 deletion 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 All @@ -73,12 +91,14 @@
"default": "off",
"enum": [
"off",
"messages",
"verbose"
],
"description": "Controls if LSP messages send to/from the server should be logged.",
"enumDescriptions": [
"do not log any lsp messages",
"log all lsp messages sent to/from the server"
"log all lsp messages sent to/from the server",
"log all lsp messages sent to/from the server, including their contents"
]
}
}
Expand Down
86 changes: 70 additions & 16 deletions examples/vscode-playground/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,12 @@
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")

// Some other nice to haves.
// TODO: Check selected env satisfies pygls' requirements - if not offer to run the select env command.
// TODO: Start a debug session for the currently configured server.
// TODO: TCP Transport
// TODO: WS Transport
// TODO: Web Extension support (requires WASM-WASI!)
Expand Down Expand Up @@ -144,33 +143,41 @@
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,10 +194,21 @@
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 = {
documentSelector: config.get<any>('documentSelector'),

Check warning on line 211 in examples/vscode-playground/src/extension.ts

View workflow job for this annotation

GitHub Actions / build

Unexpected any. Specify a different type
outputChannel: logger,
connectionOptions: {
maxRestartCount: 0 // don't restart on server failure.
Expand All @@ -200,7 +218,7 @@
return options
}

function startLangServerTCP(addr: number): LanguageClient {

Check warning on line 221 in examples/vscode-playground/src/extension.ts

View workflow job for this annotation

GitHub Actions / build

'startLangServerTCP' is defined but never used
const serverOptions: ServerOptions = () => {
return new Promise((resolve /*, reject */) => {
const clientSocket = new net.Socket();
Expand Down Expand Up @@ -270,7 +288,7 @@

/**
*
* @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 +297,49 @@
}

/**
* 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
Loading