9
9
IndentAction ,
10
10
languages ,
11
11
RelativePattern ,
12
- TextDocument ,
12
+ TextEditor ,
13
13
Uri ,
14
14
window ,
15
15
workspace ,
@@ -29,10 +29,8 @@ import { checkForRls, ensureToolchain, rustupUpdate } from './rustup';
29
29
import { startSpinner , stopSpinner } from './spinner' ;
30
30
import { activateTaskProvider , Execution , runRlsCommand } from './tasks' ;
31
31
import { withWsl } from './utils/child_process' ;
32
- import {
33
- getOuterMostWorkspaceFolder ,
34
- nearestParentWorkspace ,
35
- } from './utils/workspace' ;
32
+ import { Observable , Observer } from './utils/observable' ;
33
+ import { nearestParentWorkspace } from './utils/workspace' ;
36
34
import { uriWindowsToWsl , uriWslToWindows } from './utils/wslpath' ;
37
35
38
36
/**
@@ -48,17 +46,17 @@ interface ProgressParams {
48
46
}
49
47
50
48
export async function activate ( context : ExtensionContext ) {
51
- context . subscriptions . push ( configureLanguage ( ) ) ;
52
- context . subscriptions . push ( ... registerCommands ( ) ) ;
53
-
54
- workspace . onDidOpenTextDocument ( doc => whenOpeningTextDocument ( doc ) ) ;
55
- workspace . onDidChangeWorkspaceFolders ( e => whenChangingWorkspaceFolders ( e ) ) ;
56
- window . onDidChangeActiveTextEditor (
57
- ed => ed && whenOpeningTextDocument ( ed . document ) ,
49
+ context . subscriptions . push (
50
+ ... [
51
+ configureLanguage ( ) ,
52
+ ... registerCommands ( ) ,
53
+ workspace . onDidChangeWorkspaceFolders ( whenChangingWorkspaceFolders ) ,
54
+ window . onDidChangeActiveTextEditor ( onDidChangeActiveTextEditor ) ,
55
+ ] ,
58
56
) ;
59
- // Installed listeners don't fire immediately for already opened files, so
60
- // trigger an open event manually to fire up RLS instances where needed
61
- workspace . textDocuments . forEach ( whenOpeningTextDocument ) ;
57
+ // Manually trigger the first event to start up server instance if necessary,
58
+ // since VSCode doesn't do that on startup by itself.
59
+ onDidChangeActiveTextEditor ( window . activeTextEditor ) ;
62
60
63
61
// Migrate the users of multi-project setup for RLS to disable the setting
64
62
// entirely (it's always on now)
@@ -92,55 +90,38 @@ export async function deactivate() {
92
90
return Promise . all ( [ ...workspaces . values ( ) ] . map ( ws => ws . stop ( ) ) ) ;
93
91
}
94
92
95
- // Taken from https://github.com/Microsoft/vscode-extension-samples/blob/master/lsp-multi-server-sample/client/src/extension.ts
96
- function whenOpeningTextDocument ( document : TextDocument ) {
97
- if ( document . languageId !== 'rust' && document . languageId !== 'toml' ) {
98
- return ;
99
- }
93
+ /** Tracks dynamically updated progress for the active client workspace for UI purposes. */
94
+ const progressObserver : Observer < { message : string } | null > = new Observer ( ) ;
100
95
101
- let folder = workspace . getWorkspaceFolder ( document . uri ) ;
102
- if ( ! folder ) {
96
+ function onDidChangeActiveTextEditor ( editor : TextEditor | undefined ) {
97
+ if ( ! editor || ! editor . document ) {
103
98
return ;
104
99
}
100
+ const { languageId, uri } = editor . document ;
105
101
106
- folder = nearestParentWorkspace ( folder , document . uri . fsPath ) ;
107
-
108
- if ( ! folder ) {
109
- stopSpinner ( `RLS: Cargo.toml missing` ) ;
102
+ const workspace = clientWorkspaceForUri ( uri , {
103
+ startIfMissing : languageId === 'rust' || languageId === 'toml' ,
104
+ } ) ;
105
+ if ( ! workspace ) {
110
106
return ;
111
107
}
112
108
113
- const folderPath = folder . uri . toString ( ) ;
109
+ activeWorkspace = workspace ;
114
110
115
- if ( ! workspaces . has ( folderPath ) ) {
116
- const workspace = new ClientWorkspace ( folder ) ;
117
- activeWorkspace = workspace ;
118
- workspaces . set ( folderPath , workspace ) ;
119
- workspace . start ( ) ;
120
- } else {
121
- const ws = workspaces . get ( folderPath ) ;
122
- activeWorkspace = typeof ws === 'undefined' ? null : ws ;
123
- }
111
+ const updateProgress = ( progress : { message : string } | null ) => {
112
+ if ( progress ) {
113
+ startSpinner ( `[${ workspace . folder . name } ] ${ progress . message } ` ) ;
114
+ } else {
115
+ stopSpinner ( `[${ workspace . folder . name } ]` ) ;
116
+ }
117
+ } ;
118
+
119
+ progressObserver . bind ( activeWorkspace . progress , updateProgress ) ;
120
+ // Update UI ourselves immediately and don't wait for value update callbacks
121
+ updateProgress ( activeWorkspace . progress . value ) ;
124
122
}
125
123
126
124
function whenChangingWorkspaceFolders ( e : WorkspaceFoldersChangeEvent ) {
127
- // If a VSCode workspace has been added, check to see if it is part of an existing one, and
128
- // if not, and it is a Rust project (i.e., has a Cargo.toml), then create a new client.
129
- for ( let folder of e . added ) {
130
- folder = getOuterMostWorkspaceFolder ( folder ) ;
131
- if ( workspaces . has ( folder . uri . toString ( ) ) ) {
132
- continue ;
133
- }
134
- for ( const f of fs . readdirSync ( folder . uri . fsPath ) ) {
135
- if ( f === 'Cargo.toml' ) {
136
- const workspace = new ClientWorkspace ( folder ) ;
137
- workspaces . set ( folder . uri . toString ( ) , workspace ) ;
138
- workspace . start ( ) ;
139
- break ;
140
- }
141
- }
142
- }
143
-
144
125
// If a workspace is removed which is a Rust workspace, kill the client.
145
126
for ( const folder of e . removed ) {
146
127
const ws = workspaces . get ( folder . uri . toString ( ) ) ;
@@ -154,25 +135,58 @@ function whenChangingWorkspaceFolders(e: WorkspaceFoldersChangeEvent) {
154
135
// Don't use URI as it's unreliable the same path might not become the same URI.
155
136
const workspaces : Map < string , ClientWorkspace > = new Map ( ) ;
156
137
138
+ /**
139
+ * Fetches a `ClientWorkspace` for a given URI. If missing and `startIfMissing`
140
+ * option was provided, it is additionally initialized beforehand, if applicable.
141
+ */
142
+ function clientWorkspaceForUri (
143
+ uri : Uri ,
144
+ options ?: { startIfMissing : boolean } ,
145
+ ) : ClientWorkspace | undefined {
146
+ const rootFolder = workspace . getWorkspaceFolder ( uri ) ;
147
+ if ( ! rootFolder ) {
148
+ return ;
149
+ }
150
+
151
+ const folder = nearestParentWorkspace ( rootFolder , uri . fsPath ) ;
152
+ if ( ! folder ) {
153
+ return undefined ;
154
+ }
155
+
156
+ const existing = workspaces . get ( folder . uri . toString ( ) ) ;
157
+ if ( ! existing && options && options . startIfMissing ) {
158
+ const workspace = new ClientWorkspace ( folder ) ;
159
+ workspaces . set ( folder . uri . toString ( ) , workspace ) ;
160
+ workspace . start ( ) ;
161
+ }
162
+
163
+ return workspaces . get ( folder . uri . toString ( ) ) ;
164
+ }
165
+
157
166
// We run one RLS and one corresponding language client per workspace folder
158
167
// (VSCode workspace, not Cargo workspace). This class contains all the per-client
159
168
// and per-workspace stuff.
160
169
class ClientWorkspace {
170
+ public readonly folder : WorkspaceFolder ;
161
171
// FIXME(#233): Don't only rely on lazily initializing it once on startup,
162
172
// handle possible `rust-client.*` value changes while extension is running
163
173
private readonly config : RLSConfiguration ;
164
174
private lc : LanguageClient | null = null ;
165
- private readonly folder : WorkspaceFolder ;
166
175
private disposables : Disposable [ ] ;
176
+ private _progress : Observable < { message : string } | null > ;
177
+ get progress ( ) {
178
+ return this . _progress ;
179
+ }
167
180
168
181
constructor ( folder : WorkspaceFolder ) {
169
182
this . config = RLSConfiguration . loadFromWorkspace ( folder . uri . fsPath ) ;
170
183
this . folder = folder ;
171
184
this . disposables = [ ] ;
185
+ this . _progress = new Observable < { message : string } | null > ( null ) ;
172
186
}
173
187
174
188
public async start ( ) {
175
- startSpinner ( 'RLS' , 'Starting' ) ;
189
+ this . _progress . value = { message : 'Starting' } ;
176
190
177
191
const serverOptions : ServerOptions = async ( ) => {
178
192
await this . autoUpdate ( ) ;
@@ -273,7 +287,6 @@ class ClientWorkspace {
273
287
274
288
const runningProgress : Set < string > = new Set ( ) ;
275
289
await this . lc . onReady ( ) ;
276
- stopSpinner ( 'RLS' ) ;
277
290
278
291
this . lc . onNotification (
279
292
new NotificationType < ProgressParams , void > ( 'window/progress' ) ,
@@ -292,9 +305,9 @@ class ClientWorkspace {
292
305
} else if ( progress . title ) {
293
306
status = `[${ progress . title . toLowerCase ( ) } ]` ;
294
307
}
295
- startSpinner ( 'RLS' , status ) ;
308
+ this . _progress . value = { message : status } ;
296
309
} else {
297
- stopSpinner ( 'RLS' ) ;
310
+ this . _progress . value = null ;
298
311
}
299
312
} ,
300
313
) ;
0 commit comments