Skip to content

Commit d690b8b

Browse files
committed
Rename and add more docs
1 parent 3642f5a commit d690b8b

File tree

4 files changed

+72
-57
lines changed

4 files changed

+72
-57
lines changed

src/config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { OutputChannel, Uri, window, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
1+
import { OutputChannel, Uri, window, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
22
import { expandHomeDir, IEnvVars } from './utils';
33
import * as path from 'path';
44
import { Logger } from 'vscode-languageclient';
@@ -87,10 +87,10 @@ function getClientLogLevel(workspaceConfig: WorkspaceConfiguration): ClientLogLe
8787
clientLogLevel = clientLogLevel_;
8888
break;
8989
default:
90-
throw new Error();
90+
throw new Error("Option \"haskell.trace.client\" is expected to be one of 'off', 'error', 'info', 'debug'.");
9191
}
9292
} else {
93-
throw new Error();
93+
throw new Error('Option "haskell.trace.client" is expected to be a string');
9494
}
9595
return clientLogLevel;
9696
}
@@ -106,10 +106,10 @@ function getLogLevel(workspaceConfig: WorkspaceConfiguration): LogLevel {
106106
logLevel = logLevel_;
107107
break;
108108
default:
109-
throw new Error("haskell.trace.server is expected to be one of 'off', 'messages', 'verbose'.");
109+
throw new Error("Option \"haskell.trace.server\" is expected to be one of 'off', 'messages', 'verbose'.");
110110
}
111111
} else {
112-
throw new Error('haskell.trace.server is expected to be a string');
112+
throw new Error('Option "haskell.trace.server" is expected to be a string');
113113
}
114114
return logLevel;
115115
}

src/errors.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ export class MissingToolError extends HlsError {
2020
prettyTool = 'GHCup';
2121
break;
2222
case 'haskell-language-server':
23-
prettyTool = 'HLS';
24-
break;
2523
case 'hls':
2624
prettyTool = 'HLS';
2725
break;

src/extension.ts

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -120,34 +120,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
120120
try {
121121
hlsExecutable = await findHaskellLanguageServer(context, logger, config.workingDir, folder);
122122
} catch (e) {
123-
if (e instanceof MissingToolError) {
124-
const link = e.installLink();
125-
if (link) {
126-
if (await window.showErrorMessage(e.message, `Install ${e.tool}`)) {
127-
env.openExternal(link);
128-
}
129-
} else {
130-
await window.showErrorMessage(e.message);
131-
}
132-
} else if (e instanceof HlsError) {
133-
logger.error(`General HlsError: ${e.message}`);
134-
window.showErrorMessage(e.message);
135-
} else if (e instanceof NoMatchingHls) {
136-
const link = e.docLink();
137-
logger.error(`${e.message}`);
138-
if (await window.showErrorMessage(e.message, 'Open documentation')) {
139-
env.openExternal(link);
140-
}
141-
} else if (e instanceof Error) {
142-
logger.error(`Internal Error: ${e.message}`);
143-
window.showErrorMessage(e.message);
144-
}
145-
if (e instanceof Error) {
146-
// general stack trace printing
147-
if (e.stack) {
148-
logger.error(`${e.stack}`);
149-
}
150-
}
123+
await handleInitializationError(e, logger);
151124
return;
152125
}
153126

@@ -261,6 +234,43 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
261234
await langClient.start();
262235
}
263236

237+
/**
238+
* Handle errors the extension may throw. Errors are expected to be fatal.
239+
*
240+
* @param e Error thrown during the extension initialization.
241+
* @param logger
242+
*/
243+
async function handleInitializationError(e: unknown, logger: Logger) {
244+
if (e instanceof MissingToolError) {
245+
const link = e.installLink();
246+
if (link) {
247+
if (await window.showErrorMessage(e.message, `Install ${e.tool}`)) {
248+
env.openExternal(link);
249+
}
250+
} else {
251+
await window.showErrorMessage(e.message);
252+
}
253+
} else if (e instanceof HlsError) {
254+
logger.error(`General HlsError: ${e.message}`);
255+
window.showErrorMessage(e.message);
256+
} else if (e instanceof NoMatchingHls) {
257+
const link = e.docLink();
258+
logger.error(`${e.message}`);
259+
if (await window.showErrorMessage(e.message, 'Open documentation')) {
260+
env.openExternal(link);
261+
}
262+
} else if (e instanceof Error) {
263+
logger.error(`Internal Error: ${e.message}`);
264+
window.showErrorMessage(e.message);
265+
}
266+
if (e instanceof Error) {
267+
// general stack trace printing
268+
if (e.stack) {
269+
logger.error(`${e.stack}`);
270+
}
271+
}
272+
}
273+
264274
function initServerEnvironment(config: Config, hlsExecutable: HlsExecutable) {
265275
let serverEnvironment: IEnvVars = config.serverEnvironment;
266276
if (hlsExecutable.tag === 'ghcup') {

src/hlsBinaries.ts

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ function findServerExecutable(logger: Logger, folder?: WorkspaceFolder): string
4949
}
5050
}
5151

