@@ -37,27 +37,63 @@ export class FortlsClient {
37
37
}
38
38
39
39
private client : LanguageClient | undefined ;
40
- private version : string | undefined ;
40
+ private path : string | undefined ; // path to the forls binary
41
+ private version : string | undefined ; // fortls version
41
42
private readonly name : string = 'Fortran Language Server' ;
42
43
43
44
public async activate ( ) {
44
- // Detect if fortls is present, download if missing or disable LS functionality
45
- // Do not allow activating the LS functionality if no fortls is detected
46
- await this . fortlsDownload ( ) . then ( fortlsDisabled => {
47
- if ( fortlsDisabled ) return ;
48
- workspace . onDidOpenTextDocument ( this . didOpenTextDocument , this ) ;
49
- workspace . textDocuments . forEach ( this . didOpenTextDocument , this ) ;
50
- workspace . onDidChangeWorkspaceFolders ( event => {
51
- for ( const folder of event . removed ) {
52
- const client = clients . get ( folder . uri . toString ( ) ) ;
53
- if ( client ) {
54
- clients . delete ( folder . uri . toString ( ) ) ;
55
- client . stop ( ) ;
45
+ const config = workspace . getConfiguration ( EXTENSION_ID ) ;
46
+
47
+ if ( ! config . get [ 'fortls.disabled' ] ) {
48
+ // Detect if fortls is present, download if missing or disable LS functionality
49
+ // Do not allow activating the LS functionality if no fortls is detected
50
+ const fortlsFound = this . getLSPath ( ) ;
51
+
52
+ if ( ! fortlsFound ) {
53
+ const msg = `Forlts wasn't found on your system.
54
+ It is highly recommended to use the fortls to enable IDE features like hover, peeking, GoTos and many more.
55
+ For a full list of features the language server adds see: https://fortls.fortran-lang.org` ;
56
+
57
+ const selection = window . showInformationMessage ( msg , 'Install' , 'Disable' ) ;
58
+ selection . then ( async opt => {
59
+ if ( opt === 'Install' ) {
60
+ try {
61
+ this . logger . info ( `[lsp.client] Downloading ${ LS_NAME } ` ) ;
62
+ const msg = await pipInstall ( LS_NAME ) ;
63
+ window . showInformationMessage ( msg ) ;
64
+ this . logger . info ( `[lsp.client] ${ LS_NAME } installed` ) ;
65
+
66
+ // restart this class
67
+ this . deactivate ( ) ;
68
+ this . activate ( ) ;
69
+
70
+ } catch ( error ) {
71
+ this . logger . error ( `[lsp.client] Error installing ${ LS_NAME } : ${ error } ` ) ;
72
+ window . showErrorMessage ( error ) ;
73
+ }
74
+ } else if ( opt == 'Disable' ) {
75
+ config . update ( 'fortls.disabled' , true , vscode . ConfigurationTarget . Global ) ;
76
+ this . logger . info ( `[lsp.client] ${ LS_NAME } disabled in settings` ) ;
56
77
}
57
- }
58
- } ) ;
59
- } ) ;
78
+ } ) ;
79
+
80
+ } else {
81
+ workspace . onDidOpenTextDocument ( this . didOpenTextDocument , this ) ;
82
+ workspace . textDocuments . forEach ( this . didOpenTextDocument , this ) ;
83
+ workspace . onDidChangeWorkspaceFolders ( event => {
84
+ for ( const folder of event . removed ) {
85
+ const client = clients . get ( folder . uri . toString ( ) ) ;
86
+ if ( client ) {
87
+ clients . delete ( folder . uri . toString ( ) ) ;
88
+ client . stop ( ) ;
89
+ }
90
+ }
91
+ } ) ;
92
+ }
93
+ }
94
+
60
95
return ;
96
+
61
97
}
62
98
63
99
public async deactivate ( ) : Promise < void > {
@@ -84,7 +120,7 @@ export class FortlsClient {
84
120
if ( ! isFortran ( document ) ) return ;
85
121
86
122
const args : string [ ] = await this . fortlsArguments ( ) ;
87
- const executablePath : string = await this . fortlsPath ( document ) ;
123
+ const executablePath : string = this . path ;
88
124
89
125
// Detect language server version and verify selected options
90
126
this . version = this . getLSVersion ( executablePath , args ) ;
@@ -251,6 +287,63 @@ export class FortlsClient {
251
287
return args ;
252
288
}
253
289
290
+ /**
291
+ * Tries to find fortls and saves its path to this.path.
292
+ *
293
+ * If a user path is configured, then only use this.
294
+ * If not, try running fortls globally, or from python user scripts folder on Windows.
295
+ *
296
+ * @returns true if fortls found, false if not
297
+ */
298
+ private getLSPath ( ) : boolean {
299
+ const config = workspace . getConfiguration ( EXTENSION_ID ) ;
300
+ const configuredPath = resolveVariables ( config . get < string > ( 'fortls.path' ) ) ;
301
+
302
+ const pathsToCheck : string [ ] = [ ] ;
303
+
304
+ // if there's a user configured path to the executable, check if it's absolute
305
+ if ( configuredPath !== '' ) {
306
+ if ( ! path . isAbsolute ( configuredPath ) ) {
307
+ throw Error ( "The path to fortls (fortls.path) must be absolute." ) ;
308
+ }
309
+
310
+ pathsToCheck . push ( configuredPath ) ;
311
+
312
+ } else { // no user configured path => perform standard search for fortls
313
+
314
+ pathsToCheck . push ( 'fortls' ) ;
315
+
316
+ // On Windows, `pip install fortls --user` installs fortls to the userbase\PythonXY\Scripts path,
317
+ // so we want to look for it in this path as well.
318
+ if ( os . platform ( ) == 'win32' ) {
319
+ const result = spawnSync ( 'python' , [ '-c' , 'import site; print(site.getusersitepackages())' ] ) ;
320
+ const userSitePackagesStr = result . stdout . toString ( ) . trim ( ) ;
321
+
322
+ // check if the call above returned something, in case the site module in python ever changes...
323
+ if ( userSitePackagesStr ) {
324
+ const userScriptsPath = path . resolve ( userSitePackagesStr , '../Scripts/fortls' ) ;
325
+ pathsToCheck . push ( userScriptsPath ) ;
326
+ }
327
+ }
328
+
329
+ }
330
+
331
+ // try to run `fortls --version` for all the given paths
332
+ // if any succeed, save it to this.path and stop.
333
+ for ( const pathToCheck of pathsToCheck ) {
334
+ const result = spawnSync ( pathToCheck , [ '--version' ] ) ;
335
+ if ( result . status == 0 ) {
336
+ this . path = pathToCheck ;
337
+ this . logger . info ( 'Successfully spawned fortls with path ' + pathToCheck ) ;
338
+ return true ;
339
+ } else {
340
+ this . logger . info ( 'Failed to spawn fortls with path ' + pathToCheck ) ;
341
+ }
342
+ }
343
+
344
+ return false ; // fortls not found
345
+ }
346
+
254
347
/**
255
348
* Check if `fortls` is present and the arguments being passed are correct
256
349
* The presence check has already been done in the extension activate call
@@ -299,92 +392,6 @@ export class FortlsClient {
299
392
return results . stdout . toString ( ) . trim ( ) ;
300
393
}
301
394
302
- /**
303
- * Check if fortls is present in the system, if not show prompt to install/disable.
304
- * If disabling or erroring the function will return true.
305
- * For all normal cases it should return false.
306
- *
307
- * @returns false if the fortls has been detected or installed successfully
308
- */
309
- private async fortlsDownload ( ) : Promise < boolean > {
310
- const config = workspace . getConfiguration ( EXTENSION_ID ) ;
311
- const ls = await this . fortlsPath ( ) ;
312
-
313
- // Check for version, if this fails fortls provided is invalid
314
- const results = spawnSync ( ls , [ '--version' ] ) ;
315
- const msg = `It is highly recommended to use the fortls to enable IDE features like hover, peeking, GoTos and many more.
316
- For a full list of features the language server adds see: https://fortls.fortran-lang.org` ;
317
- return new Promise < boolean > ( resolve => {
318
- if ( results . error ) {
319
- const selection = window . showInformationMessage ( msg , 'Install' , 'Disable' ) ;
320
- selection . then ( async opt => {
321
- if ( opt === 'Install' ) {
322
- try {
323
- this . logger . info ( `[lsp.client] Downloading ${ LS_NAME } ` ) ;
324
- const msg = await pipInstall ( LS_NAME ) ;
325
- window . showInformationMessage ( msg ) ;
326
- this . logger . info ( `[lsp.client] ${ LS_NAME } installed` ) ;
327
- resolve ( false ) ;
328
- } catch ( error ) {
329
- this . logger . error ( `[lsp.client] Error installing ${ LS_NAME } : ${ error } ` ) ;
330
- window . showErrorMessage ( error ) ;
331
- resolve ( true ) ;
332
- }
333
- } else if ( opt == 'Disable' ) {
334
- config . update ( 'fortls.disabled' , true ) ;
335
- this . logger . info ( `[lsp.client] ${ LS_NAME } disabled in settings` ) ;
336
- resolve ( true ) ;
337
- }
338
- } ) ;
339
- } else {
340
- resolve ( false ) ;
341
- }
342
- } ) ;
343
- }
344
-
345
- /**
346
- * Try and find the path to the `fortls` executable.
347
- * It will first try and fetch the top-most workspaceFolder from `document`.
348
- * If that fails because the document is standalone and does not belong in a
349
- * workspace it will assume that relative paths are wrt `os.homedir()`.
350
- *
351
- * If the `document` argument is missing, then it will try and find the
352
- * first workspaceFolder and use that as the root. If that fails it will
353
- * revert back to `os.homedir()`.
354
- *
355
- * @param document Optional textdocument
356
- * @returns a promise with the path to the fortls executable
357
- */
358
- private async fortlsPath ( document ?: TextDocument ) : Promise < string > {
359
- // Get the workspace folder that contains the document, this can be undefined
360
- // which means that the document is standalone and not part of any workspace.
361
- let folder : vscode . WorkspaceFolder | undefined ;
362
- if ( document ) {
363
- folder = workspace . getWorkspaceFolder ( document . uri ) ;
364
- }
365
- // If the document argument is missing, such as in the case of the Client's
366
- // activation, then try and fetch the first workspace folder to use as a root.
367
- else {
368
- folder = workspace . workspaceFolders [ 0 ] ? workspace . workspaceFolders [ 0 ] : undefined ;
369
- }
370
-
371
- // Get the outer most workspace folder to resolve relative paths, but if
372
- // the folder is undefined then use the home directory of the OS
373
- const root = folder ? getOuterMostWorkspaceFolder ( folder ) . uri : vscode . Uri . parse ( os . homedir ( ) ) ;
374
-
375
- const config = workspace . getConfiguration ( EXTENSION_ID ) ;
376
- let executablePath = resolveVariables ( config . get < string > ( 'fortls.path' ) ) ;
377
-
378
- // The path can be resolved as a relative path if:
379
- // 1. it does not have the default value `fortls` AND
380
- // 2. is not an absolute path
381
- if ( executablePath !== 'fortls' && ! path . isAbsolute ( executablePath ) ) {
382
- this . logger . debug ( `[lsp.client] Assuming relative fortls path is to ${ root . fsPath } ` ) ;
383
- executablePath = path . join ( root . fsPath , executablePath ) ;
384
- }
385
-
386
- return executablePath ;
387
- }
388
395
389
396
/**
390
397
* Restart the language server
0 commit comments