Skip to content

Commit 3b0b82e

Browse files
committed
Add better messages when the cradle's tool isn't installed
1 parent 15878a6 commit 3b0b82e

File tree

2 files changed

+74
-19
lines changed

2 files changed

+74
-19
lines changed

src/extension.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
import { CommandNames } from './commands/constants';
2323
import { ImportIdentifier } from './commands/importIdentifier';
2424
import { DocsBrowser } from './docsBrowser';
25-
import { downloadServer } from './hlsBinaries';
25+
import { downloadHaskellLanguageServer } from './hlsBinaries';
2626
import { executableExists } from './utils';
2727

2828
const clients: Map<string, LanguageClient> = new Map();
@@ -140,13 +140,19 @@ async function activateHieNoCheck(context: ExtensionContext, uri: Uri, folder?:
140140

141141
let serverExecutable;
142142
try {
143-
serverExecutable =
144-
findManualExecutable(uri, folder) ??
145-
findLocalServer(context, uri, folder) ??
146-
(await downloadServer(context, uri, folder));
143+
// Try and find local installations first
144+
serverExecutable = findManualExecutable(uri, folder) ?? findLocalServer(context, uri, folder);
147145
if (serverExecutable === null) {
148-
showNotInstalledErrorMessage(uri);
149-
return;
146+
// If not, then try to download haskell-language-server binaries if it's selected
147+
if (workspace.getConfiguration('haskell', uri).languageServerVariant === 'haskell-language-server') {
148+
serverExecutable = await downloadHaskellLanguageServer(context, uri, folder);
149+
if (!serverExecutable) {
150+
return;
151+
}
152+
} else {
153+
showNotInstalledErrorMessage(uri);
154+
return;
155+
}
150156
}
151157
} catch (e) {
152158
if (e instanceof Error) {

src/hlsBinaries.ts

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
'use strict';
2-
31
import * as child_process from 'child_process';
42
import * as fs from 'fs';
53
import * as https from 'https';
64
import * as path from 'path';
75
import * as url from 'url';
8-
import { ExtensionContext, ProgressLocation, Uri, window, workspace, WorkspaceFolder } from 'vscode';
6+
import { env, ExtensionContext, ProgressLocation, Uri, window, workspace, WorkspaceFolder } from 'vscode';
97
import { downloadFile, executableExists, userAgentHeader } from './utils';
108

119
/** GitHub API release */
@@ -23,6 +21,43 @@ interface IAsset {
2321
// On Windows the executable needs to be stored somewhere with an .exe extension
2422
const exeExtension = process.platform === 'win32' ? '.exe' : '';
2523

24+
class MissingToolError extends Error {
25+
public readonly tool: string;
26+
constructor(tool: string) {
27+
let prettyTool: string;
28+
switch (tool) {
29+
case 'stack':
30+
prettyTool = 'Stack';
31+
break;
32+
case 'cabal':
33+
prettyTool = 'Cabal';
34+
break;
35+
case 'ghc':
36+
prettyTool = 'GHC';
37+
break;
38+
default:
39+
prettyTool = tool;
40+
break;
41+
}
42+
super(`Project requires ${prettyTool} but it isn't installed`);
43+
this.tool = prettyTool;
44+
}
45+
46+
public installLink(): Uri | null {
47+
switch (this.tool) {
48+
case 'Stack':
49+
return Uri.parse('https://docs.haskellstack.org/en/stable/install_and_upgrade/');
50+
case 'Cabal':
51+
case 'GHC':
52+
return process.platform === 'win32'
53+
? Uri.parse('https://www.haskell.org/platform/index.html#windows')
54+
: Uri.parse('https://www.haskell.org/ghcup/');
55+
default:
56+
return null;
57+
}
58+
}
59+
}
60+
2661
/** Works out what the project's ghc version is, downloading haskell-language-server-wrapper
2762
* if needed. Returns null if there was an error in either downloading the wrapper or
2863
* in working out the ghc version
@@ -41,7 +76,15 @@ async function getProjectGhcVersion(context: ExtensionContext, dir: string, rele
4176
throw out.error;
4277
}
4378
if (out.status !== 0) {
44-
throw Error(`${wrapper} --project-ghc-version exited with exit code ${out.status}:\n${out.stderr}`);
79+
const regex = /Cradle requires (.+) but couldn't find it/;
80+
const res = regex.exec(out.stderr);
81+
if (res) {
82+
throw new MissingToolError(res[1]);
83+
}
84+
85+
throw Error(
86+
`${wrapper} --project-ghc-version exited with exit code ${out.status}:\n${out.stdout}\n${out.stderr}`
87+
);
4588
}
4689
return out.stdout.trim();
4790
}
@@ -87,16 +130,11 @@ async function getProjectGhcVersion(context: ExtensionContext, dir: string, rele
87130
* Downloads the latest haskell-language-server binaries from GitHub releases.
88131
* Returns null if it can't find any that match.
89132
*/
90-
export async function downloadServer(
133+
export async function downloadHaskellLanguageServer(
91134
context: ExtensionContext,
92135
resource: Uri,
93136
folder?: WorkspaceFolder
94137
): Promise<string | null> {
95-
// We only download binaries for haskell-language-server at the moment
96-
if (workspace.getConfiguration('haskell', resource).languageServerVariant !== 'haskell-language-server') {
97-
return null;
98-
}
99-
100138
// Fetch the latest release from GitHub
101139
const releases: IRelease[] = await new Promise((resolve, reject) => {
102140
let data: string = '';
@@ -137,8 +175,19 @@ export async function downloadServer(
137175
try {
138176
ghcVersion = await getProjectGhcVersion(context, dir, release);
139177
} catch (error) {
140-
// We couldn't figure out the right ghc version to download
141-
window.showErrorMessage(`Couldn't figure out what GHC version the project is using:\n${error.message}`);
178+
if (error instanceof MissingToolError) {
179+
const link = error.installLink();
180+
if (link) {
181+
if (await window.showErrorMessage(error.message, `Install ${error.tool}`)) {
182+
env.openExternal(link);
183+
}
184+
} else {
185+
await window.showErrorMessage(error.message);
186+
}
187+
} else {
188+
// We couldn't figure out the right ghc version to download
189+
window.showErrorMessage(`Couldn't figure out what GHC version the project is using:\n${error.message}`);
190+
}
142191
return null;
143192
}
144193

0 commit comments

Comments
 (0)