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' ;
5
1
import {
6
2
commands ,
7
3
ConfigurationTarget ,
@@ -16,36 +12,17 @@ import {
16
12
WorkspaceFolder ,
17
13
WorkspaceFoldersChangeEvent ,
18
14
} from 'vscode' ;
19
- import {
20
- LanguageClient ,
21
- LanguageClientOptions ,
22
- NotificationType ,
23
- ServerOptions ,
24
- } from 'vscode-languageclient' ;
15
+ import * as lc from 'vscode-languageclient' ;
25
16
26
17
import { RLSConfiguration } from './configuration' ;
27
- import { SignatureHelpProvider } from './providers/signatureHelpProvider ' ;
18
+ import * as rls from './rls ' ;
28
19
import * as rustAnalyzer from './rustAnalyzer' ;
29
- import { checkForRls , ensureToolchain , rustupUpdate } from './rustup' ;
20
+ import { rustupUpdate } from './rustup' ;
30
21
import { startSpinner , stopSpinner } from './spinner' ;
31
22
import { activateTaskProvider , Execution , runRlsCommand } from './tasks' ;
32
23
import { Observable } from './utils/observable' ;
33
24
import { nearestParentWorkspace } from './utils/workspace' ;
34
25
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
-
49
26
/**
50
27
* External API as exposed by the extension. Can be queried by other extensions
51
28
* or by the integration test runner for VSCode extensions.
@@ -187,19 +164,19 @@ function clientWorkspaceForUri(
187
164
}
188
165
189
166
/** Denotes the state or progress the workspace is currently in. */
190
- type WorkspaceProgress =
167
+ export type WorkspaceProgress =
191
168
| { state : 'progress' ; message : string }
192
169
| { state : 'ready' | 'standby' } ;
193
170
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.
197
174
export class ClientWorkspace {
198
175
public readonly folder : WorkspaceFolder ;
199
176
// FIXME(#233): Don't only rely on lazily initializing it once on startup,
200
177
// handle possible `rust-client.*` value changes while extension is running
201
178
private readonly config : RLSConfiguration ;
202
- private lc : LanguageClient | null = null ;
179
+ private lc : lc . LanguageClient | null = null ;
203
180
private disposables : Disposable [ ] ;
204
181
private _progress : Observable < WorkspaceProgress > ;
205
182
get progress ( ) {
@@ -225,69 +202,29 @@ export class ClientWorkspace {
225
202
public async start ( ) {
226
203
this . _progress . value = { state : 'progress' , message : 'Starting' } ;
227
204
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 ,
257
210
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 ,
261
216
} ,
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
+ } ) ;
277
220
278
- const selector = { language : 'rust' , scheme : 'file' , pattern } ;
221
+ setupProgress ( client , this . _progress ) ;
279
222
280
- this . setupProgressCounter ( ) ;
281
223
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
+ }
291
228
}
292
229
293
230
public async stop ( ) {
@@ -312,154 +249,6 @@ export class ClientWorkspace {
312
249
public rustupUpdate ( ) {
313
250
return rustupUpdate ( this . config . rustupConfig ( ) ) ;
314
251
}
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
- }
463
252
}
464
253
465
254
/**
0 commit comments