From db6d1e3064ad99e627c4e60631cb94a06d57ec42 Mon Sep 17 00:00:00 2001 From: Miguel Carvajal Date: Sun, 14 Oct 2018 15:24:08 +0200 Subject: [PATCH] Improve location of external tools (#91) * Extract intrisinc list into its own file * Add type attributes completion gif * Add a basic language server implementation * Update the minum supported version of vscode to 1.22.0 * Update changelog * Locate bin tools by path * Improve bin tools lookup * Add command for install the fortran lang server --- CHANGELOG.md | 4 ++-- README.md | 11 +++++++++++ src/extension.ts | 12 +++++++----- src/lang-server.ts | 33 +++++++++++++++++++++++++++++---- src/lib/helper.ts | 19 ++++++++++++++++--- src/lib/paths.ts | 40 ++++++++++++++++++++++++++++++++++++++++ src/lib/tools.ts | 23 +++++++++++++++++++++++ 7 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 src/lib/paths.ts create mode 100644 src/lib/tools.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index cd45ffac..9102a7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Autoindentation rules for code blocks (thx @graceyangfan for the feature request) +- Auto indentation rules for code blocks (thx @graceyangfan for the feature request) ### Fixed @@ -81,7 +81,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed -- A bug in the regex to parse output errors from gfortran +- A bug in the regex to parse output errors from `gfortran` - Now the spawn command uses the directory of the file `gfortran` is analyzing ## [0.5.1] - 2017-07-06 diff --git a/README.md b/README.md index 2d7a1be7..2dea7f05 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,17 @@ This is a list of some of the snippets included, if you like to include addition To trigger code validations you must save the file first. +## Fortran Language Server (Experimental) + +This extension uses a host of tools to provide the various language features. An alternative is to use a single language server that provides the same feature. + +Set `fortran.useLanguageServer` to `true` to use the Fortran language server from [Chris Hansen](https://github.com/hansec/fortran-language-server) for features like Hover, Definition, Find All References, Signature Help, Go to Symbol in File and Workspace. + +- This is an experimental feature and is not available in Windows yet. +- Since only a single language server is spun up for given VS Code instance, having multi-root setup does not work +- If set to true, you will be prompted to install the Fortran language server. Once installed, you will have to reload VS Code window. The language server will then be run by the Fortran extension in the background to provide services needed for the above mentioned features. +- Every time you change the value of the setting `fortran.useLanguageServer`, you need to reload the VS Code window for it to take effect. + ## Requirements For the linter to work you need to have `gfortran` on your path, or wherever you configure it to be. diff --git a/src/extension.ts b/src/extension.ts index e677b5ac..ae17fd11 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,9 +5,9 @@ import FortranLintingProvider from './features/linter-provider' import FortranHoverProvider from './features/hover-provider' import { FortranCompletionProvider } from './features/completion-provider' import { FortranDocumentSymbolProvider } from './features/document-symbol-provider' -import { FORTRAN_FREE_FORM_ID } from './lib/helper' -import { FortranLangServer } from './lang-server' -import { ConfigurationFeature } from 'vscode-languageclient/lib/configuration' + +import { FORTRAN_FREE_FORM_ID, EXTENSION_ID } from './lib/helper' +import { FortranLangServer, checkForLangServer } from './lang-server' export function activate(context: vscode.ExtensionContext) { @@ -15,7 +15,7 @@ export function activate(context: vscode.ExtensionContext) { let completionProvider = new FortranCompletionProvider() let symbolProvider = new FortranDocumentSymbolProvider() - const extensionConfig = vscode.workspace.getConfiguration('LANGUAGE_ID') + const extensionConfig = vscode.workspace.getConfiguration(EXTENSION_ID) if (extensionConfig.get('linterEnabled', true)) { let linter = new FortranLintingProvider() @@ -33,7 +33,9 @@ export function activate(context: vscode.ExtensionContext) { FORTRAN_FREE_FORM_ID, symbolProvider ) - if (extensionConfig.get('useLanguageServer')) { + + if (checkForLangServer(extensionConfig)) { + const langServer = new FortranLangServer(context, extensionConfig) langServer.start() langServer.onReady().then(() => { diff --git a/src/lang-server.ts b/src/lang-server.ts index d83b360e..432d0a58 100644 --- a/src/lang-server.ts +++ b/src/lang-server.ts @@ -3,15 +3,22 @@ import { LanguageClientOptions, Executable, } from 'vscode-languageclient' -import { getBinPath, FORTRAN_FREE_FORM_ID } from './lib/helper' +import * as vscode from 'vscode' +import { + getBinPath, + FORTRAN_FREE_FORM_ID, + promptForMissingTool, +} from './lib/helper' +import { LANG_SERVER_TOOL_ID } from './lib/tools' + +export class FortranLangServer { -class FortranLangServer { c: LanguageClient constructor(context, config) { let langServerFlags: string[] = config.get('languageServerFlags', []) const serverOptions: Executable = { - command: getBinPath('fortran-langserver'), + command: getBinPath(LANG_SERVER_TOOL_ID), args: [...langServerFlags], options: {}, } @@ -42,4 +49,22 @@ class FortranLangServer { } } -export { FortranLangServer } +export function checkForLangServer(config) { + const useLangServer = config.get('useLanguageServer') + if (!useLangServer) return false + if (process.platform === 'win32') { + vscode.window.showInformationMessage( + 'The Fortran language server is not supported on Windows yet.' + ) + return false + } + let langServerAvailable = getBinPath('fortran-langserver') + if (!langServerAvailable) { + promptForMissingTool(LANG_SERVER_TOOL_ID) + vscode.window.showInformationMessage( + 'Reload VS Code window after installing the Fortran language server' + ) + } + return true +} + diff --git a/src/lib/helper.ts b/src/lib/helper.ts index 3508cfa6..58a0e8a0 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -1,6 +1,7 @@ import * as fs from 'fs' import * as vscode from 'vscode' import intrinsics from './fortran-intrinsics' +import { installTool } from './tools' // IMPORTANT: this should match the value // on the package.json otherwise the extension won't @@ -10,7 +11,6 @@ export const FORTRAN_FREE_FORM_ID = { language: LANGUAGE_ID, scheme: 'file' } export { intrinsics } export const EXTENSION_ID = 'fortran' - export const FORTRAN_KEYWORDS = [ 'FUNCTION', 'MODULE', @@ -118,6 +118,19 @@ let saveKeywordToJson = keyword => { }) } -export function getBinPath(tool: string): string { - return '/usr/local/bin/fortls' +export { default as getBinPath } from './paths' + +export function promptForMissingTool(tool: string) { + const items = ['Install'] + let message = '' + if (tool === 'fortran-langserver') { + message = + 'You choose to use the fortranLanguageServer functionality but it is not installed. Please press the Install button to install it' + } + vscode.window.showInformationMessage(message, ...items).then(selected => { + if (selected === 'Install') { + installTool(tool) + } + }) + } diff --git a/src/lib/paths.ts b/src/lib/paths.ts new file mode 100644 index 00000000..1c0463e1 --- /dev/null +++ b/src/lib/paths.ts @@ -0,0 +1,40 @@ +import * as fs from 'fs' +import * as path from 'path' +import { toolBinNames } from './tools' + +let binPathCache: { [tool: string]: string } = {} + +export const envPath = + process.env['PATH'] || + (process.platform === 'win32' ? process.env['Path'] : null) + +// checks in the PATH defined in the PATH env variables +// for the specified tools and returns its complete path +export default function getBinPath(tool: string): string | null { + if (binPathCache[tool]) return binPathCache[tool] + const binDirPaths = envPath.split(path.delimiter) + const binName = getBinName(tool) + const possiblePaths = binDirPaths.map(binDirPath => + path.join(binDirPath, binName) + ) + for (let p of possiblePaths) { + if (fileExists(p)) { + // save in cache + binPathCache[tool] = p + return p + } + } + return null +} + +function getBinName(tool: string): string { + return toolBinNames[tool] +} + +function fileExists(filePath: string): boolean { + try { + return fs.statSync(filePath).isFile() + } catch (e) { + return false + } +} diff --git a/src/lib/tools.ts b/src/lib/tools.ts new file mode 100644 index 00000000..a225f0c8 --- /dev/null +++ b/src/lib/tools.ts @@ -0,0 +1,23 @@ +export const LANG_SERVER_TOOL_ID = 'fortran-langserver' +import * as cp from 'child_process' +export const toolBinNames = { + [LANG_SERVER_TOOL_ID]: 'fortls', + 'gnu-compiler': 'gfortran', +} + +export function installTool(toolname) { + if (toolname === LANG_SERVER_TOOL_ID) { + const installProcess = cp.spawn( + 'pip', + 'install fortran-language-server'.split(' ') + ) + installProcess.on('exit', (code, signal) => { + if (code !== 0) { + // extension failed to install + } + }) + installProcess.on('error', err => { + //failed to install + }) + } +}