@@ -4,6 +4,7 @@ import type { Config } from "./config";
44import { type Env , log } from "./util" ;
55import type { PersistentState } from "./persistent_state" ;
66import { exec , spawnSync } from "child_process" ;
7+ import { TextDecoder } from "node:util" ;
78
89export async function bootstrap (
910 context : vscode . ExtensionContext ,
@@ -50,26 +51,35 @@ async function getServer(
5051 }
5152 return explicitPath ;
5253 }
53- if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
5454
55- if ( vscode . workspace . workspaceFolders ?. length === 1 ) {
56- // otherwise check if there is a toolchain override for the current vscode workspace
57- // and if the toolchain of this override has a rust-analyzer component
58- // if so, use the rust-analyzer component
59- const toolchainTomlExists = await fileExists (
60- vscode . Uri . joinPath ( vscode . workspace . workspaceFolders [ 0 ] ! . uri , "rust-toolchain.toml" ) ,
61- ) ;
62- if ( toolchainTomlExists ) {
63- const res = spawnSync ( "rustup" , [ "which" , "rust-analyzer" ] , {
64- encoding : "utf8" ,
65- env : { ...process . env } ,
66- cwd : vscode . workspace . workspaceFolders [ 0 ] ! . uri . fsPath ,
67- } ) ;
68- if ( ! res . error && res . status === 0 ) {
69- return res . stdout . trim ( ) ;
55+ let toolchainServerPath = undefined ;
56+ if ( vscode . workspace . workspaceFolders ) {
57+ for ( const workspaceFolder of vscode . workspace . workspaceFolders ) {
58+ // otherwise check if there is a toolchain override for the current vscode workspace
59+ // and if the toolchain of this override has a rust-analyzer component
60+ // if so, use the rust-analyzer component
61+ const toolchainUri = vscode . Uri . joinPath ( workspaceFolder . uri , "rust-toolchain.toml" ) ;
62+ if ( await hasToolchainFileWithRaDeclared ( toolchainUri ) ) {
63+ const res = spawnSync ( "rustup" , [ "which" , "rust-analyzer" ] , {
64+ encoding : "utf8" ,
65+ env : { ...process . env } ,
66+ cwd : workspaceFolder . uri . fsPath ,
67+ } ) ;
68+ if ( ! res . error && res . status === 0 ) {
69+ toolchainServerPath = earliestToolchainPath (
70+ toolchainServerPath ,
71+ res . stdout . trim ( ) ,
72+ raVersionResolver ,
73+ ) ;
74+ }
7075 }
7176 }
7277 }
78+ if ( toolchainServerPath ) {
79+ return toolchainServerPath ;
80+ }
81+
82+ if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
7383
7484 // finally, use the bundled one
7585 const ext = process . platform === "win32" ? ".exe" : "" ;
@@ -102,13 +112,77 @@ async function getServer(
102112 return undefined ;
103113}
104114
115+ // Given a path to a rust-analyzer executable, resolve its version and return it.
116+ function raVersionResolver ( path : string ) : string | undefined {
117+ const res = spawnSync ( path , [ "--version" ] , {
118+ encoding : "utf8" ,
119+ } ) ;
120+ if ( ! res . error && res . status === 0 ) {
121+ return res . stdout ;
122+ } else {
123+ return undefined ;
124+ }
125+ }
126+
127+ // Given a path to two rust-analyzer executables, return the earliest one by date.
128+ function earliestToolchainPath (
129+ path0 : string | undefined ,
130+ path1 : string ,
131+ raVersionResolver : ( path : string ) => string | undefined ,
132+ ) : string {
133+ if ( path0 ) {
134+ if ( orderFromPath ( path0 , raVersionResolver ) < orderFromPath ( path1 , raVersionResolver ) ) {
135+ return path0 ;
136+ } else {
137+ return path1 ;
138+ }
139+ } else {
140+ return path1 ;
141+ }
142+ }
143+
144+ // Further to extracting a date for comparison, determine the order of a toolchain as follows:
145+ // Highest - nightly
146+ // Medium - versioned
147+ // Lowest - stable
148+ // Example paths:
149+ // nightly - /Users/myuser/.rustup/toolchains/nightly-2022-11-22-aarch64-apple-darwin/bin/rust-analyzer
150+ // versioned - /Users/myuser/.rustup/toolchains/1.72.1-aarch64-apple-darwin/bin/rust-analyzer
151+ // stable - /Users/myuser/.rustup/toolchains/stable-aarch64-apple-darwin/bin/rust-analyzer
152+ function orderFromPath (
153+ path : string ,
154+ raVersionResolver : ( path : string ) => string | undefined ,
155+ ) : string {
156+ const raVersion = raVersionResolver ( path ) ;
157+ const raDate = raVersion ?. match ( / ^ r u s t - a n a l y z e r .* \( .* ( \d { 4 } - \d { 2 } - \d { 2 } ) \) $ / ) ;
158+ if ( raDate ?. length === 2 ) {
159+ const precedence = path . includes ( "nightly-" ) ? "0" : "1" ;
160+ return "0-" + raDate [ 1 ] + "/" + precedence ;
161+ } else {
162+ return "2" ;
163+ }
164+ }
165+
105166async function fileExists ( uri : vscode . Uri ) {
106167 return await vscode . workspace . fs . stat ( uri ) . then (
107168 ( ) => true ,
108169 ( ) => false ,
109170 ) ;
110171}
111172
173+ async function hasToolchainFileWithRaDeclared ( uri : vscode . Uri ) : Promise < boolean > {
174+ try {
175+ const toolchainFileContents = new TextDecoder ( ) . decode (
176+ await vscode . workspace . fs . readFile ( uri ) ,
177+ ) ;
178+ return (
179+ toolchainFileContents . match ( / c o m p o n e n t s \s * = \s * \[ .* \" r u s t - a n a l y z e r \" .* \] / g) ?. length === 1
180+ ) ;
181+ } catch ( e ) {
182+ return false ;
183+ }
184+ }
185+
112186export function isValidExecutable ( path : string , extraEnv : Env ) : boolean {
113187 log . debug ( "Checking availability of a binary at" , path ) ;
114188
@@ -207,3 +281,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
207281 } ,
208282 ) ;
209283}
284+
285+ export const _private = {
286+ earliestToolchainPath,
287+ orderFromPath,
288+ } ;
0 commit comments