Skip to content

Commit 7a4c578

Browse files
hasufellfendor
authored andcommitted
Fix bugs, improve popups and other stuff
1 parent 3397b1a commit 7a4c578

File tree

3 files changed

+76
-67
lines changed

3 files changed

+76
-67
lines changed

src/extension.ts

Lines changed: 2 additions & 4 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 { addPathToProcessPath, findHaskellLanguageServer, IEnvVars, validateHLSToolchain } from './hlsBinaries';
25+
import { addPathToProcessPath, findHaskellLanguageServer, IEnvVars } from './hlsBinaries';
2626
import { expandHomeDir, ExtensionLogger } from './utils';
2727

2828
// The current map of documents & folders to language servers.
@@ -147,9 +147,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
147147
let serverExecutable;
148148
let addInternalServerPath: string | undefined; // if we download HLS, add that bin dir to PATH
149149
try {
150-
const [serverExecutable_, projectGhc] = await findHaskellLanguageServer(context, logger, currentWorkingDir, folder);
151-
serverExecutable = serverExecutable_;
152-
await validateHLSToolchain(serverExecutable, projectGhc, currentWorkingDir, logger);
150+
serverExecutable = await findHaskellLanguageServer(context, logger, currentWorkingDir, folder);
153151
addInternalServerPath = path.dirname(serverExecutable);
154152
if (!serverExecutable) {
155153
return;

src/hlsBinaries.ts

Lines changed: 73 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class MissingToolError extends Error {
7373
* @param callback Upon process termination, execute this callback. If given, must resolve promise.
7474
* @returns Stdout of the process invocation, trimmed off newlines, or whatever the `callback` resolved to.
7575
*/
76+
7677
async function callAsync(
7778
binary: string,
7879
args: string[],
@@ -85,7 +86,7 @@ async function callAsync(
8586
error: ExecException | null,
8687
stdout: string,
8788
stderr: string,
88-
resolve: (value: string | PromiseLike<string>) => void,
89+
resolve: (value: string | PromiseLike<string> ) => void,
8990
reject: (reason?: any) => void
9091
) => void
9192
): Promise<string> {
@@ -191,7 +192,7 @@ export async function findHaskellLanguageServer(
191192
logger: Logger,
192193
workingDir: string,
193194
folder?: WorkspaceFolder
194-
): Promise<[string, string]> {
195+
): Promise<string> {
195196
// we manage HLS, make sure ghcup is installed/available
196197
await getGHCup(context, logger);
197198

@@ -205,61 +206,87 @@ export async function findHaskellLanguageServer(
205206
}
206207

207208
const manageHLS = workspace.getConfiguration('haskell').get('manageHLS') as boolean;
208-
const wrapper = findHLSinPATH(context, logger, folder);
209-
const [installableHls, projectGhc] = await getLatestSuitableHLS(
210-
context,
211-
logger,
212-
workingDir,
213-
(wrapper === null) ? undefined : wrapper
214-
);
215209

216210
if (!manageHLS) {
211+
const wrapper = findHLSinPATH(context, logger, folder);
217212
if (!wrapper) {
218213
const msg = 'Could not find a HLS binary! Consider installing HLS via ghcup or set "haskell.manageHLS" to true';
219214
window.showErrorMessage(msg);
220215
throw new Error(msg);
221216
} else {
222-
return [wrapper, projectGhc];
217+
return wrapper;
223218
}
224-
}
225-
226-
const symHLSPath = path.join(storagePath, 'hls', installableHls);
227-
228-
// check if the found existing wrapper is suitable
229-
if (wrapper !== undefined && wrapper !== null) {
230-
// version of active hls wrapper
231-
const setVersion = await callAsync(wrapper, ['--numeric-version'], storagePath, logger);
232-
233-
// is the currently set hls wrapper matching the required version?
234-
if (comparePVP(setVersion, installableHls) !== 0) {
235-
return [wrapper, projectGhc];
219+
} else {
220+
// permissively check if we have HLS installed
221+
// this is just to avoid a popup
222+
let wrapper = await callGHCup(context, logger,
223+
['whereis', 'hls'],
224+
undefined,
225+
false,
226+
(err, stdout, _stderr, resolve, _reject) => { err ? resolve('') : resolve(stdout?.trim()) }
227+
);
228+
if (wrapper === '') {
229+
// install recommended HLS... even if this doesn't support the project GHC, because
230+
// we need a HLS to find the correct project GHC in the first place
231+
await callGHCup(context, logger,
232+
['install', 'hls'],
233+
'Installing latest HLS',
234+
true
235+
);
236+
// get path to just installed HLS
237+
wrapper = await callGHCup(context, logger,
238+
['whereis', 'hls'],
239+
undefined,
240+
false
241+
);
242+
}
243+
// now figure out the project GHC version and the latest supported HLS version
244+
// we need for it (e.g. this might in fact be a downgrade for old GHCs)
245+
const installableHls = await getLatestSuitableHLS(
246+
context,
247+
logger,
248+
workingDir,
249+
(wrapper === null) ? undefined : wrapper
250+
);
251+
252+
// now install said version in an isolated symlink directory
253+
const symHLSPath = path.join(storagePath, 'hls', installableHls);
254+
wrapper = path.join(symHLSPath, `haskell-language-server-wrapper${exeExt}`);
255+
// Check if we have a working symlink, so we can avoid another popup
256+
if (!fs.existsSync(wrapper)) {
257+
await callGHCup(context, logger,
258+
['run', '--hls', installableHls, '-b', symHLSPath, '-i'],
259+
`Installing HLS ${installableHls}`,
260+
true
261+
);
236262
}
263+
return wrapper;
237264
}
238-
239-
await callGHCup(context, logger,
240-
['run', '--hls', installableHls, '-b', symHLSPath, '-i'],
241-
`Installing HLS ${installableHls}`,
242-
true
243-
);
244-
return [path.join(symHLSPath, `haskell-language-server-wrapper${exeExt}`), projectGhc];
245265
}
246266

247267
async function callGHCup(
248268
context: ExtensionContext,
249269
logger: Logger,
250270
args: string[],
251271
title?: string,
252-
cancellable?: boolean
272+
cancellable?: boolean,
273+
callback?: (
274+
error: ExecException | null,
275+
stdout: string,
276+
stderr: string,
277+
resolve: (value: string | PromiseLike<string>) => void,
278+
reject: (reason?: any) => void
279+
) => void
253280
): Promise<string> {
254281

255282
const storagePath: string = await getStoragePath(context);
256283
const ghcup = (systemGHCup === true) ? `ghcup${exeExt}` : path.join(storagePath, `ghcup${exeExt}`);
257284
if (systemGHCup) {
258-
return await callAsync('ghcup', ['--no-verbose'].concat(args), storagePath, logger, title, cancellable);
285+
return await callAsync('ghcup', ['--no-verbose'].concat(args), storagePath, logger, title, cancellable, undefined, callback);
259286
} else {
260287
return await callAsync(ghcup, ['--no-verbose'].concat(args), storagePath, logger, title, cancellable, {
261288
GHCUP_INSTALL_BASE_PREFIX: storagePath,
262-
});
289+
}, callback);
263290
}
264291
}
265292

@@ -268,7 +295,7 @@ async function getLatestSuitableHLS(
268295
logger: Logger,
269296
workingDir: string,
270297
wrapper?: string
271-
): Promise<[string, string]> {
298+
): Promise<string> {
272299
const storagePath: string = await getStoragePath(context);
273300

274301
// get latest hls version
@@ -282,7 +309,6 @@ async function getLatestSuitableHLS(
282309
const latestHlsVersion = hlsVersions.split(/\r?\n/).pop()!.split(' ')[1];
283310

284311
// get project GHC version
285-
// TODO: we may run this function twice on startup (e.g. in extension.ts)
286312
const projectGhc =
287313
wrapper === undefined
288314
? await callAsync(`ghc${exeExt}`, ['--numeric-version'], storagePath, logger, undefined, false)
@@ -293,25 +319,9 @@ async function getLatestSuitableHLS(
293319
projectGhc !== null ? await getLatestHLSforGHC(context, storagePath, projectGhc, logger) : null;
294320
const installableHls = latestMetadataHls !== null ? latestMetadataHls : latestHlsVersion;
295321

296-
return [installableHls, projectGhc];
322+
return installableHls;
297323
}
298324

299-
// also serves as sanity check
300-
export async function validateHLSToolchain(
301-
wrapper: string,
302-
ghc: string,
303-
workingDir: string,
304-
logger: Logger
305-
): Promise<void> {
306-
const wrapperDir = path.dirname(wrapper);
307-
const hlsExe = path.join(wrapperDir, `haskell-language-server-${ghc}${exeExt}`);
308-
const hlsVer = await callAsync(wrapper, ['--numeric-version'], workingDir, logger);
309-
if (!executableExists(hlsExe)) {
310-
const msg = `Couldn't find ${hlsExe}. Your project ghc version ${ghc} may not be supported! Consider building HLS from source, e.g.: ghcup compile hls --jobs 8 --ghc ${ghc} ${hlsVer}`;
311-
window.showErrorMessage(msg);
312-
throw new Error(msg);
313-
}
314-
}
315325

316326
/**
317327
* Obtain the project ghc version from the HLS - Wrapper.
@@ -361,30 +371,30 @@ export async function getProjectGHCVersion(
361371
*/
362372
export async function getGHCup(context: ExtensionContext, logger: Logger): Promise<string | undefined> {
363373
logger.info('Checking for ghcup installation');
364-
const localGHCup = ['ghcup'].find(executableExists);
374+
const localGHCup = ['ghcup'].find(executableExists);
365375

366376
if (systemGHCup === null) {
367-
if (localGHCup !== undefined) {
377+
if (localGHCup !== undefined) {
368378
const promptMessage =
369379
'Detected system ghcup. Do you want VSCode to use it instead of an internal ghcup?';
370380

371381
systemGHCup = await window.showInformationMessage(promptMessage, 'Yes', 'No').then(b => b === 'Yes');
372-
logger.info(`set useSystemGHCup to ${systemGHCup}`);
382+
logger.info(`set useSystemGHCup to ${systemGHCup}`);
373383

374-
} else { // no local ghcup, disable
375-
systemGHCup = false;
376-
}
377-
}
378-
// set config globally
379-
workspace.getConfiguration('haskell').update('useSystemGHCup', systemGHCup, ConfigurationTarget.Global);
384+
} else { // no local ghcup, disable
385+
systemGHCup = false;
386+
}
387+
}
388+
// set config globally
389+
workspace.getConfiguration('haskell').update('useSystemGHCup', systemGHCup, ConfigurationTarget.Global);
380390

381391
if (systemGHCup === true) {
382-
if (localGHCup === undefined) {
392+
if (localGHCup === undefined) {
383393
const msg = 'Could not find a system ghcup installation, please follow instructions at https://www.haskell.org/ghcup/';
384394
window.showErrorMessage(msg);
385395
throw new Error(msg);
386-
}
387-
logger.info(`found system ghcup at ${localGHCup}`);
396+
}
397+
logger.info(`found system ghcup at ${localGHCup}`);
388398
return localGHCup;
389399
}
390400

test/suite/extension.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ suite('Extension Test Suite', () => {
114114
, joinUri(getWorkspaceRoot().uri, 'bin', process.platform === 'win32' ? 'ghcup' : '.ghcup', 'cache')
115115
]
116116
);
117+
await getHaskellConfig().update('useSystemGHCup', false);
117118
await getHaskellConfig().update('logFile', 'hls.log');
118119
await getHaskellConfig().update('trace.server', 'messages');
119120
await getHaskellConfig().update('releasesDownloadStoragePath', path.normalize(getWorkspaceFile('bin').fsPath));

0 commit comments

Comments
 (0)