Skip to content
Open
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
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,27 @@ The `lspprotocol.lpk` package and `pasls.lpi` are both in the
`pascallanguageserver.lpg`project group; if you have project group support enabled,
then you can use this to compile this package and the executable.

## Run

When the Pascal Language Server is started as stand-alone executable, it starts
listening for incoming network connections on a configurable port. So that any
client/editor can connect to it.

When the application is started as a sub-proces by a client/editor, it will
automatically detect this and use standard input to communicate with the
client.

## Debugging the LSP server

### The problem
To debug the Pascal language Server, just run it in the debugger and let
the editor/client connect to it usint tcp/ip.

### Another way to debug

VS Code and other editors that use the LSP server start the LSP server and
send messages in JSON-RPC style to standard input, and read replies through
standard output. This makes the LSP server process hard to debug.
standard output. When the regular way to run the LSP server in tcp/ip mode
is not suitable, this makes the LSP server process hard to debug.

### The solution
To solve this, 2 extra projects have been added:
Expand Down
25 changes: 24 additions & 1 deletion clients/pasls-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,32 @@
"default": "off",
"description": "Traces the communication between VS Code and the language server."
},
"pascalLanguageServer.transport": {
"type": "string",
"scope": "application",
"default": "stdio",
"enum": [
"stdio",
"tcp"
],
"description": "Communication transport used between VS Code and the language server."
},
"pascalLanguageServer.tcp.host": {
"type": "string",
"scope": "application",
"default": "127.0.0.1",
"description": "Host to connect to when `pascalLanguageServer.transport` is `tcp`."
},
"pascalLanguageServer.tcp.port": {
"type": "number",
"scope": "application",
"default": 4002,
"description": "Port to connect to when `pascalLanguageServer.transport` is `tcp`."
},
"pascalLanguageServer.executable": {
"type": "string",
"default": "/usr/local/bin/pasls",
"description": "Path to the language server executable."
"description": "Path to the language server executable that is started when `pascalLanguageServer.transport` is `stdio`."
},
"pascalLanguageServer.initializationOptions.program": {
"type": "string",
Expand Down Expand Up @@ -221,6 +243,7 @@
"build": "vsce package"
},
"dependencies": {
"vsce": "^2.15.0",
"vscode-languageclient": "^6.1.3"
},
"devDependencies": {
Expand Down
89 changes: 77 additions & 12 deletions clients/pasls-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import {
ServerOptions,
NotificationType
} from 'vscode-languageclient';
import { StreamMessageReader, StreamMessageWriter } from 'vscode-languageserver-protocol';
import * as fs from 'fs';
import * as net from 'net';
import {
InputRegion ,
DecorationRangesPair,
Expand Down Expand Up @@ -59,6 +61,7 @@ const InactiveRegionNotification: NotificationType<InactiveRegionParams> = new N
let client: LanguageClient;
let completecmd: Command;
let inactiveRegionsDecorations = new Map<string, DecorationRangesPair>();
let tcpSocket: net.Socket | undefined;

function invokeFormat(document: TextDocument, range: Range) {
let activeEditor = window.activeTextEditor;
Expand Down Expand Up @@ -236,17 +239,70 @@ export function activate(context: ExtensionContext) {
});


let run: Executable = {
command: executable,
options: {
env: userEnvironmentVariables
}
};
let debug: Executable = run;
let serverOptions: ServerOptions = {
run: run,
debug: debug
};
let transport: string = workspace.getConfiguration('pascalLanguageServer').get('transport') || 'stdio';
transport = transport.toLowerCase();
let tcpHost: string = workspace.getConfiguration('pascalLanguageServer').get('tcp.host') || '127.0.0.1';
let tcpPort: number = workspace.getConfiguration('pascalLanguageServer').get('tcp.port') || 4002;

let serverOptions: ServerOptions;

if (transport === 'tcp') {
serverOptions = () => {
return new Promise((resolve, reject) => {
let settled = false;

const startTime = Date.now();
const maxWaitMs = 10_000;
const retryDelayMs = 200;

const attemptConnect = () => {
if (settled) return;

let socket = net.connect({ host: tcpHost, port: tcpPort });
tcpSocket = socket;
socket.setNoDelay(true);

socket.once('connect', () => {
if (settled) return;
settled = true;

resolve({
detached: false,
reader: new StreamMessageReader(socket),
writer: new StreamMessageWriter(socket)
} as any);
});

socket.once('error', (err: Error) => {
socket.destroy();
tcpSocket = undefined;

if (settled) return;
if (Date.now() - startTime >= maxWaitMs) {
settled = true;
reject(err);
return;
}
setTimeout(attemptConnect, retryDelayMs);
});
};

attemptConnect();
});
};
} else {
let run: Executable = {
command: executable,
options: {
env: userEnvironmentVariables
}
};
let debug: Executable = run;
serverOptions = {
run: run,
debug: debug
};
}

let initializationOptions = workspace.getConfiguration('pascalLanguageServer.initializationOptions');

Expand Down Expand Up @@ -313,7 +369,16 @@ export function activate(context: ExtensionContext) {

export function deactivate(): Thenable<void> | undefined {
if (!client) {
if (tcpSocket) {
tcpSocket.destroy();
tcpSocket = undefined;
}
return undefined;
}
return client.stop();
return client.stop().then(() => {
if (tcpSocket) {
tcpSocket.destroy();
tcpSocket = undefined;
}
});
}
Loading
Loading