@@ -4,6 +4,7 @@ import type { Config } from "./config";
4
4
import { type Env , log } from "./util" ;
5
5
import type { PersistentState } from "./persistent_state" ;
6
6
import { exec , spawnSync } from "child_process" ;
7
+ import { TextDecoder } from "node:util" ;
7
8
8
9
export async function bootstrap (
9
10
context : vscode . ExtensionContext ,
@@ -50,26 +51,35 @@ async function getServer(
50
51
}
51
52
return explicitPath ;
52
53
}
53
- if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
54
54
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
+ }
70
75
}
71
76
}
72
77
}
78
+ if ( toolchainServerPath ) {
79
+ return toolchainServerPath ;
80
+ }
81
+
82
+ if ( packageJson . releaseTag === null ) return "rust-analyzer" ;
73
83
74
84
// finally, use the bundled one
75
85
const ext = process . platform === "win32" ? ".exe" : "" ;
@@ -102,13 +112,77 @@ async function getServer(
102
112
return undefined ;
103
113
}
104
114
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
+
105
166
async function fileExists ( uri : vscode . Uri ) {
106
167
return await vscode . workspace . fs . stat ( uri ) . then (
107
168
( ) => true ,
108
169
( ) => false ,
109
170
) ;
110
171
}
111
172
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
+
112
186
export function isValidExecutable ( path : string , extraEnv : Env ) : boolean {
113
187
log . debug ( "Checking availability of a binary at" , path ) ;
114
188
@@ -207,3 +281,8 @@ async function patchelf(dest: vscode.Uri): Promise<void> {
207
281
} ,
208
282
) ;
209
283
}
284
+
285
+ export const _private = {
286
+ earliestToolchainPath,
287
+ orderFromPath,
288
+ } ;
0 commit comments