Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/lsp #436

Merged
merged 5 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
25 changes: 0 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
},
Expand Down Expand Up @@ -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": {
Expand All @@ -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"
}
]
}
Expand Down
50 changes: 4 additions & 46 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<string>('fortls.disabled')) {
which(config.get<string>('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<boolean>('fortls.disabled')) {
new FortlsClient(loggingService, context).activate();
}
}

Expand Down
44 changes: 1 addition & 43 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
@@ -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();
}
3 changes: 2 additions & 1 deletion src/lib/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 81 additions & 12 deletions src/features/fortls-interface.ts → src/lsp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<void> {
const promises: Thenable<void>[] = [];
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;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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<string>('fortls.path');
const maxLineLength = conf.get<number>('fortls.maxLineLength');
const maxCommentLineLength = conf.get<number>('fortls.maxCommentLineLength');
const fortlsExtraArgs = conf.get<string[]>('fortls.extraArgs');
Expand Down Expand Up @@ -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<boolean> {
const config = workspace.getConfiguration(EXTENSION_ID);
const ls = config.get<string>('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<boolean>(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<void> {
this.logger.logInfo('Restarting language server...');
vscode.window.showInformationMessage('Restarting language server...');
await this.deactivate();
await this.activate();
}
}