Skip to content

Commit

Permalink
Merge pull request #605 from hasufell/cleanups
Browse files Browse the repository at this point in the history
Cleanups
  • Loading branch information
fendor committed Apr 30, 2022
2 parents 902204c + 4398241 commit 1a2b356
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
fail-fast: false
matrix:
os: [macos-11, ubuntu-latest, windows-latest]
ghc: [9.0.1, 8.10.4]
ghc: [9.0.2, 8.10.4]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ export class NoMatchingHls extends Error {
super(`HLS does not support GHC ${ghcProjVersion} yet.`);
}
public docLink(): Uri {
return Uri.parse('https://haskell-language-server.readthedocs.io/en/latest/supported-versions.html');
return Uri.parse('https://haskell-language-server.readthedocs.io/en/latest/supported-versions.html');
}
}
2 changes: 1 addition & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold

let serverEnvironment: IEnvVars = await workspace.getConfiguration('haskell', uri).serverEnvironment;
if (addInternalServerPath !== undefined) {
const newPath = await addPathToProcessPath(addInternalServerPath, logger);
const newPath = await addPathToProcessPath(addInternalServerPath);
serverEnvironment = {
...serverEnvironment,
...{ PATH: newPath },
Expand Down
111 changes: 59 additions & 52 deletions src/hlsBinaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { stat } from 'fs/promises';
import * as https from 'https';
import * as path from 'path';
import { match } from 'ts-pattern';
import * as url from 'url';
import { promisify } from 'util';
import { ConfigurationTarget, ExtensionContext, ProgressLocation, window, workspace, WorkspaceFolder } from 'vscode';
import { Logger } from 'vscode-languageclient';
Expand Down Expand Up @@ -68,7 +67,7 @@ async function callAsync(
envAdd?: IEnvVars,
callback?: ProcessCallback
): Promise<string> {
let newEnv: IEnvVars = await resolveServerEnvironmentPATH(
let newEnv: IEnvVars = resolveServerEnvironmentPATH(
workspace.getConfiguration('haskell').get('serverEnvironment') || {}
);
newEnv = { ...(process.env as IEnvVars), ...newEnv, ...(envAdd || {}) };
Expand Down Expand Up @@ -135,15 +134,14 @@ async function callAsync(
/** Gets serverExecutablePath and fails if it's not set.
*/
async function findServerExecutable(
context: ExtensionContext,
logger: Logger,
folder?: WorkspaceFolder
): Promise<string> {
let exePath = workspace.getConfiguration('haskell').get('serverExecutablePath') as string;
logger.info(`Trying to find the server executable in: ${exePath}`);
exePath = resolvePathPlaceHolders(exePath, folder);
logger.log(`Location after path variables substitution: ${exePath}`);
if (await executableExists(exePath)) {
if (executableExists(exePath)) {
return exePath;
} else {
const msg = `Could not find a HLS binary at ${exePath}! Consider installing HLS via ghcup or change "haskell.manageHLS" in your settings.`;
Expand All @@ -153,13 +151,13 @@ async function findServerExecutable(

/** Searches the PATH. Fails if nothing is found.
*/
async function findHLSinPATH(context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): Promise<string> {
async function findHLSinPATH(_context: ExtensionContext, logger: Logger): Promise<string> {
// try PATH
const exes: string[] = ['haskell-language-server-wrapper', 'haskell-language-server'];
logger.info(`Searching for server executables ${exes.join(',')} in $PATH`);
logger.info(`$PATH environment variable: ${process.env.PATH}`);
for (const exe of exes) {
if (await executableExists(exe)) {
if (executableExists(exe)) {
logger.info(`Found server executable in $PATH: ${exe}`);
return exe;
}
Expand Down Expand Up @@ -189,7 +187,7 @@ export async function findHaskellLanguageServer(
logger.info('Finding haskell-language-server');

if (workspace.getConfiguration('haskell').get('serverExecutablePath') as string) {
const exe = await findServerExecutable(context, logger, folder);
const exe = await findServerExecutable(logger, folder);
return [exe, undefined];
}

Expand Down Expand Up @@ -226,7 +224,7 @@ export async function findHaskellLanguageServer(
}

if (manageHLS === 'PATH') {
const exe = await findHLSinPATH(context, logger, folder);
const exe = await findHLSinPATH(context, logger);
return [exe, undefined];
} else {
// we manage HLS, make sure ghcup is installed/available
Expand Down Expand Up @@ -267,40 +265,42 @@ export async function findHaskellLanguageServer(
latestStack = await getLatestToolFromGHCup(context, logger, 'stack');
}
if (recGHC === undefined) {
recGHC = !(await executableExists('ghc'))
recGHC = !executableExists('ghc')
? await getLatestAvailableToolFromGHCup(context, logger, 'ghc', 'recommended')
: null;
}

// download popups
const promptBeforeDownloads = workspace.getConfiguration('haskell').get('promptBeforeDownloads') as boolean;
if (promptBeforeDownloads) {
const hlsInstalled = latestHLS
? await toolInstalled(context, logger, 'hls', latestHLS)
: undefined;
const cabalInstalled = latestCabal
? await toolInstalled(context, logger, 'cabal', latestCabal)
: undefined;
const stackInstalled = latestStack
? await toolInstalled(context, logger, 'stack', latestStack)
: undefined;
const hlsInstalled = latestHLS ? await toolInstalled(context, logger, 'hls', latestHLS) : undefined;
const cabalInstalled = latestCabal ? await toolInstalled(context, logger, 'cabal', latestCabal) : undefined;
const stackInstalled = latestStack ? await toolInstalled(context, logger, 'stack', latestStack) : undefined;
const ghcInstalled = executableExists('ghc')
? new InstalledTool('ghc', await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false))
// if recGHC is null, that means user disabled automatic handling,
: (recGHC !== null ? await toolInstalled(context, logger, 'ghc', recGHC) : undefined);
const toInstall: InstalledTool[] = [hlsInstalled, cabalInstalled, stackInstalled, ghcInstalled]
.filter((tool) => tool && !tool.installed) as InstalledTool[];
? new InstalledTool(
'ghc',
await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false)
)
: // if recGHC is null, that means user disabled automatic handling,
recGHC !== null
? await toolInstalled(context, logger, 'ghc', recGHC)
: undefined;
const toInstall: InstalledTool[] = [hlsInstalled, cabalInstalled, stackInstalled, ghcInstalled].filter(
(tool) => tool && !tool.installed
) as InstalledTool[];
if (toInstall.length > 0) {
const decision = await window.showInformationMessage(
`Need to download ${toInstall.map(t => t.nameWithVersion).join(', ')}, continue?`,
`Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
'Yes',
'No',
"Yes, don't ask again"
);
if (decision === 'Yes') {
logger.info(`User accepted download for ${toInstall.map(t => t.nameWithVersion).join(', ')}.`);
logger.info(`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')}.`);
} else if (decision === "Yes, don't ask again") {
logger.info(`User accepted download for ${toInstall.map(t => t.nameWithVersion).join(', ')} and won't be asked again.`);
logger.info(
`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')} and won't be asked again.`
);
workspace.getConfiguration('haskell').update('promptBeforeDownloads', false);
} else {
toInstall.forEach((tool) => {
Expand Down Expand Up @@ -353,26 +353,25 @@ export async function findHaskellLanguageServer(

// more download popups
if (promptBeforeDownloads) {
const hlsInstalled = projectHls
? await toolInstalled(context, logger, 'hls', projectHls)
: undefined;
const ghcInstalled = projectGhc
? await toolInstalled(context, logger, 'ghc', projectGhc)
: undefined;
const toInstall: InstalledTool[] = [hlsInstalled, ghcInstalled]
.filter((tool) => tool && !tool.installed) as InstalledTool[];
const hlsInstalled = projectHls ? await toolInstalled(context, logger, 'hls', projectHls) : undefined;
const ghcInstalled = projectGhc ? await toolInstalled(context, logger, 'ghc', projectGhc) : undefined;
const toInstall: InstalledTool[] = [hlsInstalled, ghcInstalled].filter(
(tool) => tool && !tool.installed
) as InstalledTool[];
if (toInstall.length > 0) {
const decision = await window.showInformationMessage(
`Need to download ${toInstall.map(t => t.nameWithVersion).join(', ')}, continue?`,
`Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
{ modal: true },
'Yes',
'No',
"Yes, don't ask again"
);
if (decision === 'Yes') {
logger.info(`User accepted download for ${toInstall.map(t => t.nameWithVersion).join(', ')}.`);
logger.info(`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')}.`);
} else if (decision === "Yes, don't ask again") {
logger.info(`User accepted download for ${toInstall.map(t => t.nameWithVersion).join(', ')} and won't be asked again.`);
logger.info(
`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')} and won't be asked again.`
);
workspace.getConfiguration('haskell').update('promptBeforeDownloads', false);
} else {
toInstall.forEach((tool) => {
Expand Down Expand Up @@ -400,14 +399,22 @@ export async function findHaskellLanguageServer(
...(projectGhc ? ['--ghc', projectGhc] : []),
'--install',
],
`Installing project specific toolchain: ${[['hls', projectHls], ['GHC', projectGhc], ['cabal', latestCabal], ['stack', latestStack]].filter(t => t[1]).map(t => `${t[0]}-${t[1]}`).join(', ')}`,
`Installing project specific toolchain: ${[
['hls', projectHls],
['GHC', projectGhc],
['cabal', latestCabal],
['stack', latestStack],
]
.filter((t) => t[1])
.map((t) => `${t[0]}-${t[1]}`)
.join(', ')}`,
true
);

if (projectHls) {
return [path.join(hlsBinDir, `haskell-language-server-wrapper${exeExt}`), hlsBinDir];
} else {
const exe = await findHLSinPATH(context, logger, folder);
const exe = await findHLSinPATH(context, logger);
return [exe, hlsBinDir];
}
}
Expand Down Expand Up @@ -470,8 +477,8 @@ async function getLatestProjectHLS(
const merged = new Map<string, string[]>([...metadataMap, ...ghcupMap]); // right-biased
// now sort and get the latest suitable version
const latest = [...merged]
.filter(([k, v]) => v.some((x) => x === projectGhc))
.sort(([k1, v1], [k2, v2]) => comparePVP(k1, k2))
.filter(([_k, v]) => v.some((x) => x === projectGhc))
.sort(([k1, _v1], [k2, _v2]) => comparePVP(k1, k2))
.pop();

if (!latest) {
Expand All @@ -484,7 +491,7 @@ async function getLatestProjectHLS(
/**
* Obtain the project ghc version from the HLS - Wrapper (which must be in PATH now).
* Also, serves as a sanity check.
* @param wrapper Path to the Haskell-Language-Server wrapper
* @param toolchainBindir Path to the toolchainn bin directory (added to PATH)
* @param workingDir Directory to run the process, usually the root of the workspace.
* @param logger Logger for feedback.
* @returns The GHC version, or fail with an `Error`.
Expand All @@ -499,7 +506,7 @@ export async function getProjectGHCVersion(

const args = ['--project-ghc-version'];

const newPath = await addPathToProcessPath(toolchainBindir, logger);
const newPath = await addPathToProcessPath(toolchainBindir);
const environmentNew: IEnvVars = {
PATH: newPath,
};
Expand Down Expand Up @@ -550,14 +557,14 @@ export async function upgradeGHCup(context: ExtensionContext, logger: Logger): P
}
}

export async function findGHCup(context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): Promise<string> {
export async function findGHCup(_context: ExtensionContext, logger: Logger, folder?: WorkspaceFolder): Promise<string> {
logger.info('Checking for ghcup installation');
let exePath = workspace.getConfiguration('haskell').get('ghcupExecutablePath') as string;
if (exePath) {
logger.info(`Trying to find the ghcup executable in: ${exePath}`);
exePath = resolvePathPlaceHolders(exePath, folder);
logger.log(`Location after path variables substitution: ${exePath}`);
if (await executableExists(exePath)) {
if (executableExists(exePath)) {
return exePath;
} else {
throw new Error(`Could not find a ghcup binary at ${exePath}!`);
Expand Down Expand Up @@ -684,8 +691,8 @@ async function toolInstalled(
version: string
): Promise<InstalledTool> {
const b = await callGHCup(context, logger, ['whereis', tool, version], undefined, false)
.then((x) => true)
.catch((x) => false);
.then((_x) => true)
.catch((_x) => false);
return new InstalledTool(tool, version, b);
}

Expand Down Expand Up @@ -737,7 +744,7 @@ export type ReleaseMetadata = Map<string, Map<string, Map<string, string[]>>>;
*/
async function getHLSesfromMetadata(context: ExtensionContext, logger: Logger): Promise<Map<string, string[]> | null> {
const storagePath: string = await getStoragePath(context);
const metadata = await getReleaseMetadata(context, storagePath, logger).catch((e) => null);
const metadata = await getReleaseMetadata(context, storagePath, logger).catch((_e) => null);
if (!metadata) {
window.showErrorMessage('Could not get release metadata');
return null;
Expand Down Expand Up @@ -803,23 +810,23 @@ export function findSupportedHlsPerGhc(
/**
* Download GHCUP metadata.
*
* @param context Extension context.
* @param _context Extension context.
* @param storagePath Path to put in binary files and caches.
* @param logger Logger for feedback.
* @returns Metadata of releases, or null if the cache can not be found.
*/
async function getReleaseMetadata(
context: ExtensionContext,
_context: ExtensionContext,
storagePath: string,
logger: Logger
): Promise<ReleaseMetadata | null> {
const releasesUrl = workspace.getConfiguration('haskell').releasesURL
? url.parse(workspace.getConfiguration('haskell').releasesURL)
? new URL(workspace.getConfiguration('haskell').releasesURL)
: undefined;
const opts: https.RequestOptions = releasesUrl
? {
host: releasesUrl.host,
path: releasesUrl.path,
path: releasesUrl.pathname,
}
: {
host: 'raw.githubusercontent.com',
Expand Down
11 changes: 5 additions & 6 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as http from 'http';
import * as https from 'https';
import * as os from 'os';
import { extname } from 'path';
import * as url from 'url';
import { promisify } from 'util';
import { OutputChannel, ProgressLocation, window, workspace, WorkspaceFolder } from 'vscode';
import { Logger } from 'vscode-languageclient';
Expand Down Expand Up @@ -215,10 +214,10 @@ export async function downloadFile(titleMsg: string, src: string, dest: string):
},
async (progress) => {
const p = new Promise<void>((resolve, reject) => {
const srcUrl = url.parse(src);
const srcUrl = new URL(src);
const opts: https.RequestOptions = {
host: srcUrl.host,
path: srcUrl.path,
path: srcUrl.pathname,
protocol: srcUrl.protocol,
port: srcUrl.port,
headers: userAgentHeader,
Expand All @@ -230,9 +229,9 @@ export async function downloadFile(titleMsg: string, src: string, dest: string):

// Decompress it if it's a gzip or zip
const needsGunzip =
res.headers['content-type'] === 'application/gzip' || extname(srcUrl.path ?? '') === '.gz';
res.headers['content-type'] === 'application/gzip' || extname(srcUrl.pathname ?? '') === '.gz';
const needsUnzip =
res.headers['content-type'] === 'application/zip' || extname(srcUrl.path ?? '') === '.zip';
res.headers['content-type'] === 'application/zip' || extname(srcUrl.pathname ?? '') === '.zip';
if (needsGunzip) {
const gunzip = createGunzip();
gunzip.on('error', reject);
Expand Down Expand Up @@ -360,7 +359,7 @@ export function resolvePATHPlaceHolders(path: string) {
}

// also honours serverEnvironment.PATH
export async function addPathToProcessPath(extraPath: string, logger: Logger): Promise<string> {
export async function addPathToProcessPath(extraPath: string): Promise<string> {
const pathSep = process.platform === 'win32' ? ';' : ':';
const serverEnvironment: IEnvVars = (await workspace.getConfiguration('haskell').get('serverEnvironment')) || {};
const path: string[] = serverEnvironment.PATH
Expand Down

0 comments on commit 1a2b356

Please sign in to comment.