@@ -13,6 +13,8 @@ import { Observable } from './utils/observable';
13
13
14
14
const stat = promisify ( fs . stat ) ;
15
15
const mkdir = promisify ( fs . mkdir ) ;
16
+ const readFile = promisify ( fs . readFile ) ;
17
+ const writeFile = promisify ( fs . writeFile ) ;
16
18
17
19
const REQUIRED_COMPONENTS = [ 'rust-src' ] ;
18
20
@@ -43,18 +45,29 @@ function installDir(): string | undefined {
43
45
return undefined ;
44
46
}
45
47
46
- async function ensureInstallDir ( ) {
47
- const dir = installDir ( ) ;
48
- if ( ! dir ) {
49
- return ;
50
- }
51
- const exists = await stat ( dir ) . then (
52
- ( ) => true ,
53
- ( ) => false ,
54
- ) ;
55
- if ( ! exists ) {
56
- await mkdir ( dir ) ;
48
+ /** Returns a path where persistent data for rust-analyzer should be installed. */
49
+ function metadataDir ( ) : string | undefined {
50
+ if ( process . platform === 'linux' || process . platform === 'darwin' ) {
51
+ // Prefer, in this order:
52
+ // 1. $XDG_CONFIG_HOME/rust-analyzer
53
+ // 2. $HOME/.config/rust-analyzer
54
+ const { HOME , XDG_CONFIG_HOME } = process . env ;
55
+ const baseDir = XDG_CONFIG_HOME || ( HOME && path . join ( HOME , '.config' ) ) ;
56
+
57
+ return baseDir && path . resolve ( path . join ( baseDir , 'rust-analyzer' ) ) ;
58
+ } else if ( process . platform === 'win32' ) {
59
+ // %LocalAppData%\rust-analyzer\
60
+ const { LocalAppData } = process . env ;
61
+ return (
62
+ LocalAppData && path . resolve ( path . join ( LocalAppData , 'rust-analyzer' ) )
63
+ ) ;
57
64
}
65
+
66
+ return undefined ;
67
+ }
68
+
69
+ function ensureDir ( path : string ) {
70
+ return ! ! path && stat ( path ) . catch ( ( ) => mkdir ( path , { recursive : true } ) ) ;
58
71
}
59
72
60
73
interface RustAnalyzerConfig {
@@ -64,9 +77,44 @@ interface RustAnalyzerConfig {
64
77
} ;
65
78
}
66
79
67
- export async function getServer (
68
- config : RustAnalyzerConfig ,
69
- ) : Promise < string | undefined > {
80
+ interface Metadata {
81
+ releaseTag : string ;
82
+ }
83
+
84
+ async function readMetadata ( ) : Promise < Metadata | { } > {
85
+ const stateDir = metadataDir ( ) ;
86
+ if ( ! stateDir ) {
87
+ return { kind : 'error' , code : 'NotSupported' } ;
88
+ }
89
+
90
+ const filePath = path . join ( stateDir , 'metadata.json' ) ;
91
+ if ( ! ( await stat ( filePath ) . catch ( ( ) => false ) ) ) {
92
+ return { kind : 'error' , code : 'FileMissing' } ;
93
+ }
94
+
95
+ const contents = await readFile ( filePath , 'utf8' ) ;
96
+ const obj = JSON . parse ( contents ) ;
97
+ return typeof obj === 'object' ? obj : { } ;
98
+ }
99
+
100
+ async function writeMetadata ( config : Metadata ) {
101
+ const stateDir = metadataDir ( ) ;
102
+ if ( ! stateDir ) {
103
+ return false ;
104
+ }
105
+
106
+ if ( ! ( await ensureDir ( stateDir ) ) ) {
107
+ return false ;
108
+ }
109
+
110
+ const filePath = path . join ( stateDir , 'metadata.json' ) ;
111
+ return writeFile ( filePath , JSON . stringify ( config ) ) . then ( ( ) => true ) ;
112
+ }
113
+
114
+ export async function getServer ( {
115
+ askBeforeDownload,
116
+ package : pkg ,
117
+ } : RustAnalyzerConfig ) : Promise < string | undefined > {
70
118
let binaryName : string | undefined ;
71
119
if ( process . arch === 'x64' || process . arch === 'ia32' ) {
72
120
if ( process . platform === 'linux' ) {
@@ -95,19 +143,24 @@ export async function getServer(
95
143
if ( ! dir ) {
96
144
return ;
97
145
}
98
- await ensureInstallDir ( ) ;
146
+ await ensureDir ( dir ) ;
147
+
148
+ const metadata : Partial < Metadata > = await readMetadata ( ) . catch ( ( ) => ( { } ) ) ;
149
+
99
150
const dest = path . join ( dir , binaryName ) ;
100
- const exists = await stat ( dest ) . then (
101
- ( ) => true ,
102
- ( ) => false ,
103
- ) ;
104
- if ( exists ) {
151
+ const exists = await stat ( dest ) . catch ( ( ) => false ) ;
152
+ if ( exists && metadata . releaseTag === pkg . releaseTag ) {
105
153
return dest ;
106
154
}
107
155
108
- if ( config . askBeforeDownload ) {
156
+ if ( askBeforeDownload ) {
109
157
const userResponse = await vs . window . showInformationMessage (
110
- `Language server release ${ config . package . releaseTag } for rust-analyzer is not installed.\n
158
+ `${
159
+ metadata . releaseTag && metadata . releaseTag !== pkg . releaseTag
160
+ ? `You seem to have installed release \`${ metadata . releaseTag } \` but requested a different one.`
161
+ : ''
162
+ }
163
+ Release \`${ pkg . releaseTag } \` of rust-analyzer is not installed.\n
111
164
Install to ${ dir } ?` ,
112
165
'Download' ,
113
166
) ;
@@ -119,7 +172,7 @@ export async function getServer(
119
172
const release = await fetchRelease (
120
173
'rust-analyzer' ,
121
174
'rust-analyzer' ,
122
- config . package . releaseTag ,
175
+ pkg . releaseTag ,
123
176
) ;
124
177
const artifact = release . assets . find ( asset => asset . name === binaryName ) ;
125
178
if ( ! artifact ) {
@@ -133,6 +186,10 @@ export async function getServer(
133
186
{ mode : 0o755 } ,
134
187
) ;
135
188
189
+ await writeMetadata ( { releaseTag : pkg . releaseTag } ) . catch ( ( ) => {
190
+ vs . window . showWarningMessage ( `Couldn't save rust-analyzer metadata` ) ;
191
+ } ) ;
192
+
136
193
return dest ;
137
194
}
138
195
@@ -156,27 +213,27 @@ export async function createLanguageClient(
156
213
revealOutputChannelOn ?: lc . RevealOutputChannelOn ;
157
214
logToFile ?: boolean ;
158
215
rustup : { disabled : boolean ; path : string ; channel : string } ;
159
- rustAnalyzer ? : { path ?: string ; releaseTag ? : string } ;
216
+ rustAnalyzer : { path ?: string ; releaseTag : string } ;
160
217
} ,
161
218
) : Promise < lc . LanguageClient > {
162
219
await rustup . ensureToolchain ( config . rustup ) ;
163
220
await rustup . ensureComponents ( config . rustup , REQUIRED_COMPONENTS ) ;
164
- await getServer ( {
165
- askBeforeDownload : true ,
166
- package : { releaseTag : config . rustAnalyzer ?. releaseTag || '2020-05-04' } ,
167
- } ) ;
221
+ if ( ! config . rustAnalyzer . path ) {
222
+ await getServer ( {
223
+ askBeforeDownload : true ,
224
+ package : { releaseTag : config . rustAnalyzer . releaseTag } ,
225
+ } ) ;
226
+ }
168
227
169
228
if ( INSTANCE ) {
170
229
return INSTANCE ;
171
230
}
172
231
173
232
const serverOptions : lc . ServerOptions = async ( ) => {
174
233
const binPath =
175
- config . rustAnalyzer ? .path ||
234
+ config . rustAnalyzer . path ||
176
235
( await getServer ( {
177
- package : {
178
- releaseTag : config . rustAnalyzer ?. releaseTag || '2020-05-04' ,
179
- } ,
236
+ package : { releaseTag : config . rustAnalyzer . releaseTag } ,
180
237
} ) ) ;
181
238
182
239
if ( ! binPath ) {
0 commit comments