diff --git a/CHANGELOG.md b/CHANGELOG.md index a9c2beb4..7ebab5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Adds native support for the fortran-language-server (`fortls`) making unnecessary the usage of Fortran Intellisense extension ([#290](https://github.com/krvajal/vscode-fortran-support/issues/290)) -- Adds commands for re/starting/stopping the Language Server +- Added command for restarting the Language Server - Added more options for configuring the `fortls` settings through the UI ## [2.6.2] diff --git a/package.json b/package.json index 7709d698..a2a94476 100644 --- a/package.json +++ b/package.json @@ -301,11 +301,6 @@ ], "description": "Specify the word case to use when suggesting autocomplete options." }, - "fortran.ignoreWarning.fortls": { - "type": "boolean", - "default": false, - "description": "Hide error message when the fortls is not detected" - }, "fortran.includePaths": { "deprecationMessage": "fortran.includePaths has been renamed to fortran.linter.includePaths." }, @@ -351,16 +346,6 @@ "category": "Fortran", "command": "fortran.analysis.restartLanguageServer", "title": "Restart the Fortran Language Server" - }, - { - "category": "Fortran", - "command": "fortran.analysis.stopLanguageServer", - "title": "Stop the Fortran Language Server" - }, - { - "category": "Fortran", - "command": "fortran.analysis.startLanguageServer", - "title": "Start the Fortran Language Server" } ], "menus": { @@ -370,16 +355,6 @@ "command": "fortran.analysis.restartLanguageServer", "title": "Restart the Fortran Language Server", "when": "!virtualWorkspace && shellExecutionSupported" - }, - { - "command": "fortran.analysis.stopLanguageServer", - "title": "Stop the Fortran Language Server", - "when": "!virtualWorkspace && shellExecutionSupported" - }, - { - "command": "fortran.analysis.startLanguageServer", - "title": "Start the Fortran Language Server", - "when": "!virtualWorkspace && shellExecutionSupported" } ] } diff --git a/src/extension.ts b/src/extension.ts index a54a33f5..57ff4953 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,20 +1,13 @@ // src/extension.ts import * as vscode from 'vscode'; -import which from 'which'; import * as pkg from '../package.json'; -import { registerCommands } from './features/commands'; import { FortranCompletionProvider } from './features/completion-provider'; import { FortranDocumentSymbolProvider } from './features/document-symbol-provider'; import { FortranFormattingProvider } from './features/formatting-provider'; -import { FortranLanguageServer } from './features/fortls-interface'; +import { FortlsClient } from './lsp/client'; import { FortranHoverProvider } from './features/hover-provider'; import { FortranLintingProvider } from './features/linter-provider'; -import { - EXTENSION_ID, - FortranDocumentSelector, - LANG_SERVER_TOOL_ID, - promptForMissingTool, -} from './lib/tools'; +import { EXTENSION_ID, FortranDocumentSelector } from './lib/tools'; import { LoggingService } from './services/logging-service'; // Make it global to catch errors when activation fails @@ -65,43 +58,8 @@ export async function activate(context: vscode.ExtensionContext) { vscode.languages.registerDocumentSymbolProvider(FortranDocumentSelector(), symbolProvider); } - registerCommands(context.subscriptions); - - // Check if the language server is installed and if not prompt to install it - // Not the most elegant solution but we need pip install to have finished - // before the activate function is called so we do a little code duplication - if (!config.get('fortls.disabled')) { - which(config.get('fortls.path'), (err: any) => { - if (!config.get('ignoreWarning.fortls')) { - if (err) { - const msg = `It is highly recommended to use the fortls to - enable IDE features like hover, peeking, gotos and many more. - For a full list of features the language server adds see: - https://github.com/gnikit/fortls`; - promptForMissingTool( - LANG_SERVER_TOOL_ID, - msg, - 'Python', - ['Install', "Don't Show Again"], - loggingService, - () => { - config.update('ignoreWarning.fortls', true); - } - ).then(() => { - // fortls not installed AND Warnings are enabled - new FortranLanguageServer(loggingService).activate(); - }); - } - // Ignore fortls Warnings NOT set. Activate the LS - else { - new FortranLanguageServer(loggingService).activate(); - } - } - // Ignore fortls Warnings are SET. Activate the LS - else { - new FortranLanguageServer(loggingService).activate(); - } - }); + if (!config.get('fortls.disabled')) { + new FortlsClient(loggingService, context).activate(); } } diff --git a/src/features/commands.ts b/src/features/commands.ts index baa191af..9fbaf9b6 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -1,47 +1,5 @@ 'use strict'; -import * as vscode from 'vscode'; -import { checkLanguageServerActivation, clients } from './fortls-interface'; +// Module to hold all command names export const RestartLS = 'fortran.analysis.restartLanguageServer'; -export const StarttLS = 'fortran.analysis.startLanguageServer'; -export const StoptLS = 'fortran.analysis.stopLanguageServer'; - -let commandsActivated = false; - -export function registerCommands(disposables: vscode.Disposable[]): void { - if (commandsActivated) return; - - commandsActivated = true; - disposables.push(vscode.commands.registerCommand(RestartLS, onRestartLS)); - disposables.push(vscode.commands.registerCommand(StarttLS, onStartLS)); - disposables.push(vscode.commands.registerCommand(StoptLS, onStopLS)); -} - -function onRestartLS(): void { - const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; - vscode.window.showInformationMessage('Restarting the Fortran Language Server'); - const folder = checkLanguageServerActivation(activeEditor.document); - - if (!folder) return; - clients.get(folder.uri.toString()).stop(); - clients.get(folder.uri.toString()).start(); -} - -function onStartLS(): void { - const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; - vscode.window.showInformationMessage('Starting the Fortran Language Server'); - const folder = checkLanguageServerActivation(activeEditor.document); - - if (!folder) return; - clients.get(folder.uri.toString()).start(); -} - -function onStopLS(): void { - const activeEditor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; - vscode.window.showInformationMessage('Stopping the Fortran Language Server'); - const folder = checkLanguageServerActivation(activeEditor.document); - - if (!folder) return; - clients.get(folder.uri.toString()).stop(); -} diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 104aed18..e2b8833d 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -5,8 +5,9 @@ import * as cp from 'child_process'; import { LoggingService } from '../services/logging-service'; import { isString, isArrayOfString } from './helper'; +export const LS_NAME = 'fortls'; export const EXTENSION_ID = 'fortran'; -export const LANG_SERVER_TOOL_ID = 'fortls'; +export const EXTENSION_VSSTORE_ID = 'krvajalm.linter-gfortran'; export const FORMATTERS = ['Disabled', 'findent', 'fprettify']; // Platform-specific environment variable delimiter diff --git a/src/features/fortls-interface.ts b/src/lsp/client.ts similarity index 69% rename from src/features/fortls-interface.ts rename to src/lsp/client.ts index 87e0416a..2dc1c63e 100644 --- a/src/features/fortls-interface.ts +++ b/src/lsp/client.ts @@ -2,11 +2,13 @@ 'use strict'; +import * as vscode from 'vscode'; import { spawnSync } from 'child_process'; import { commands, window, workspace, TextDocument, WorkspaceFolder } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; -import { EXTENSION_ID, FortranDocumentSelector } from '../lib/tools'; +import { EXTENSION_ID, FortranDocumentSelector, LS_NAME } from '../lib/tools'; import { LoggingService } from '../services/logging-service'; +import { RestartLS } from '../features/commands'; // The clients are non member variables of the class because they need to be // shared for command registration. The command operates on the client and not @@ -38,23 +40,38 @@ export function checkLanguageServerActivation(document: TextDocument): Workspace return folder; } -export class FortranLanguageServer { - constructor(private logger: LoggingService) { +export class FortlsClient { + constructor(private logger: LoggingService, private context?: vscode.ExtensionContext) { this.logger.logInfo('Fortran Language Server'); + + // if context is present + if (context !== undefined) { + // Register Language Server Commands + this.context.subscriptions.push( + vscode.commands.registerCommand(RestartLS, this.restartLS, this) + ); + } } + private client: LanguageClient | undefined; private _fortlsVersion: string | undefined; public async activate() { - workspace.onDidOpenTextDocument(this.didOpenTextDocument, this); - workspace.textDocuments.forEach(this.didOpenTextDocument, this); + // Detect if fortls is present, download if missing or disable LS functionality + // Do not allow activating the LS functionality if no fortls is detected + await this.fortlsDownload().then(fortlsDisabled => { + if (fortlsDisabled) return; + workspace.onDidOpenTextDocument(this.didOpenTextDocument, this); + workspace.textDocuments.forEach(this.didOpenTextDocument, this); + }); return; } public async deactivate(): Promise { const promises: Thenable[] = []; - for (const client of clients.values()) { - promises.push(client.stop()); + for (const [key, client] of clients.entries()) { + promises.push(client.stop()); // stop the language server + clients.delete(key); // delete the URI from the map } await Promise.all(promises); return undefined; @@ -99,14 +116,15 @@ export class FortranLanguageServer { }; // Create the language client, start the client and add it to the registry - const client = new LanguageClient( - 'fortls', + this.client = new LanguageClient( + LS_NAME, 'Fortran Language Server', serverOptions, clientOptions ); - client.start(); - clients.set(folder.uri.toString(), client); + this.client.start(); + // Add the Language Client to the global map + clients.set(folder.uri.toString(), this.client); } } @@ -117,7 +135,6 @@ export class FortranLanguageServer { private async fortlsArguments() { // Get path for the language server const conf = workspace.getConfiguration(EXTENSION_ID); - const executablePath = conf.get('fortls.path'); const maxLineLength = conf.get('fortls.maxLineLength'); const maxCommentLineLength = conf.get('fortls.maxCommentLineLength'); const fortlsExtraArgs = conf.get('fortls.extraArgs'); @@ -215,4 +232,56 @@ export class FortranLanguageServer { } return results.stdout.toString().trim(); } + + /** + * Check if fortls is present in the system, if not show prompt to install/disable. + * If disabling or erroring the function will return true. + * For all normal cases it should return false. + * + * @returns false if the fortls has been detected or installed successfully + */ + private async fortlsDownload(): Promise { + const config = workspace.getConfiguration(EXTENSION_ID); + const ls = config.get('fortls.path'); + + // Check for version, if this fails fortls provided is invalid + const results = spawnSync(ls, ['--version']); + const msg = `It is highly recommended to use the fortls to enable IDE features like hover, peeking, GoTos and many more. + For a full list of features the language server adds see: https://github.com/gnikit/fortls`; + return new Promise(resolve => { + let fortlsDisabled = false; + if (results.error) { + const selection = window.showInformationMessage(msg, 'Install', 'Disable'); + selection.then(opt => { + if (opt === 'Install') { + const install = spawnSync('pip', ['install', '--user', '--upgrade', LS_NAME]); + if (install.error) { + window.showErrorMessage('Had trouble installing fortls, please install manually'); + fortlsDisabled = true; + } + if (install.stdout) { + this.logger.logInfo(install.stdout.toString()); + fortlsDisabled = false; + } + } else if (opt == 'Disable') { + config.update('fortls.disabled', true); + fortlsDisabled = true; + } + resolve(fortlsDisabled); + }); + } else { + resolve(false); + } + }); + } + + /** + * Restart the language server + */ + private async restartLS(): Promise { + this.logger.logInfo('Restarting language server...'); + vscode.window.showInformationMessage('Restarting language server...'); + await this.deactivate(); + await this.activate(); + } }