52-
/** Searches the PATH. Fails if nothing is found.
52+
/**
53+
* Searches the `PATH` for `haskell-language-server` or `haskell-language-server-wrapper` binary.
54+
* Fails if nothing is found.
55+
* @param logger Log all the stuff!
56+
* @returns Location of the `haskell-language-server` or `haskell-language-server-wrapper` binary if found.
5357
*/
5458
function findHlsInPath(logger: Logger): string {
5559
// try PATH
@@ -87,17 +91,19 @@ export type HlsViaGhcup = {
8791
};
8892

8993
/**
90-
* Downloads the latest haskell-language-server binaries via GHCup.
91-
* Makes sure that either `ghcup` is available locally, otherwise installs
92-
* it into an isolated location.
93-
* If we figure out the correct GHC version, but it isn't compatible with
94-
* the latest HLS executables, we download the latest compatible HLS binaries
95-
* as a fallback.
94+
* Find and setup the Haskell Language Server.
95+
*
96+
* We support three ways of finding the HLS binary:
97+
*
98+
* 1. Let the user provide a location via `haskell.serverExecutablePath` option.
99+
* 2. Find a `haskell-language-server` binary on the `$PATH` if the user wants to do that.
100+
* 3. Use GHCup to install and locate HLS and other required tools, such as cabal, stack and ghc.
96101
*
97102
* @param context Context of the extension, required for metadata.
98103
* @param logger Logger for progress updates.
99104
* @param workingDir Working directory in VSCode.
100-
* @returns Path to haskell-language-server-wrapper
105+
* @param folder Optional workspace folder. If given, will be preferred over {@link workingDir} for finding configuration entries.
106+
* @returns Path to haskell-language-server, paired with additional data required for setting up.
101107
*/
102108
export async function findHaskellLanguageServer(
103109
context: ExtensionContext,
@@ -124,6 +130,7 @@ export async function findHaskellLanguageServer(
124130
// first extension initialization
125131
manageHLS = await promptUserForManagingHls(context, manageHLS);
126132

133+
// based on the user-decision
127134
if (manageHLS === 'PATH') {
128135
const exe = findHlsInPath(logger);
129136
return {
@@ -176,21 +183,21 @@ export async function findHaskellLanguageServer(
176183
// download popups
177184
const promptBeforeDownloads = workspace.getConfiguration('haskell').get('promptBeforeDownloads') as boolean;
178185
if (promptBeforeDownloads) {
179-
const hlsInstalled = latestHLS ? await toolInstalled(ghcup, 'hls', latestHLS) : undefined;
180-
const cabalInstalled = latestCabal ? await toolInstalled(ghcup, 'cabal', latestCabal) : undefined;
181-
const stackInstalled = latestStack ? await toolInstalled(ghcup, 'stack', latestStack) : undefined;
186+
const hlsInstalled = latestHLS ? await installationStatusOfGhcupTool(ghcup, 'hls', latestHLS) : undefined;
187+
const cabalInstalled = latestCabal ? await installationStatusOfGhcupTool(ghcup, 'cabal', latestCabal) : undefined;
188+
const stackInstalled = latestStack ? await installationStatusOfGhcupTool(ghcup, 'stack', latestStack) : undefined;
182189
const ghcInstalled = executableExists('ghc')
183-
? new InstalledTool(
190+
? new ToolStatus(
184191
'ghc',
185192
await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false),
186193
)
187194
: // if recGHC is null, that means user disabled automatic handling,
188195
recGHC !== null
189-
? await toolInstalled(ghcup, 'ghc', recGHC)
196+
? await installationStatusOfGhcupTool(ghcup, 'ghc', recGHC)
190197
: undefined;
191-
const toInstall: InstalledTool[] = [hlsInstalled, cabalInstalled, stackInstalled, ghcInstalled].filter(
198+
const toInstall: ToolStatus[] = [hlsInstalled, cabalInstalled, stackInstalled, ghcInstalled].filter(
192199
(tool) => tool && !tool.installed,
193-
) as InstalledTool[];
200+
) as ToolStatus[];
194201
if (toInstall.length > 0) {
195202
const decision = await window.showInformationMessage(
196203
`Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
@@ -258,11 +265,11 @@ export async function findHaskellLanguageServer(
258265

259266
// more download popups
260267
if (promptBeforeDownloads) {
261-
const hlsInstalled = projectHls ? await toolInstalled(ghcup, 'hls', projectHls) : undefined;
262-
const ghcInstalled = projectGhc ? await toolInstalled(ghcup, 'ghc', projectGhc) : undefined;
263-
const toInstall: InstalledTool[] = [hlsInstalled, ghcInstalled].filter(
268+
const hlsInstalled = projectHls ? await installationStatusOfGhcupTool(ghcup, 'hls', projectHls) : undefined;
269+
const ghcInstalled = projectGhc ? await installationStatusOfGhcupTool(ghcup, 'ghc', projectGhc) : undefined;
270+
const toInstall: ToolStatus[] = [hlsInstalled, ghcInstalled].filter(
264271
(tool) => tool && !tool.installed,
265-
) as InstalledTool[];
272+
) as ToolStatus[];
266273
if (toInstall.length > 0) {
267274
const decision = await window.showInformationMessage(
268275
`Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
@@ -511,18 +518,18 @@ async function findAvailableHlsBinariesFromGHCup(ghcup: GHCup): Promise<Map<stri
511518
}
512519
}
513520

514-
async function toolInstalled(ghcup: GHCup, tool: Tool, version: string): Promise<InstalledTool> {
521+
async function installationStatusOfGhcupTool(ghcup: GHCup, tool: Tool, version: string): Promise<ToolStatus> {
515522
const b = await ghcup
516523
.call(['whereis', tool, version], undefined, false)
517524
.then(() => true)
518525
.catch(() => false);
519-
return new InstalledTool(tool, version, b);
526+
return new ToolStatus(tool, version, b);
520527
}
521528

522529
/**
523530
* Tracks the name, version and installation state of tools we need.
524531
*/
525-
class InstalledTool {
532+
class ToolStatus {
526533
/**
527534
* "\<name\>-\<version\>" of the installed Tool.
528535
*/

0 commit comments

Comments
 (0)