Skip to content
This repository was archived by the owner on Nov 18, 2022. It is now read-only.

Commit 360de06

Browse files
committed
Refactor engine spawning internals and support global servers
1 parent 8b3fd40 commit 360de06

File tree

4 files changed

+439
-261
lines changed

4 files changed

+439
-261
lines changed

src/extension.ts

Lines changed: 26 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import * as child_process from 'child_process';
2-
import * as fs from 'fs';
3-
import * as path from 'path';
4-
import * as util from 'util';
51
import {
62
commands,
73
ConfigurationTarget,
@@ -16,36 +12,17 @@ import {
1612
WorkspaceFolder,
1713
WorkspaceFoldersChangeEvent,
1814
} from 'vscode';
19-
import {
20-
LanguageClient,
21-
LanguageClientOptions,
22-
NotificationType,
23-
ServerOptions,
24-
} from 'vscode-languageclient';
15+
import * as lc from 'vscode-languageclient';
2516

2617
import { RLSConfiguration } from './configuration';
27-
import { SignatureHelpProvider } from './providers/signatureHelpProvider';
18+
import * as rls from './rls';
2819
import * as rustAnalyzer from './rustAnalyzer';
29-
import { checkForRls, ensureToolchain, rustupUpdate } from './rustup';
20+
import { rustupUpdate } from './rustup';
3021
import { startSpinner, stopSpinner } from './spinner';
3122
import { activateTaskProvider, Execution, runRlsCommand } from './tasks';
3223
import { Observable } from './utils/observable';
3324
import { nearestParentWorkspace } from './utils/workspace';
3425

35-
const exec = util.promisify(child_process.exec);
36-
37-
/**
38-
* Parameter type to `window/progress` request as issued by the RLS.
39-
* https://github.com/rust-lang/rls/blob/17a439440e6b00b1f014a49c6cf47752ecae5bb7/rls/src/lsp_data.rs#L395-L419
40-
*/
41-
interface ProgressParams {
42-
id: string;
43-
title?: string;
44-
message?: string;
45-
percentage?: number;
46-
done?: boolean;
47-
}
48-
4926
/**
5027
* External API as exposed by the extension. Can be queried by other extensions
5128
* or by the integration test runner for VSCode extensions.
@@ -187,19 +164,19 @@ function clientWorkspaceForUri(
187164
}
188165

189166
/** Denotes the state or progress the workspace is currently in. */
190-
type WorkspaceProgress =
167+
export type WorkspaceProgress =
191168
| { state: 'progress'; message: string }
192169
| { state: 'ready' | 'standby' };
193170

194-
// We run one RLS and one corresponding language client per workspace folder
195-
// (VSCode workspace, not Cargo workspace). This class contains all the per-client
196-
// and per-workspace stuff.
171+
// We run a single server/client pair per workspace folder (VSCode workspace,
172+
// not Cargo workspace). This class contains all the per-client and
173+
// per-workspace stuff.
197174
export class ClientWorkspace {
198175
public readonly folder: WorkspaceFolder;
199176
// FIXME(#233): Don't only rely on lazily initializing it once on startup,
200177
// handle possible `rust-client.*` value changes while extension is running
201178
private readonly config: RLSConfiguration;
202-
private lc: LanguageClient | null = null;
179+
private lc: lc.LanguageClient | null = null;
203180
private disposables: Disposable[];
204181
private _progress: Observable<WorkspaceProgress>;
205182
get progress() {
@@ -225,69 +202,29 @@ export class ClientWorkspace {
225202
public async start() {
226203
this._progress.value = { state: 'progress', message: 'Starting' };
227204

228-
const serverOptions: ServerOptions = async () => {
229-
await this.autoUpdate();
230-
const engine = this.config.engine;
231-
return engine === 'rust-analyzer'
232-
? rustAnalyzer
233-
.getServer({
234-
askBeforeDownload: true,
235-
package: { releaseTag: '2020-05-04' },
236-
})
237-
.then(binPath =>
238-
child_process.execFile(binPath!),
239-
) /* TODO: Handle possibly undefined RA */
240-
: this.makeRlsProcess();
241-
};
242-
243-
// This accepts `vscode.GlobPattern` under the hood, which requires only
244-
// forward slashes. It's worth mentioning that RelativePattern does *NOT*
245-
// work in remote scenarios (?), so rely on normalized fs path from VSCode URIs.
246-
const pattern = `${this.folder.uri.fsPath.replace(path.sep, '/')}/**`;
247-
248-
const clientOptions: LanguageClientOptions = {
249-
// Register the server for Rust files
250-
documentSelector: [
251-
{ language: 'rust', scheme: 'file', pattern },
252-
{ language: 'rust', scheme: 'untitled', pattern },
253-
],
254-
diagnosticCollectionName: `rust-${this.folder.uri}`,
255-
synchronize: { configurationSection: 'rust' },
256-
// Controls when to focus the channel rather than when to reveal it in the drop-down list
205+
const { createLanguageClient, setupClient, setupProgress } =
206+
this.config.engine === 'rls' ? rls : rustAnalyzer;
207+
208+
const client = await createLanguageClient(this.folder, {
209+
updateOnStartup: this.config.updateOnStartup,
257210
revealOutputChannelOn: this.config.revealOutputChannelOn,
258-
initializationOptions: {
259-
omitInitBuild: true,
260-
cmdRun: true,
211+
logToFile: this.config.logToFile,
212+
rustup: {
213+
channel: this.config.channel,
214+
path: this.config.rustupPath,
215+
disabled: this.config.rustupDisabled,
261216
},
262-
workspaceFolder: this.folder,
263-
};
264-
265-
// Create the language client and start the client.
266-
this.lc = new LanguageClient(
267-
'rust-client',
268-
'Rust Language Server',
269-
serverOptions,
270-
clientOptions,
271-
);
272-
273-
if (this.config.engine === 'rust-analyzer') {
274-
// Register for semantic tokens, among others
275-
this.lc.registerProposedFeatures();
276-
}
217+
rls: { path: this.config.rlsPath },
218+
rustAnalyzer: { releaseTag: '2020-05-04' },
219+
});
277220

278-
const selector = { language: 'rust', scheme: 'file', pattern };
221+
setupProgress(client, this._progress);
279222

280-
this.setupProgressCounter();
281223
this.disposables.push(activateTaskProvider(this.folder));
282-
this.disposables.push(this.lc.start());
283-
this.disposables.push(
284-
languages.registerSignatureHelpProvider(
285-
selector,
286-
new SignatureHelpProvider(this.lc),
287-
'(',
288-
',',
289-
),
290-
);
224+
this.disposables.push(...setupClient(client, this.folder));
225+
if (client.needsStart()) {
226+
this.disposables.push(client.start());
227+
}
291228
}
292229

293230
public async stop() {
@@ -312,154 +249,6 @@ export class ClientWorkspace {
312249
public rustupUpdate() {
313250
return rustupUpdate(this.config.rustupConfig());
314251
}
315-
316-
private async setupProgressCounter() {
317-
if (!this.lc) {
318-
return;
319-
}
320-
321-
const runningProgress: Set<string> = new Set();
322-
await this.lc.onReady();
323-
324-
this.lc.onNotification(
325-
new NotificationType<ProgressParams, void>('window/progress'),
326-
progress => {
327-
if (progress.done) {
328-
runningProgress.delete(progress.id);
329-
} else {
330-
runningProgress.add(progress.id);
331-
}
332-
if (runningProgress.size) {
333-
let status = '';
334-
if (typeof progress.percentage === 'number') {
335-
status = `${Math.round(progress.percentage * 100)}%`;
336-
} else if (progress.message) {
337-
status = progress.message;
338-
} else if (progress.title) {
339-
status = `[${progress.title.toLowerCase()}]`;
340-
}
341-
this._progress.value = { state: 'progress', message: status };
342-
} else {
343-
this._progress.value = { state: 'ready' };
344-
}
345-
},
346-
);
347-
}
348-
349-
private async getSysroot(env: typeof process.env): Promise<string> {
350-
const printSysrootCmd = this.config.rustupDisabled
351-
? 'rustc --print sysroot'
352-
: `${this.config.rustupPath} run ${this.config.channel} rustc --print sysroot`;
353-
354-
const { stdout } = await exec(printSysrootCmd, { env });
355-
return stdout.toString().trim();
356-
}
357-
358-
// Make an evironment to run the RLS.
359-
private async makeRlsEnv(
360-
args = {
361-
setLibPath: false,
362-
},
363-
): Promise<typeof process.env> {
364-
// Shallow clone, we don't want to modify this process' $PATH or
365-
// $(DY)LD_LIBRARY_PATH
366-
const env = { ...process.env };
367-
368-
let sysroot: string | undefined;
369-
try {
370-
sysroot = await this.getSysroot(env);
371-
} catch (err) {
372-
console.info(err.message);
373-
console.info(`Let's retry with extended $PATH`);
374-
env.PATH = `${env.HOME || '~'}/.cargo/bin:${env.PATH || ''}`;
375-
try {
376-
sysroot = await this.getSysroot(env);
377-
} catch (e) {
378-
console.warn('Error reading sysroot (second try)', e);
379-
window.showWarningMessage(`Error reading sysroot: ${e.message}`);
380-
return env;
381-
}
382-
}
383-
384-
console.info(`Setting sysroot to`, sysroot);
385-
if (args.setLibPath) {
386-
function appendEnv(envVar: string, newComponent: string) {
387-
const old = process.env[envVar];
388-
return old ? `${newComponent}:${old}` : newComponent;
389-
}
390-
const newComponent = path.join(sysroot, 'lib');
391-
env.DYLD_LIBRARY_PATH = appendEnv('DYLD_LIBRARY_PATH', newComponent);
392-
env.LD_LIBRARY_PATH = appendEnv('LD_LIBRARY_PATH', newComponent);
393-
}
394-
395-
return env;
396-
}
397-
398-
private async makeRlsProcess(): Promise<child_process.ChildProcess> {
399-
// Run "rls" from the PATH unless there's an override.
400-
const rlsPath = this.config.rlsPath || 'rls';
401-
402-
// We don't need to set [DY]LD_LIBRARY_PATH if we're using rustup,
403-
// as rustup will set it for us when it chooses a toolchain.
404-
// NOTE: Needs an installed toolchain when using rustup, hence we don't call
405-
// it immediately here.
406-
const makeRlsEnv = () =>
407-
this.makeRlsEnv({
408-
setLibPath: this.config.rustupDisabled,
409-
});
410-
const cwd = this.folder.uri.fsPath;
411-
412-
let childProcess: child_process.ChildProcess;
413-
if (this.config.rustupDisabled) {
414-
console.info(`running without rustup: ${rlsPath}`);
415-
const env = await makeRlsEnv();
416-
417-
childProcess = child_process.spawn(rlsPath, [], {
418-
env,
419-
cwd,
420-
shell: true,
421-
});
422-
} else {
423-
console.info(`running with rustup: ${rlsPath}`);
424-
const config = this.config.rustupConfig();
425-
426-
await ensureToolchain(config);
427-
if (!this.config.rlsPath) {
428-
// We only need a rustup-installed RLS if we weren't given a
429-
// custom RLS path.
430-
console.info('will use a rustup-installed RLS; ensuring present');
431-
await checkForRls(config);
432-
}
433-
434-
const env = await makeRlsEnv();
435-
childProcess = child_process.spawn(
436-
config.path,
437-
['run', config.channel, rlsPath],
438-
{ env, cwd, shell: true },
439-
);
440-
}
441-
442-
childProcess.on('error', (err: { code?: string; message: string }) => {
443-
if (err.code === 'ENOENT') {
444-
console.error(`Could not spawn RLS: ${err.message}`);
445-
window.showWarningMessage(`Could not spawn RLS: \`${err.message}\``);
446-
}
447-
});
448-
449-
if (this.config.logToFile) {
450-
const logPath = path.join(this.folder.uri.fsPath, `rls${Date.now()}.log`);
451-
const logStream = fs.createWriteStream(logPath, { flags: 'w+' });
452-
childProcess.stderr?.pipe(logStream);
453-
}
454-
455-
return childProcess;
456-
}
457-
458-
private async autoUpdate() {
459-
if (this.config.updateOnStartup && !this.config.rustupDisabled) {
460-
await rustupUpdate(this.config.rustupConfig());
461-
}
462-
}
463252
}
464253

465254
/**

0 commit comments

Comments
 (0)