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

Allow custom command to be excecuted for LSP, and run it by shell #1387

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,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": {
Expand Down
4 changes: 2 additions & 2 deletions src/helpViewer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getRpath,
doWithProgress,
DummyMemento,
getRPathConfigEntry,
getOSConfigEntry,
escapeHtml,
makeWebviewCommandUriString,
uniqueEntries,
Expand Down Expand Up @@ -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;
}
Expand Down
30 changes: 13 additions & 17 deletions src/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, substituteVariables } from './util';
import { DisposableProcess, getRLibPaths, getRpath, getInvokeCommand, promptToInstallRPackage, spawn, substituteVariables } from './util';
import { extensionContext } from './extension';
import { CommonOptions } from 'child_process';

Expand All @@ -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) => {
Expand Down Expand Up @@ -82,18 +82,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')?.map(substituteVariables) ?? []).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')?.map(substituteVariables) ?? []).map((str) => JSON.stringify(str)).join(' ');
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
Expand All @@ -114,7 +110,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);
});
});

Expand Down Expand Up @@ -152,7 +148,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);
}
Expand Down
3 changes: 2 additions & 1 deletion src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { purgeAddinPickerItems, dispatchRStudioAPICall } from './rstudioapi';

import { IRequest } from './liveShare/shareSession';
import { homeExtDir, rWorkspace, globalRHelp, globalHttpgdManager, extensionContext, sessionStatusBarItem } from './extension';
import { UUID, rHostService, rGuestService, isLiveShare, isHost, isGuestSession, closeBrowser, guestResDir, shareBrowser, openVirtualDoc, shareWorkspace } from './liveShare';
import { UUID, rHostService, rGuestService, isLiveShare, isHost, isGuestSession, closeBrowser, guestResDir, shareBrowser, shareWorkspace } from './liveShare';
import { openVirtualDoc } from './liveShare/virtualDocs';

export interface GlobalEnv {
[key: string]: {
Expand Down
19 changes: 15 additions & 4 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,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' :
Expand All @@ -104,7 +103,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);
rpath &&= substituteVariables(rpath);

Expand Down Expand Up @@ -132,7 +131,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 &&= substituteVariables(rpath);
rpath ||= await getRpathFromSystem();
Expand All @@ -145,6 +144,18 @@ export async function getRterm(): Promise<string | undefined> {
return undefined;
}

export function getInvokeCommand(): string | undefined {
const configEntry = getOSConfigEntry('lsp.invokeCommand');
const 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';
Expand Down
Loading
Loading