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

Commit e63d147

Browse files
committed
Dynamically show progress only for the active client workspace
1 parent 22d70b7 commit e63d147

File tree

3 files changed

+99
-7
lines changed

3 files changed

+99
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Unreleased
22

3+
* Dynamically show progress only for the active client workspace
34
* Correctly run tasks based on active text editor rather than last opened Rust file
45
* (!) Don't immediately try to start server instance for newly added workspace folders
56
* Use smooth, universally supported spinner in the status bar ⚙️

src/extension.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { checkForRls, ensureToolchain, rustupUpdate } from './rustup';
3030
import { startSpinner, stopSpinner } from './spinner';
3131
import { activateTaskProvider, Execution, runRlsCommand } from './tasks';
3232
import { withWsl } from './utils/child_process';
33+
import { Observable, Observer } from './utils/observable';
3334
import { nearestParentWorkspace } from './utils/workspace';
3435
import { uriWindowsToWsl, uriWslToWindows } from './utils/wslpath';
3536

@@ -102,15 +103,32 @@ function whenOpeningTextDocument(document: TextDocument) {
102103
clientWorkspaceForUri(document.uri, { startIfMissing: true });
103104
}
104105

106+
/** Tracks dynamically updated progress for the active client workspace for UI purposes. */
107+
const progressObserver: Observer<{ message: string } | null> = new Observer();
108+
105109
function onDidChangeActiveTextEditor(editor: TextEditor | undefined) {
106110
if (!editor) {
107111
return;
108112
}
109113

110114
const workspace = clientWorkspaceForUri(editor.document.uri);
111-
if (workspace) {
112-
activeWorkspace = workspace;
115+
if (!workspace) {
116+
return;
113117
}
118+
119+
activeWorkspace = workspace;
120+
121+
const updateProgress = (progress: { message: string } | null) => {
122+
if (progress) {
123+
startSpinner(`[${workspace.folder.name}] ${progress.message}`);
124+
} else {
125+
stopSpinner(`[${workspace.folder.name}]`);
126+
}
127+
};
128+
129+
progressObserver.bind(activeWorkspace.progress, updateProgress);
130+
// Update UI ourselves immediately and don't wait for value update callbacks
131+
updateProgress(activeWorkspace.progress.value);
114132
}
115133

116134
function whenChangingWorkspaceFolders(e: WorkspaceFoldersChangeEvent) {
@@ -159,21 +177,26 @@ function clientWorkspaceForUri(
159177
// (VSCode workspace, not Cargo workspace). This class contains all the per-client
160178
// and per-workspace stuff.
161179
class ClientWorkspace {
180+
public readonly folder: WorkspaceFolder;
162181
// FIXME(#233): Don't only rely on lazily initializing it once on startup,
163182
// handle possible `rust-client.*` value changes while extension is running
164183
private readonly config: RLSConfiguration;
165184
private lc: LanguageClient | null = null;
166-
private readonly folder: WorkspaceFolder;
167185
private disposables: Disposable[];
186+
private _progress: Observable<{ message: string } | null>;
187+
get progress() {
188+
return this._progress;
189+
}
168190

169191
constructor(folder: WorkspaceFolder) {
170192
this.config = RLSConfiguration.loadFromWorkspace(folder.uri.fsPath);
171193
this.folder = folder;
172194
this.disposables = [];
195+
this._progress = new Observable<{ message: string } | null>(null);
173196
}
174197

175198
public async start() {
176-
startSpinner('Starting');
199+
this._progress.value = { message: 'Starting' };
177200

178201
const serverOptions: ServerOptions = async () => {
179202
await this.autoUpdate();
@@ -274,7 +297,6 @@ class ClientWorkspace {
274297

275298
const runningProgress: Set<string> = new Set();
276299
await this.lc.onReady();
277-
stopSpinner();
278300

279301
this.lc.onNotification(
280302
new NotificationType<ProgressParams, void>('window/progress'),
@@ -293,9 +315,9 @@ class ClientWorkspace {
293315
} else if (progress.title) {
294316
status = `[${progress.title.toLowerCase()}]`;
295317
}
296-
startSpinner(status);
318+
this._progress.value = { message: status };
297319
} else {
298-
stopSpinner();
320+
this._progress.value = null;
299321
}
300322
},
301323
);

src/utils/observable.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* A wrapper around a value of type `T` that can be subscribed to whenever the
3+
* underlying value changes.
4+
*/
5+
export class Observable<T> {
6+
private _listeners: Set<(arg: T) => void> = new Set();
7+
private _value: T;
8+
/** Returns the current value. */
9+
get value() {
10+
return this._value;
11+
}
12+
/** Every change to the value triggers all the registered callbacks. */
13+
set value(value: T) {
14+
this._value = value;
15+
this._listeners.forEach(fn => fn(value));
16+
}
17+
18+
constructor(value: T) {
19+
this._value = value;
20+
}
21+
22+
/**
23+
* Registers a listener function that's called whenever the underlying value
24+
* changes.
25+
* @returns a function that unregisters the listener when called.
26+
*/
27+
public observe(fn: (arg: T) => void): () => void {
28+
this._listeners.add(fn);
29+
30+
return () => this._listeners.delete(fn);
31+
}
32+
}
33+
34+
/**
35+
* Capable of observing an `Observable<T>` type.
36+
*
37+
* Convenient when using a single observer that potentially binds multiple times
38+
* to different observables, where it automatically unregisters from previous
39+
* observables.
40+
*/
41+
// tslint:disable-next-line: max-classes-per-file
42+
export class Observer<T> {
43+
private _observable?: Observable<T>;
44+
private _stopObserving?: () => void;
45+
/** Returns the current value of a bound observable, if there is one. */
46+
get value() {
47+
return this._observable && this._observable.value;
48+
}
49+
/**
50+
* Binds to an observable value, along with the provided listener that's
51+
* called whenever the underlying value changes.
52+
*/
53+
public bind(observable: Observable<T>, handler: (arg: T) => void) {
54+
this.stop();
55+
56+
this._observable = observable;
57+
this._stopObserving = observable.observe(handler);
58+
}
59+
/** Unbinds from the observable, deregistering the previously bound callback. */
60+
public stop() {
61+
if (this._stopObserving) {
62+
this._stopObserving();
63+
delete this._stopObserving;
64+
}
65+
if (this._observable) {
66+
delete this._observable;
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)