@@ -48,6 +48,7 @@ interface IAsset {
48
48
export async function activate ( context : ExtensionContext ) {
49
49
// Register HIE to check every time a text document gets opened, to
50
50
// support multi-root workspaces.
51
+
51
52
workspace . onDidOpenTextDocument ( async ( document : TextDocument ) => await activateHie ( context , document ) ) ;
52
53
workspace . textDocuments . forEach ( async ( document : TextDocument ) => await activateHie ( context , document ) ) ;
53
54
@@ -63,10 +64,10 @@ export async function activate(context: ExtensionContext) {
63
64
} ) ;
64
65
}
65
66
66
- async function getProjectGhcVersion ( context : ExtensionContext , release : IRelease ) : Promise < string | null > {
67
+ async function getProjectGhcVersion ( context : ExtensionContext , dir : string , release : IRelease ) : Promise < string | null > {
67
68
const callWrapper = ( wrapper : string ) => {
68
69
// Need to set the encoding to 'utf8' in order to get back a string
69
- const out = child_process . spawnSync ( wrapper , [ '--project-ghc-version' ] , { encoding : 'utf8' } ) ;
70
+ const out = child_process . spawnSync ( wrapper , [ '--project-ghc-version' ] , { encoding : 'utf8' , cwd : dir } ) ;
70
71
return ! out . error ? out . stdout . trim ( ) : null ;
71
72
} ;
72
73
@@ -92,7 +93,9 @@ async function getProjectGhcVersion(context: ExtensionContext, release: IRelease
92
93
window . showErrorMessage ( `Couldn't find any haskell-language-server-wrapper binaries for ${ process . platform } ` ) ;
93
94
return null ;
94
95
}
95
- const wrapperAsset = release . assets . find ( ( x ) => x . name === `haskell-language-server-wrapper-${ githubOS } ` ) ;
96
+
97
+ const assetName = `haskell-language-server-wrapper-${ githubOS } .gz` ;
98
+ const wrapperAsset = release . assets . find ( ( x ) => x . name === assetName ) ;
96
99
97
100
if ( ! wrapperAsset ) {
98
101
return null ;
@@ -111,44 +114,51 @@ async function getProjectGhcVersion(context: ExtensionContext, release: IRelease
111
114
return callWrapper ( downloadedWrapper ) ;
112
115
}
113
116
114
- /** Searches the PATH for whatever is set in hieVariant as well as whatever's in hieExecutablePath */
115
- function findLocalServer ( context : ExtensionContext , uri : Uri , folder ?: WorkspaceFolder ) : string | null {
116
- const hieVariant = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . hieVariant ;
117
+ function findManualExecutable ( uri : Uri , folder ?: WorkspaceFolder ) : string | null {
117
118
let hieExecutablePath = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . hieExecutablePath ;
119
+ if ( hieExecutablePath === '' ) {
120
+ return null ;
121
+ }
118
122
119
123
// Substitute path variables with their corresponding locations.
120
- if ( hieExecutablePath !== '' ) {
124
+ hieExecutablePath = hieExecutablePath
125
+ . replace ( '${HOME}' , os . homedir )
126
+ . replace ( '${home}' , os . homedir )
127
+ . replace ( / ^ ~ / , os . homedir ) ;
128
+ if ( folder ) {
121
129
hieExecutablePath = hieExecutablePath
122
- . replace ( '${HOME}' , os . homedir )
123
- . replace ( '${home}' , os . homedir )
124
- . replace ( / ^ ~ / , os . homedir ) ;
125
- if ( folder ) {
126
- hieExecutablePath = hieExecutablePath
127
- . replace ( '${workspaceFolder}' , folder . uri . path )
128
- . replace ( '${workspaceRoot}' , folder . uri . path ) ;
129
- }
130
- return hieExecutablePath ;
130
+ . replace ( '${workspaceFolder}' , folder . uri . path )
131
+ . replace ( '${workspaceRoot}' , folder . uri . path ) ;
131
132
}
132
133
134
+ if ( ! executableExists ( hieExecutablePath ) ) {
135
+ throw new Error ( 'Manual executable missing' ) ;
136
+ }
137
+ return hieExecutablePath ;
138
+ }
139
+
140
+ /** Searches the PATH for whatever is set in hieVariant */
141
+ function findLocalServer ( context : ExtensionContext , uri : Uri , folder ?: WorkspaceFolder ) : string | null {
142
+ const hieVariant = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . hieVariant ;
143
+
133
144
// Set the executable, based on the settings.
134
- let serverExecutable = 'hie' ; // should get set below
145
+ let exes : string [ ] = [ ] ; // should get set below
135
146
switch ( hieVariant ) {
136
147
case 'haskell-ide-engine' :
137
- serverExecutable = 'hie-wrapper' ;
148
+ exes = [ 'hie-wrapper' , 'hie' ] ;
138
149
break ;
139
150
case 'haskell-language-server' :
140
- serverExecutable = 'haskell-language-server-wrapper' ;
151
+ exes = [ 'haskell-language-server-wrapper' , 'haskell-language-server' ] ;
141
152
break ;
142
153
case 'ghcide' :
143
- serverExecutable = 'ghcide' ;
154
+ exes = [ 'ghcide' ] ;
144
155
break ;
145
156
}
146
- if ( hieExecutablePath !== '' ) {
147
- serverExecutable = hieExecutablePath ;
148
- }
149
157
150
- if ( executableExists ( serverExecutable ) ) {
151
- return serverExecutable ;
158
+ for ( const exe in exes ) {
159
+ if ( executableExists ( exe ) ) {
160
+ return exe ;
161
+ }
152
162
}
153
163
154
164
return null ;
@@ -172,8 +182,32 @@ function getGithubOS(): string | null {
172
182
return platformToGithubOS ( process . platform ) ;
173
183
}
174
184
175
- async function downloadServer ( context : ExtensionContext , releases : IRelease [ ] ) : Promise < string | null > {
185
+ async function downloadServer (
186
+ context : ExtensionContext ,
187
+ resource : Uri ,
188
+ folder ?: WorkspaceFolder
189
+ ) : Promise < string | null > {
190
+ // We only download binaries for haskell-language-server at the moment
191
+ if ( workspace . getConfiguration ( 'languageServerHaskell' , resource ) . hieVariant !== 'haskell-language-server' ) {
192
+ return null ;
193
+ }
194
+
176
195
// fetch the latest release from GitHub
196
+ const releases : IRelease [ ] = await new Promise ( ( resolve , reject ) => {
197
+ let data : string = '' ;
198
+ const opts : https . RequestOptions = {
199
+ host : 'api.github.com' ,
200
+ path : '/repos/bubba/haskell-language-server/releases' ,
201
+ headers : userAgentHeader ,
202
+ } ;
203
+ https . get ( opts , ( res ) => {
204
+ res . on ( 'data' , ( d ) => ( data += d ) ) ;
205
+ res . on ( 'error' , reject ) ;
206
+ res . on ( 'close' , ( ) => {
207
+ resolve ( JSON . parse ( data ) ) ;
208
+ } ) ;
209
+ } ) ;
210
+ } ) ;
177
211
178
212
const githubOS = getGithubOS ( ) ;
179
213
if ( githubOS === null ) {
@@ -184,15 +218,17 @@ async function downloadServer(context: ExtensionContext, releases: IRelease[]):
184
218
185
219
// const release = releases.find(x => !x.prerelease);
186
220
const release = releases [ 0 ] ;
221
+ const dir : string = folder ?. uri ?. fsPath ?? path . dirname ( resource . fsPath ) ;
187
222
188
- const ghcVersion = await getProjectGhcVersion ( context , release ) ;
223
+ const ghcVersion = await getProjectGhcVersion ( context , dir , release ) ;
189
224
if ( ! ghcVersion ) {
190
225
window . showErrorMessage ( "Couldn't figure out what GHC version the project is using" ) ;
191
226
// We couldn't figure out the right ghc version to download
192
227
return null ;
193
228
}
194
229
195
- const asset = release ?. assets . find ( ( x ) => x . name . includes ( githubOS ) && x . name . includes ( ghcVersion ) ) ;
230
+ const assetName = `haskell-language-server-${ githubOS } -${ ghcVersion } .gz` ;
231
+ const asset = release ?. assets . find ( ( x ) => x . name === assetName ) ;
196
232
if ( ! release || ! asset ) {
197
233
return null ;
198
234
}
@@ -239,47 +275,6 @@ async function activateHie(context: ExtensionContext, document: TextDocument) {
239
275
if ( folder && clients . has ( folder . uri . toString ( ) ) ) {
240
276
return ;
241
277
}
242
-
243
- // try {
244
- // const hieVariant = workspace.getConfiguration('languageServerHaskell', uri).hieVariant;
245
- // const hieExecutablePath = workspace.getConfiguration('languageServerHaskell', uri).hieExecutablePath;
246
- // // Check if hie is installed.
247
- // let exeName = 'hie';
248
- // switch (hieVariant) {
249
- // case 'haskell-ide-engine':
250
- // break;
251
- // case 'haskell-language-server':
252
- // case 'ghcide':
253
- // exeName = hieVariant;
254
- // break;
255
- // }
256
- // if (!await isHieInstalled(exeName) && hieExecutablePath === '') {
257
- // // TODO: Once haskell-ide-engine is on hackage/stackage, enable an option to install it via cabal/stack.
258
- // let hieProjectUrl = '/haskell/haskell-ide-engine';
259
- // switch (hieVariant) {
260
- // case 'haskell-ide-engine':
261
- // break;
262
- // case 'haskell-language-server':
263
- // hieProjectUrl = '/haskell/haskell-language-server';
264
- // break;
265
- // case 'ghcide':
266
- // hieProjectUrl = '/digital-asset/ghcide';
267
- // break;
268
- // }
269
- // const notInstalledMsg: string =
270
- // exeName + ' executable missing, please make sure it is installed, see https://github.com' + hieProjectUrl + '.';
271
- // const forceStart: string = 'Force Start';
272
- // window.showErrorMessage(notInstalledMsg, forceStart).then(option => {
273
- // if (option === forceStart) {
274
- // activateHieNoCheck(context, uri, folder);
275
- // }
276
- // });
277
- // } else {
278
- // activateHieNoCheck(context, uri, folder);
279
- // }
280
- // } catch (e) {
281
- // console.error(e);
282
- // }
283
278
activateHieNoCheck ( context , uri , folder ) ;
284
279
}
285
280
@@ -300,24 +295,20 @@ async function activateHieNoCheck(context: ExtensionContext, uri: Uri, folder?:
300
295
const logLevel = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . trace . server ;
301
296
const logFile = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . logFile ;
302
297
303
- const releases : IRelease [ ] = await new Promise ( ( resolve , reject ) => {
304
- let data : string = '' ;
305
- const opts : https . RequestOptions = {
306
- host : 'api.github.com' ,
307
- path : '/repos/bubba/haskell-language-server/releases' ,
308
- headers : userAgentHeader ,
309
- } ;
310
- https . get ( opts , ( res ) => {
311
- res . on ( 'data' , ( d ) => ( data += d ) ) ;
312
- res . on ( 'error' , reject ) ;
313
- res . on ( 'close' , ( ) => {
314
- resolve ( JSON . parse ( data ) ) ;
315
- } ) ;
316
- } ) ;
317
- } ) ;
318
-
319
- const serverExecutable = findLocalServer ( context , uri , folder ) ?? ( await downloadServer ( context , releases ) ) ;
320
- if ( serverExecutable === null ) {
298
+ let serverExecutable ;
299
+ try {
300
+ serverExecutable =
301
+ findManualExecutable ( uri , folder ) ??
302
+ findLocalServer ( context , uri , folder ) ??
303
+ ( await downloadServer ( context , uri , folder ) ) ;
304
+ if ( serverExecutable === null ) {
305
+ showNotInstalledErrorMessage ( uri ) ;
306
+ return ;
307
+ }
308
+ } catch ( e ) {
309
+ if ( e instanceof Error ) {
310
+ window . showErrorMessage ( e . message ) ;
311
+ }
321
312
return ;
322
313
}
323
314
@@ -467,3 +458,22 @@ async function registerHiePointCommand(name: string, command: string, context: E
467
458
} ) ;
468
459
context . subscriptions . push ( editorCmd ) ;
469
460
}
461
+
462
+ function showNotInstalledErrorMessage ( uri : Uri ) {
463
+ const variant = workspace . getConfiguration ( 'languageServerHaskell' , uri ) . hieVariant ;
464
+ let projectUrl = '' ;
465
+ switch ( variant ) {
466
+ case 'haskell-ide-engine' :
467
+ projectUrl = '/haskell/haskell-ide-engine' ;
468
+ break ;
469
+ case 'haskell-language-server' :
470
+ projectUrl = '/haskell/haskell-language-server' ;
471
+ break ;
472
+ case 'ghcide' :
473
+ projectUrl = '/digital-asset/ghcide' ;
474
+ break ;
475
+ }
476
+ const notInstalledMsg : string =
477
+ variant + ' executable missing, please make sure it is installed, see https://github.com' + projectUrl + '.' ;
478
+ window . showErrorMessage ( notInstalledMsg ) ;
479
+ }
0 commit comments