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

Commit 3cfc22a

Browse files
committed
Weave in global state when constructing lang server
1 parent 94ed102 commit 3cfc22a

File tree

3 files changed

+102
-35
lines changed

3 files changed

+102
-35
lines changed

src/extension.ts

+32-17
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
workspace,
1212
WorkspaceFolder,
1313
WorkspaceFoldersChangeEvent,
14+
Memento,
1415
} from 'vscode';
1516
import * as lc from 'vscode-languageclient';
1617

@@ -32,17 +33,21 @@ export interface Api {
3233
}
3334

3435
export async function activate(context: ExtensionContext): Promise<Api> {
36+
// Weave in global state when handling changed active text editor
37+
const handleChangedActiveTextEd = (ed: TextEditor | undefined) =>
38+
onDidChangeActiveTextEditor(ed, context.globalState);
39+
3540
context.subscriptions.push(
3641
...[
3742
configureLanguage(),
3843
...registerCommands(),
3944
workspace.onDidChangeWorkspaceFolders(whenChangingWorkspaceFolders),
40-
window.onDidChangeActiveTextEditor(onDidChangeActiveTextEditor),
45+
window.onDidChangeActiveTextEditor(handleChangedActiveTextEd),
4146
],
4247
);
4348
// Manually trigger the first event to start up server instance if necessary,
4449
// since VSCode doesn't do that on startup by itself.
45-
onDidChangeActiveTextEditor(window.activeTextEditor);
50+
handleChangedActiveTextEd(window.activeTextEditor);
4651

4752
// Migrate the users of multi-project setup for RLS to disable the setting
4853
// entirely (it's always on now)
@@ -81,13 +86,16 @@ export async function deactivate() {
8186
/** Tracks dynamically updated progress for the active client workspace for UI purposes. */
8287
let progressObserver: Disposable | undefined;
8388

84-
function onDidChangeActiveTextEditor(editor: TextEditor | undefined) {
89+
function onDidChangeActiveTextEditor(
90+
editor: TextEditor | undefined,
91+
globalState: Memento,
92+
) {
8593
if (!editor || !editor.document) {
8694
return;
8795
}
8896
const { languageId, uri } = editor.document;
8997

90-
const workspace = clientWorkspaceForUri(uri, {
98+
const workspace = clientWorkspaceForUri(uri, globalState, {
9199
initializeIfMissing: languageId === 'rust' || languageId === 'toml',
92100
});
93101
if (!workspace) {
@@ -135,6 +143,7 @@ const workspaces: Map<string, ClientWorkspace> = new Map();
135143
*/
136144
function clientWorkspaceForUri(
137145
uri: Uri,
146+
globalState: Memento,
138147
options?: { initializeIfMissing: boolean },
139148
): ClientWorkspace | undefined {
140149
const rootFolder = workspace.getWorkspaceFolder(uri);
@@ -149,7 +158,7 @@ function clientWorkspaceForUri(
149158

150159
const existing = workspaces.get(folder.uri.toString());
151160
if (!existing && options && options.initializeIfMissing) {
152-
const workspace = new ClientWorkspace(folder);
161+
const workspace = new ClientWorkspace(folder, globalState);
153162
workspaces.set(folder.uri.toString(), workspace);
154163
workspace.autoStart();
155164
}
@@ -173,15 +182,17 @@ export class ClientWorkspace {
173182
private lc: lc.LanguageClient | null = null;
174183
private disposables: Disposable[];
175184
private _progress: Observable<WorkspaceProgress>;
185+
private globalState: Memento;
176186
get progress() {
177187
return this._progress;
178188
}
179189

180-
constructor(folder: WorkspaceFolder) {
190+
constructor(folder: WorkspaceFolder, globalState: Memento) {
181191
this.config = RLSConfiguration.loadFromWorkspace(folder.uri.fsPath);
182192
this.folder = folder;
183193
this.disposables = [];
184194
this._progress = new Observable<WorkspaceProgress>({ state: 'standby' });
195+
this.globalState = globalState;
185196
}
186197

187198
/**
@@ -198,18 +209,22 @@ export class ClientWorkspace {
198209
const { createLanguageClient, setupClient, setupProgress } =
199210
this.config.engine === 'rls' ? rls : rustAnalyzer;
200211

201-
const client = await createLanguageClient(this.folder, {
202-
updateOnStartup: this.config.updateOnStartup,
203-
revealOutputChannelOn: this.config.revealOutputChannelOn,
204-
logToFile: this.config.logToFile,
205-
rustup: {
206-
channel: this.config.channel,
207-
path: this.config.rustupPath,
208-
disabled: this.config.rustupDisabled,
212+
const client = await createLanguageClient(
213+
this.folder,
214+
{
215+
updateOnStartup: this.config.updateOnStartup,
216+
revealOutputChannelOn: this.config.revealOutputChannelOn,
217+
logToFile: this.config.logToFile,
218+
rustup: {
219+
channel: this.config.channel,
220+
path: this.config.rustupPath,
221+
disabled: this.config.rustupDisabled,
222+
},
223+
rls: { path: this.config.rlsPath },
224+
rustAnalyzer: this.config.rustAnalyzer,
209225
},
210-
rls: { path: this.config.rlsPath },
211-
rustAnalyzer: this.config.rustAnalyzer,
212-
});
226+
this.globalState,
227+
);
213228

214229
client.onDidChangeState(({ newState }) => {
215230
if (newState === lc.State.Starting) {

src/rust-analyzer/persistent_state.ts

+44-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import { promisify } from 'util';
4+
import * as vscode from 'vscode';
45

56
const stat = promisify(fs.stat);
67
const mkdir = promisify(fs.mkdir);
@@ -32,22 +33,20 @@ export interface Metadata {
3233
releaseTag: string;
3334
}
3435

35-
export async function readMetadata(): Promise<
36-
Metadata | Record<string, unknown>
37-
> {
36+
export async function readMetadata(): Promise<Partial<Metadata>> {
3837
const stateDir = metadataDir();
3938
if (!stateDir) {
40-
return { kind: 'error', code: 'NotSupported' };
39+
throw new Error('Not supported');
4140
}
4241

4342
const filePath = path.join(stateDir, 'metadata.json');
4443
if (!(await stat(filePath).catch(() => false))) {
45-
return { kind: 'error', code: 'FileMissing' };
44+
throw new Error('File missing');
4645
}
4746

4847
const contents = await readFile(filePath, 'utf8');
4948
const obj = JSON.parse(contents) as unknown;
50-
return typeof obj === 'object' ? (obj as Record<string, unknown>) : {};
49+
return typeof obj === 'object' ? obj || {} : {};
5150
}
5251

5352
export async function writeMetadata(config: Metadata) {
@@ -67,3 +66,42 @@ export async function writeMetadata(config: Metadata) {
6766
function ensureDir(path: string) {
6867
return !!path && stat(path).catch(() => mkdir(path, { recursive: true }));
6968
}
69+
70+
export class PersistentState {
71+
constructor(private readonly globalState: vscode.Memento) {
72+
const { lastCheck, releaseId, serverVersion } = this;
73+
console.info('PersistentState:', { lastCheck, releaseId, serverVersion });
74+
}
75+
76+
/**
77+
* Used to check for *nightly* updates once an hour.
78+
*/
79+
get lastCheck(): number | undefined {
80+
return this.globalState.get('lastCheck');
81+
}
82+
async updateLastCheck(value: number) {
83+
await this.globalState.update('lastCheck', value);
84+
}
85+
86+
/**
87+
* Release id of the *nightly* extension.
88+
* Used to check if we should update.
89+
*/
90+
get releaseId(): number | undefined {
91+
return this.globalState.get('releaseId');
92+
}
93+
async updateReleaseId(value: number) {
94+
await this.globalState.update('releaseId', value);
95+
}
96+
97+
/**
98+
* Version of the extension that installed the server.
99+
* Used to check if we need to update the server.
100+
*/
101+
get serverVersion(): string | undefined {
102+
return this.globalState.get('serverVersion');
103+
}
104+
async updateServerVersion(value: string | undefined) {
105+
await this.globalState.update('serverVersion', value);
106+
}
107+
}

src/rust-analyzer/rustAnalyzer.ts

+26-12
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import { WorkspaceProgress } from '../extension';
1010
import { download, fetchRelease } from '../net';
1111
import * as rustup from '../rustup';
1212
import { Observable } from '../utils/observable';
13-
import { Metadata, readMetadata, writeMetadata } from './persistent_state';
13+
import {
14+
Metadata,
15+
readMetadata,
16+
writeMetadata,
17+
PersistentState,
18+
} from './persistent_state';
1419

1520
const stat = promisify(fs.stat);
1621
const mkdir = promisify(fs.mkdir);
@@ -55,10 +60,12 @@ interface RustAnalyzerConfig {
5560
};
5661
}
5762

58-
export async function getServer({
59-
askBeforeDownload,
60-
package: pkg,
61-
}: RustAnalyzerConfig): Promise<string | undefined> {
63+
export async function getServer(
64+
config: RustAnalyzerConfig,
65+
state: PersistentState,
66+
): Promise<string | undefined> {
67+
const { askBeforeDownload, package: pkg } = config;
68+
6269
let binaryName: string | undefined;
6370
if (process.arch === 'x64' || process.arch === 'ia32') {
6471
if (process.platform === 'linux') {
@@ -159,17 +166,21 @@ export async function createLanguageClient(
159166
rustup: { disabled: boolean; path: string; channel: string };
160167
rustAnalyzer: { path?: string; releaseTag: string };
161168
},
169+
state: vs.Memento,
162170
): Promise<lc.LanguageClient> {
163171
if (!config.rustup.disabled) {
164172
await rustup.ensureToolchain(config.rustup);
165173
await rustup.ensureComponents(config.rustup, REQUIRED_COMPONENTS);
166174
}
167175

168176
if (!config.rustAnalyzer.path) {
169-
await getServer({
170-
askBeforeDownload: true,
171-
package: { releaseTag: config.rustAnalyzer.releaseTag },
172-
});
177+
await getServer(
178+
{
179+
askBeforeDownload: true,
180+
package: { releaseTag: config.rustAnalyzer.releaseTag },
181+
},
182+
new PersistentState(state),
183+
);
173184
}
174185

175186
if (INSTANCE) {
@@ -179,9 +190,12 @@ export async function createLanguageClient(
179190
const serverOptions: lc.ServerOptions = async () => {
180191
const binPath =
181192
config.rustAnalyzer.path ||
182-
(await getServer({
183-
package: { releaseTag: config.rustAnalyzer.releaseTag },
184-
}));
193+
(await getServer(
194+
{
195+
package: { releaseTag: config.rustAnalyzer.releaseTag },
196+
},
197+
new PersistentState(state),
198+
));
185199

186200
if (!binPath) {
187201
throw new Error("Couldn't fetch Rust Analyzer binary");

0 commit comments

Comments
 (0)