Skip to content

Commit

Permalink
Allow custom command to be excecuted for LSP, and run it by shell. FIxes
Browse files Browse the repository at this point in the history
Tal Hadad committed Jun 27, 2023
1 parent 3779678 commit 3c24715
Showing 5 changed files with 130 additions and 211 deletions.
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1511,6 +1511,26 @@
"default": true,
"markdownDescription": "Use multiple language servers for [multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces). If disabled, only one language server will be used to handle all requests from all workspaces and files."
},
"r.lsp.bootstrapFile": {
"type": "string",
"default": "",
"description": "The bootstrap R script file for the LSP server. If empty, loads the default one that is packaged in this extension."
},
"r.lsp.invokeCommand.windows": {
"type": "string",
"default": "'${rpath}' ${r.lsp.args} --silent --slave --no-save --no-restore -e 'base::source(base::commandArgs(TRUE))' --args '${r.lsp.bootstrapFile}'",
"description": "The shell command to run LSP (Windows)"
},
"r.lsp.invokeCommand.mac": {
"type": "string",
"default": "'${rpath}' ${r.lsp.args} --silent --slave --no-save --no-restore -e 'base::source(base::commandArgs(TRUE))' --args '${r.lsp.bootstrapFile}'",
"description": "The shell command to run LSP (Mac)"
},
"r.lsp.invokeCommand.linux": {
"type": "string",
"default": "'${rpath}' ${r.lsp.args} --silent --slave --no-save --no-restore -e 'base::source(base::commandArgs(TRUE))' --args '${r.lsp.bootstrapFile}'",
"description": "The shell command to run LSP (Linux)"
},
"r.rmarkdown.codeLensCommands": {
"type": "array",
"items": {
4 changes: 2 additions & 2 deletions src/helpViewer/index.ts
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ import {
getRpath,
doWithProgress,
DummyMemento,
getRPathConfigEntry,
getOSConfigEntry,
escapeHtml,
makeWebviewCommandUriString,
uniqueEntries,
@@ -533,7 +533,7 @@ export class RHelp implements api.HelpPanel, vscode.WebviewPanelSerializer<strin
);
if (!aliases) {
void vscode.window.showErrorMessage(
`Failed to get list of R functions. Make sure that \`jsonlite\` is installed and r.${getRPathConfigEntry()} points to a valid R executable.`,
`Failed to get list of R functions. Make sure that \`jsonlite\` is installed and r.${getOSConfigEntry('rpath')} points to a valid R executable.`,
);
return undefined;
}
30 changes: 13 additions & 17 deletions src/languageService.ts
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import * as net from 'net';
import { URL } from 'url';
import { LanguageClient, LanguageClientOptions, StreamInfo, DocumentFilter, ErrorAction, CloseAction, RevealOutputChannelOn } from 'vscode-languageclient/node';
import { Disposable, workspace, Uri, TextDocument, WorkspaceConfiguration, OutputChannel, window, WorkspaceFolder } from 'vscode';
import { DisposableProcess, getRLibPaths, getRpath, promptToInstallRPackage, spawn } from './util';
import { DisposableProcess, getRLibPaths, getRpath, getInvokeCommand, promptToInstallRPackage, spawn } from './util';
import { extensionContext } from './extension';
import { CommonOptions } from 'child_process';

@@ -26,8 +26,8 @@ export class LanguageService implements Disposable {
return this.stopLanguageService();
}

private spawnServer(client: LanguageClient, rPath: string, args: readonly string[], options: CommonOptions & { cwd: string }): DisposableProcess {
const childProcess = spawn(rPath, args, options);
private spawnServer(client: LanguageClient, command: string, options: CommonOptions & { cwd: string }): DisposableProcess {
const childProcess = spawn(command, undefined, options);
const pid = childProcess.pid || -1;
client.outputChannel.appendLine(`R Language Server (${pid}) started`);
childProcess.stderr.on('data', (chunk: Buffer) => {
@@ -80,18 +80,14 @@ export class LanguageService implements Disposable {
console.log(`LANG: ${env.LANG}`);
}

const rScriptPath = extensionContext.asAbsolutePath('R/languageServer.R');
const options = { cwd: cwd, env: env };
const args = (config.get<string[]>('lsp.args') ?? []).concat(
'--silent',
'--slave',
'--no-save',
'--no-restore',
'-e',
'base::source(base::commandArgs(TRUE))',
'--args',
rScriptPath
);
const rScriptPath = config.get<string>('lsp.bootstrapFile') || extensionContext.asAbsolutePath('R/languageServer.R');
const rLspArgs = config.get<string>('${lsp.args}') ?? '';
const options = { cwd: cwd, env: env, shell: true };

const commandExp = getInvokeCommand() ?? ''; // TODO: Abort gracefully
const command = commandExp.replaceAll('${rpath}', rPath).replaceAll('${r.lsp.args}', rLspArgs).replaceAll('${r.lsp.bootstrapFile}', rScriptPath);

console.log("Language Server Command: " + command)

const tcpServerOptions = () => new Promise<DisposableProcess | StreamInfo>((resolve, reject) => {
// Use a TCP socket because of problems with blocking STDIO
@@ -112,7 +108,7 @@ export class LanguageService implements Disposable {
server.listen(0, '127.0.0.1', () => {
const port = (server.address() as net.AddressInfo).port;
env.VSCR_LSP_PORT = String(port);
return this.spawnServer(client, rPath, args, options);
return this.spawnServer(client, command, options);
});
});

@@ -150,7 +146,7 @@ export class LanguageService implements Disposable {

// Create the language client and start the client.
if (use_stdio && process.platform !== 'win32') {
client = new LanguageClient('r', 'R Language Server', { command: rPath, args: args, options: options }, clientOptions);
client = new LanguageClient('r', 'R Language Server', { command: command, options: options }, clientOptions);
} else {
client = new LanguageClient('r', 'R Language Server', tcpServerOptions, clientOptions);
}
19 changes: 15 additions & 4 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -58,8 +58,7 @@ export async function getRpathFromSystem(): Promise<string> {
return rpath;
}

export function getRPathConfigEntry(term: boolean = false): string {
const trunc = (term ? 'rterm' : 'rpath');
export function getOSConfigEntry(trunc: 'rpath' | 'rterm' | 'lsp.invokeCommand'): string {
const platform = (
process.platform === 'win32' ? 'windows' :
process.platform === 'darwin' ? 'mac' :
@@ -77,7 +76,7 @@ export async function getRpath(quote = false, overwriteConfig?: string): Promise
}

// try the os-specific config entry for the rpath:
const configEntry = getRPathConfigEntry();
const configEntry = getOSConfigEntry('rpath');
rpath ||= config().get<string>(configEntry);

// read from path/registry:
@@ -104,7 +103,7 @@ export async function getRpath(quote = false, overwriteConfig?: string): Promise
}

export async function getRterm(): Promise<string | undefined> {
const configEntry = getRPathConfigEntry(true);
const configEntry = getOSConfigEntry('rterm');
let rpath = config().get<string>(configEntry);

rpath ||= await getRpathFromSystem();
@@ -117,6 +116,18 @@ export async function getRterm(): Promise<string | undefined> {
return undefined;
}

export function getInvokeCommand(): string | undefined {
const configEntry = getOSConfigEntry('lsp.invokeCommand');
let invokeCommand = config().get<string>(configEntry);

if (invokeCommand !== '') {
return invokeCommand;
}

void vscode.window.showErrorMessage(`Cannot launch R language server. Change setting r.${configEntry} to R language server command.`);
return undefined;
}

export function ToRStringLiteral(s: string, quote: string): string {
if (s === undefined) {
return 'NULL';
268 changes: 80 additions & 188 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 3c24715

Please sign in to comment.