diff --git a/package.json b/package.json index 159b5186..6f045f6f 100644 --- a/package.json +++ b/package.json @@ -234,6 +234,22 @@ "Prefer a multiple component session, if the build tool supports it. At the moment, only `cabal` supports multiple components session loading. If the `cabal` version does not support loading multiple components at once, we gracefully fall back to \"singleComponent\" mode." ] }, + "haskell.supportCabalFiles": { + "scope": "resource", + "default": "automatic", + "type": "string", + "enum": [ + "enable", + "disable", + "automatic" + ], + "description": "Enable Language Server support for `.cabal` files. Requires Haskell Language Server version >= 1.9.0.0.", + "enumDescriptions": [ + "Enable Language Server support for `.cabal` files", + "Disable Language Server support for `.cabal` files", + "Enable Language Server support for `.cabal` files if the HLS version supports it." + ] + }, "haskell.maxCompletions": { "scope": "resource", "default": 40, diff --git a/src/extension.ts b/src/extension.ts index 111b72ed..480ba77a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,8 +21,8 @@ import { import { RestartServerCommandName, StartServerCommandName, StopServerCommandName } from './commands/constants'; import * as DocsBrowser from './docsBrowser'; import { HlsError, MissingToolError, NoMatchingHls } from './errors'; -import { findHaskellLanguageServer, IEnvVars } from './hlsBinaries'; -import { addPathToProcessPath, expandHomeDir, ExtensionLogger } from './utils'; +import { callAsync, findHaskellLanguageServer, IEnvVars } from './hlsBinaries'; +import { addPathToProcessPath, comparePVP, expandHomeDir, ExtensionLogger } from './utils'; // The current map of documents & folders to language servers. // It may be null to indicate that we are in the process of launching a server, @@ -205,6 +205,12 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold args = args.concat(extraArgs.split(' ')); } + const cabalFileSupport: 'automatic' | 'enable' | 'disable' = workspace.getConfiguration( + 'haskell', + uri, + ).supportCabalFiles; + logger.info(`Support for '.cabal' files: ${cabalFileSupport}`); + // If we're operating on a standalone file (i.e. not in a folder) then we need // to launch the server in a reasonable current directory. Otherwise the cradle // guessing logic in hie-bios will be wrong! @@ -253,14 +259,44 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold const pat = folder ? `${folder.uri.fsPath}/**/*` : '**/*'; logger.log(`document selector patten: ${pat}`); + + const cabalDocumentSelector = { scheme: 'file', language: 'cabal', pattern: pat }; + const haskellDocumentSelector = [ + { scheme: 'file', language: 'haskell', pattern: pat }, + { scheme: 'file', language: 'literate haskell', pattern: pat }, + ]; + + const documentSelector = [...haskellDocumentSelector]; + + switch (cabalFileSupport) { + case 'automatic': + const hlsVersion = await callAsync( + serverExecutable, + ['--numeric-version'], + logger, + currentWorkingDir, + undefined /* this command is very fast, don't show anything */, + false, + serverEnvironment, + ); + if (comparePVP(hlsVersion, '1.9.0.0') >= 0) { + // If hlsVersion is >= '1.9.0.0' + documentSelector.push(cabalDocumentSelector); + } + break; + case 'enable': + documentSelector.push(cabalDocumentSelector); + break; + case 'disable': + break; + default: + break; + } + const clientOptions: LanguageClientOptions = { // Use the document selector to only notify the LSP on files inside the folder // path for the specific workspace. - documentSelector: [ - { scheme: 'file', language: 'haskell', pattern: pat }, - { scheme: 'file', language: 'literate haskell', pattern: pat }, - { scheme: 'file', language: 'cabal', pattern: pat }, - ], + documentSelector: [...documentSelector], synchronize: { // Synchronize the setting section 'haskell' to the server. configurationSection: 'haskell', diff --git a/src/hlsBinaries.ts b/src/hlsBinaries.ts index 91a42f59..65ea68c7 100644 --- a/src/hlsBinaries.ts +++ b/src/hlsBinaries.ts @@ -58,7 +58,7 @@ type ProcessCallback = ( * @param callback Upon process termination, execute this callback. If given, must resolve promise. On error, stderr and stdout are logged regardless of whether the callback has been specified. * @returns Stdout of the process invocation, trimmed off newlines, or whatever the `callback` resolved to. */ -async function callAsync( +export async function callAsync( binary: string, args: string[], logger: Logger,