{-# LANGUAGE RecordWildCards #-} -- Calls to the hsinspect binary must have some context, which typically must be -- discovered from the file that the user is currently visiting. -- -- This module gathers the definition of the context and the logic to infer it, -- which assumes that .cabal (or package.yaml) and .ghc.flags files are present, -- and that the build tool is either cabal-install or stack. module HsInspect.LSP.Context where import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Except (ExceptT(..), throwE) import Data.List (isSuffixOf) import HsInspect.LSP.Util import System.Directory (findExecutablesInDirectories) import System.FilePath -- TODO replace String with Text data Context = Context { hsinspect :: FilePath , package_dir :: FilePath , ghcflags :: [String] , ghcpath :: String , srcdir :: FilePath } findContext :: FilePath -> ExceptT String IO Context findContext src = do ghcflags' <- discoverGhcflags src ghcpath' <- discoverGhcpath src let readWords file = words <$> readFile' file readFile' = liftIO . readFile ghcpath <- readFile' ghcpath' Context <$> discoverHsInspect ghcpath <*> discoverPackageDir src <*> readWords ghcflags' <*> pure ghcpath <*> pure (takeDirectory ghcflags') discoverHsInspect :: String -> ExceptT String IO FilePath discoverHsInspect path = do let dirs = splitSearchPath path found <- liftIO $ findExecutablesInDirectories dirs "hsinspect" case found of [] -> throwE help_hsinspect exe : _ -> pure exe -- c.f. haskell-tng--compile-dominating-package discoverPackageDir :: FilePath -> ExceptT String IO FilePath discoverPackageDir file = do let dir = takeDirectory file isCabal = (".cabal" `isSuffixOf`) isHpack = ("package.yaml" ==) failWithM "There must be a .cabal or package.yaml" $ locateDominatingDir (\f -> isCabal f || isHpack f) dir discoverGhcflags :: FilePath -> ExceptT String IO FilePath discoverGhcflags file = do let dir = takeDirectory file failWithM ("There must be a .ghc.flags file. " ++ help_ghcflags) $ locateDominatingFile (".ghc.flags" ==) dir discoverGhcpath :: FilePath -> ExceptT String IO FilePath discoverGhcpath file = do let dir = takeDirectory file failWithM ("There must be a .ghc.path file. " ++ help_ghcflags) $ locateDominatingFile (".ghc.path" ==) dir -- note that any formatting in these messages are stripped help_ghcflags :: String help_ghcflags = "The cause of this error could be that this package has not been compiled yet, \ \or the ghcflags compiler plugin has not been installed for this package. \ \See https://gitlab.com/tseenshe/hsinspect#installation for more details." help_hsinspect :: String help_hsinspect = "The hsinspect binary has not been installed for this package. \ \See https://gitlab.com/tseenshe/hsinspect#installation for more details." -- from Control.Error.Util failWithM :: Applicative m => e -> m (Maybe a) -> ExceptT e m a failWithM e ma = ExceptT $ (maybe (Left e) Right) <$> ma