diff options
Diffstat (limited to 'editors/code/src/main.ts')
-rw-r--r-- | editors/code/src/main.ts | 158 |
1 files changed, 138 insertions, 20 deletions
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 94ecd4dab..d907f3e6f 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -1,15 +1,18 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as path from "path"; | ||
3 | import * as os from "os"; | ||
4 | import { promises as fs } from "fs"; | ||
2 | 5 | ||
3 | import * as commands from './commands'; | 6 | import * as commands from './commands'; |
4 | import { activateInlayHints } from './inlay_hints'; | 7 | import { activateInlayHints } from './inlay_hints'; |
5 | import { activateStatusDisplay } from './status_display'; | 8 | import { activateStatusDisplay } from './status_display'; |
6 | import { Ctx } from './ctx'; | 9 | import { Ctx } from './ctx'; |
7 | import { activateHighlighting } from './highlighting'; | 10 | import { activateHighlighting } from './highlighting'; |
8 | import { ensureServerBinary } from './installation/server'; | 11 | import { Config, NIGHTLY_TAG } from './config'; |
9 | import { Config } from './config'; | 12 | import { log, assert } from './util'; |
10 | import { log } from './util'; | ||
11 | import { ensureProperExtensionVersion } from './installation/extension'; | ||
12 | import { PersistentState } from './persistent_state'; | 13 | import { PersistentState } from './persistent_state'; |
14 | import { fetchRelease, download } from './net'; | ||
15 | import { spawnSync } from 'child_process'; | ||
13 | 16 | ||
14 | let ctx: Ctx | undefined; | 17 | let ctx: Ctx | undefined; |
15 | 18 | ||
@@ -35,27 +38,14 @@ export async function activate(context: vscode.ExtensionContext) { | |||
35 | context.subscriptions.push(defaultOnEnter); | 38 | context.subscriptions.push(defaultOnEnter); |
36 | 39 | ||
37 | const config = new Config(context); | 40 | const config = new Config(context); |
38 | const state = new PersistentState(context); | 41 | const state = new PersistentState(context.globalState); |
39 | 42 | const serverPath = await bootstrap(config, state); | |
40 | vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config, state).catch(log.error)); | ||
41 | |||
42 | // Don't await the user response here, otherwise we will block the lsp server bootstrap | ||
43 | void ensureProperExtensionVersion(config, state).catch(log.error); | ||
44 | |||
45 | const serverPath = await ensureServerBinary(config, state); | ||
46 | |||
47 | if (serverPath == null) { | ||
48 | throw new Error( | ||
49 | "Rust Analyzer Language Server is not available. " + | ||
50 | "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." | ||
51 | ); | ||
52 | } | ||
53 | 43 | ||
54 | // Note: we try to start the server before we activate type hints so that it | 44 | // Note: we try to start the server before we activate type hints so that it |
55 | // registers its `onDidChangeDocument` handler before us. | 45 | // registers its `onDidChangeDocument` handler before us. |
56 | // | 46 | // |
57 | // This a horribly, horribly wrong way to deal with this problem. | 47 | // This a horribly, horribly wrong way to deal with this problem. |
58 | ctx = await Ctx.create(config, state, context, serverPath); | 48 | ctx = await Ctx.create(config, context, serverPath); |
59 | 49 | ||
60 | // Commands which invokes manually via command palette, shortcut, etc. | 50 | // Commands which invokes manually via command palette, shortcut, etc. |
61 | ctx.registerCommand('reload', (ctx) => { | 51 | ctx.registerCommand('reload', (ctx) => { |
@@ -109,3 +99,131 @@ export async function deactivate() { | |||
109 | await ctx?.client?.stop(); | 99 | await ctx?.client?.stop(); |
110 | ctx = undefined; | 100 | ctx = undefined; |
111 | } | 101 | } |
102 | |||
103 | async function bootstrap(config: Config, state: PersistentState): Promise<string> { | ||
104 | await fs.mkdir(config.globalStoragePath, { recursive: true }); | ||
105 | |||
106 | await bootstrapExtension(config, state); | ||
107 | const path = await bootstrapServer(config, state); | ||
108 | |||
109 | return path; | ||
110 | } | ||
111 | |||
112 | async function bootstrapExtension(config: Config, state: PersistentState): Promise<void> { | ||
113 | if (config.channel === "stable") { | ||
114 | if (config.extensionReleaseTag === NIGHTLY_TAG) { | ||
115 | vscode.window.showWarningMessage(`You are running a nightly version of rust-analyzer extension. | ||
116 | To switch to stable, uninstall the extension and re-install it from the marketplace`); | ||
117 | } | ||
118 | return; | ||
119 | }; | ||
120 | |||
121 | const lastCheck = state.lastCheck; | ||
122 | const now = Date.now(); | ||
123 | |||
124 | const anHour = 60 * 60 * 1000; | ||
125 | const shouldDownloadNightly = state.releaseId === undefined || (now - (lastCheck ?? 0)) > anHour; | ||
126 | |||
127 | if (!shouldDownloadNightly) return; | ||
128 | |||
129 | const release = await fetchRelease("nightly").catch((e) => { | ||
130 | log.error(e); | ||
131 | if (state.releaseId === undefined) { // Show error only for the initial download | ||
132 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); | ||
133 | } | ||
134 | return undefined; | ||
135 | }); | ||
136 | if (release === undefined || release.id === state.releaseId) return; | ||
137 | |||
138 | const userResponse = await vscode.window.showInformationMessage( | ||
139 | "New version of rust-analyzer (nightly) is available (requires reload).", | ||
140 | "Update" | ||
141 | ); | ||
142 | if (userResponse !== "Update") return; | ||
143 | |||
144 | const artifact = release.assets.find(artifact => artifact.name === "rust-analyzer.vsix"); | ||
145 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | ||
146 | |||
147 | const dest = path.join(config.globalStoragePath, "rust-analyzer.vsix"); | ||
148 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer extension"); | ||
149 | |||
150 | await vscode.commands.executeCommand("workbench.extensions.installExtension", vscode.Uri.file(dest)); | ||
151 | await fs.unlink(dest); | ||
152 | |||
153 | await state.updateReleaseId(release.id); | ||
154 | await state.updateLastCheck(now); | ||
155 | await vscode.commands.executeCommand("workbench.action.reloadWindow"); | ||
156 | } | ||
157 | |||
158 | async function bootstrapServer(config: Config, state: PersistentState): Promise<string> { | ||
159 | const path = await getServer(config, state); | ||
160 | if (!path) { | ||
161 | throw new Error( | ||
162 | "Rust Analyzer Language Server is not available. " + | ||
163 | "Please, ensure its [proper installation](https://rust-analyzer.github.io/manual.html#installation)." | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | const res = spawnSync(path, ["--version"], { encoding: 'utf8' }); | ||
168 | log.debug("Checked binary availability via --version", res); | ||
169 | log.debug(res, "--version output:", res.output); | ||
170 | if (res.status !== 0) { | ||
171 | throw new Error( | ||
172 | `Failed to execute ${path} --version` | ||
173 | ); | ||
174 | } | ||
175 | |||
176 | return path; | ||
177 | } | ||
178 | |||
179 | async function getServer(config: Config, state: PersistentState): Promise<string | undefined> { | ||
180 | const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath; | ||
181 | if (explicitPath) { | ||
182 | if (explicitPath.startsWith("~/")) { | ||
183 | return os.homedir() + explicitPath.slice("~".length); | ||
184 | } | ||
185 | return explicitPath; | ||
186 | }; | ||
187 | |||
188 | let binaryName: string | undefined = undefined; | ||
189 | if (process.arch === "x64" || process.arch === "x32") { | ||
190 | if (process.platform === "linux") binaryName = "rust-analyzer-linux"; | ||
191 | if (process.platform === "darwin") binaryName = "rust-analyzer-mac"; | ||
192 | if (process.platform === "win32") binaryName = "rust-analyzer-windows.exe"; | ||
193 | } | ||
194 | if (binaryName === undefined) { | ||
195 | vscode.window.showErrorMessage( | ||
196 | "Unfortunately we don't ship binaries for your platform yet. " + | ||
197 | "You need to manually clone rust-analyzer repository and " + | ||
198 | "run `cargo xtask install --server` to build the language server from sources. " + | ||
199 | "If you feel that your platform should be supported, please create an issue " + | ||
200 | "about that [here](https://github.com/rust-analyzer/rust-analyzer/issues) and we " + | ||
201 | "will consider it." | ||
202 | ); | ||
203 | return undefined; | ||
204 | } | ||
205 | |||
206 | const dest = path.join(config.globalStoragePath, binaryName); | ||
207 | const exists = await fs.stat(dest).then(() => true, () => false); | ||
208 | if (!exists) { | ||
209 | await state.updateServerVersion(undefined); | ||
210 | } | ||
211 | |||
212 | if (state.serverVersion === config.packageJsonVersion) return dest; | ||
213 | |||
214 | if (config.askBeforeDownload) { | ||
215 | const userResponse = await vscode.window.showInformationMessage( | ||
216 | `Language server version ${config.packageJsonVersion} for rust-analyzer is not installed.`, | ||
217 | "Download now" | ||
218 | ); | ||
219 | if (userResponse !== "Download now") return dest; | ||
220 | } | ||
221 | |||
222 | const release = await fetchRelease(config.extensionReleaseTag); | ||
223 | const artifact = release.assets.find(artifact => artifact.name === binaryName); | ||
224 | assert(!!artifact, `Bad release: ${JSON.stringify(release)}`); | ||
225 | |||
226 | await download(artifact.browser_download_url, dest, "Downloading rust-analyzer server", { mode: 0o755 }); | ||
227 | await state.updateServerVersion(config.packageJsonVersion); | ||
228 | return dest; | ||
229 | } | ||