11import * as child_process from 'child_process' ;
22import { ExecException } from 'child_process' ;
33import * as fs from 'fs' ;
4+ import { stat } from 'fs/promises' ;
45import * as https from 'https' ;
56import * as path from 'path' ;
67import { match } from 'ts-pattern' ;
@@ -242,7 +243,7 @@ export async function findHaskellLanguageServer(
242243
243244 // get a preliminary hls wrapper for finding project GHC version,
244245 // later we may install a different HLS that supports the given GHC
245- let wrapper = await getLatestHLSfromGHCup ( context , storagePath , logger ) . then ( e =>
246+ let wrapper = await getLatestWrapperFromGHCup ( context , logger ) . then ( e =>
246247 ( e === null )
247248 ? callGHCup ( context , logger ,
248249 [ 'install' , 'hls' ] ,
@@ -255,7 +256,7 @@ export async function findHaskellLanguageServer(
255256 false ,
256257 ( err , stdout , _stderr , resolve , _reject ) => { err ? resolve ( '' ) : resolve ( stdout ?. trim ( ) ) ; } )
257258 )
258- : e [ 1 ]
259+ : e
259260 ) ;
260261
261262 // now figure out the project GHC version and the latest supported HLS version
@@ -327,31 +328,24 @@ async function getLatestHLS(
327328 wrapper === undefined
328329 ? await callAsync ( `ghc${ exeExt } ` , [ '--numeric-version' ] , storagePath , logger , undefined , false )
329330 : await getProjectGHCVersion ( wrapper , workingDir , logger ) ;
330-
331- // get installable HLS that supports the project GHC version (this might not be the most recent)
332- const latestMetadataHls = await getLatestHLSfromMetadata ( context , storagePath , projectGhc , logger ) ;
333- const latestGhcupHls = await getLatestHLSfromGHCup ( context , storagePath , logger , projectGhc ) . then ( e => e === null ? null : e [ 0 ] ) ;
334-
335- if ( latestMetadataHls !== null && latestGhcupHls !== null ) {
336- // both returned a result, compare versions
337- if ( comparePVP ( latestMetadataHls , latestGhcupHls ) >= 0 ) {
338- logger . info ( "Picking HLS according to metadata" ) ;
339- return latestMetadataHls ;
340- } else {
341- logger . info ( "Picking a probably self compiled HLS via ghcup" ) ;
342- return latestGhcupHls ;
343- }
344-
345- } else if ( latestMetadataHls === null && latestGhcupHls !== null ) {
346- logger . info ( "Picking a probably self compiled HLS via ghcup" ) ;
347- return latestGhcupHls ;
348- } else if ( latestMetadataHls !== null && latestGhcupHls === null ) {
349- logger . info ( "Picking HLS according to metadata" ) ;
350- return latestMetadataHls ;
351- } else {
352- const noMatchingHLS = `No HLS version was found for supporting GHC ${ projectGhc } .` ;
331+ const noMatchingHLS = `No HLS version was found for supporting GHC ${ projectGhc } .` ;
332+
333+ // first we get supported GHC versions from available HLS bindists (whether installed or not)
334+ const metadataMap = await getHLSesfromMetadata ( context , storagePath , logger ) || new Map < string , string [ ] > ( ) ;
335+ // then we get supported GHC versions from currently installed HLS versions
336+ const ghcupMap = await getHLSesFromGHCup ( context , storagePath , logger ) || new Map < string , string [ ] > ( ) ;
337+ // since installed HLS versions may support a different set of GHC versions than the bindists
338+ // (e.g. because the user ran 'ghcup compile hls'), we need to merge both maps, preferring
339+ // values from already installed HLSes
340+ const merged = new Map < string , string [ ] > ( [ ...metadataMap , ...ghcupMap ] ) ; // right-biased
341+ // now sort and get the latest suitable version
342+ const latest = [ ...merged ] . filter ( ( [ k , v ] ) => v . some ( x => x === projectGhc ) ) . sort ( ( [ k1 , v1 ] , [ k2 , v2 ] ) => comparePVP ( k1 , k2 ) ) . pop ( ) ;
343+
344+ if ( ! latest ) {
353345 window . showErrorMessage ( noMatchingHLS ) ;
354346 throw new Error ( noMatchingHLS ) ;
347+ } else {
348+ return latest [ 0 ] ;
355349 }
356350}
357351
@@ -529,44 +523,79 @@ export function addPathToProcessPath(extraPath: string): string {
529523 return PATH . join ( pathSep ) ;
530524}
531525
526+ async function getLatestWrapperFromGHCup (
527+ context : ExtensionContext ,
528+ logger : Logger
529+ ) : Promise < string | null > {
530+ const hlsVersions = await callGHCup (
531+ context ,
532+ logger ,
533+ [ 'list' , '-t' , 'hls' , '-c' , 'installed' , '-r' ] ,
534+ undefined ,
535+ false ,
536+ ) ;
537+ const installed = hlsVersions . split ( / \r ? \n / ) . pop ( ) ;
538+ if ( installed ) {
539+ const latestHlsVersion = installed . split ( ' ' ) [ 1 ]
540+
541+ let bin = await callGHCup ( context , logger ,
542+ [ 'whereis' , 'hls' , `${ latestHlsVersion } ` ] ,
543+ undefined ,
544+ false
545+ ) ;
546+ return bin ;
547+ } else {
548+ return null ;
549+ }
550+ }
551+
532552// complements getLatestHLSfromMetadata, by checking possibly locally compiled
533553// HLS in ghcup
534554// If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
535555// otherwise ensures the specified GHC is supported.
536- async function getLatestHLSfromGHCup (
556+ async function getHLSesFromGHCup (
537557 context : ExtensionContext ,
538558 storagePath : string ,
539559 logger : Logger ,
540- targetGhc ?: string
541- ) : Promise < [ string , string ] | null > {
560+ ) : Promise < Map < string , string [ ] > | null > {
542561 const hlsVersions = await callGHCup (
543562 context ,
544563 logger ,
545564 [ 'list' , '-t' , 'hls' , '-c' , 'installed' , '-r' ] ,
546565 undefined ,
547566 false ,
548567 ) ;
549- const latestHlsVersion = hlsVersions . split ( / \r ? \n / ) . pop ( ) ! . split ( ' ' ) [ 1 ] ;
550- let bindir = await callGHCup ( context , logger ,
568+
569+ const bindir = await callGHCup ( context , logger ,
551570 [ 'whereis' , 'bindir' ] ,
552571 undefined ,
553572 false
554573 ) ;
555574
556- let hlsBin = '' ;
557- if ( targetGhc ) {
558- hlsBin = path . join ( bindir , `haskell-language-server-${ targetGhc } ~${ latestHlsVersion } ${ exeExt } ` ) ;
559- } else {
560- hlsBin = path . join ( bindir , `haskell-language-server-wrapper-${ latestHlsVersion } ${ exeExt } ` ) ;
561- }
575+ const files = fs . readdirSync ( bindir ) . filter ( async e => {
576+ return await stat ( path . join ( bindir , e ) ) . then ( s => s . isDirectory ( ) ) . catch ( ( ) => false ) ;
577+ } ) ;
578+
579+
580+ const installed = hlsVersions . split ( / \r ? \n / ) . map ( e => e . split ( ' ' ) [ 1 ] ) ;
581+ if ( installed . length > 0 ) {
582+ const myMap = new Map < string , string [ ] > ( ) ;
583+ installed . forEach ( hls => {
584+ const ghcs = files . filter ( f => f . endsWith ( `~${ hls } ${ exeExt } ` ) && f . startsWith ( 'haskell-language-server-' ) )
585+ . map ( f => {
586+ const rmPrefix = f . substring ( 'haskell-language-server-' . length ) ;
587+ return rmPrefix . substring ( 0 , rmPrefix . length - `~${ hls } ${ exeExt } ` . length ) ;
588+ } )
589+ myMap . set ( hls , ghcs ) ;
590+ } ) ;
562591
563- if ( fs . existsSync ( hlsBin ) ) {
564- return [ latestHlsVersion , hlsBin ] ;
592+ return myMap ;
565593 } else {
566- return null ;
594+ return null ;
567595 }
568596}
569597
598+
570599/**
571600 * Given a GHC version, download at least one HLS version that can be used.
572601 * This also honours the OS architecture we are on.
@@ -577,12 +606,11 @@ async function getLatestHLSfromGHCup(
577606 * @param logger Logger for feedback
578607 * @returns
579608 */
580- async function getLatestHLSfromMetadata (
609+ async function getHLSesfromMetadata (
581610 context : ExtensionContext ,
582611 storagePath : string ,
583- targetGhc : string ,
584612 logger : Logger
585- ) : Promise < string | null > {
613+ ) : Promise < Map < string , string [ ] > | null > {
586614 const metadata = await getReleaseMetadata ( context , storagePath , logger ) ;
587615 if ( metadata === null ) {
588616 window . showErrorMessage ( 'Could not get release metadata' ) ;
@@ -609,23 +637,16 @@ async function getLatestHLSfromMetadata(
609637 return null ;
610638 }
611639
612- let curHls : string | null = null ;
613-
614640 const map : ReleaseMetadata = new Map ( Object . entries ( metadata ) ) ;
641+ const newMap = new Map < string , string [ ] > ( ) ;
615642 map . forEach ( ( value , key ) => {
616643 const value_ = new Map ( Object . entries ( value ) ) ;
617644 const archValues = new Map ( Object . entries ( value_ . get ( arch ) ) ) ;
618645 const versions : string [ ] = archValues . get ( plat ) as string [ ] ;
619- if ( versions !== undefined && versions . some ( ( el ) => el === targetGhc ) ) {
620- if ( curHls === null ) {
621- curHls = key ;
622- } else if ( comparePVP ( key , curHls ) > 0 ) {
623- curHls = key ;
624- }
625- }
646+ newMap . set ( key , versions ) ;
626647 } ) ;
627648
628- return curHls ;
649+ return newMap ;
629650}
630651
631652/**
0 commit comments