-
-
Notifications
You must be signed in to change notification settings - Fork 391
Turn HLS-wrapper into an LSP Server #2591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,22 @@ | ||
{-# LANGUAGE RecordWildCards #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE RecordWildCards #-} | ||
-- | This module is based on the hie-wrapper.sh script in | ||
-- https://github.com/alanz/vscode-hie-server | ||
module Main where | ||
|
||
import Control.Monad.Extra | ||
import Control.Monad.IO.Class | ||
import Control.Monad.Trans.Except | ||
import Data.Default | ||
import Data.Foldable | ||
import Data.List | ||
import Data.Maybe | ||
import qualified Data.Text as T | ||
import qualified Data.Text.IO as T | ||
import Data.Void | ||
import qualified Development.IDE.Session as Session | ||
import qualified HIE.Bios.Environment as HieBios | ||
import qualified Development.IDE.Session as Session | ||
import qualified HIE.Bios.Environment as HieBios | ||
import HIE.Bios.Types | ||
import Ide.Arguments | ||
import Ide.Version | ||
|
@@ -20,6 +27,7 @@ import System.FilePath | |
import System.IO | ||
import System.Info | ||
import System.Process | ||
import WrapperLspMain | ||
|
||
-- --------------------------------------------------------------------- | ||
|
||
|
@@ -46,9 +54,17 @@ main = do | |
BiosMode PrintCradleType -> | ||
print =<< findProjectCradle | ||
|
||
_ -> launchHaskellLanguageServer args | ||
_ -> launchHaskellLanguageServer args >>= \case | ||
Right () -> pure () | ||
Left err -> do | ||
T.hPutStrLn stderr "*** Startup ERROR" | ||
T.hPutStrLn stderr (prettyError err NoShorten) | ||
case args of | ||
Ghcide ghcideArguments -> lspMain ghcideArguments (prettyError err Shorten) | ||
_ -> pure () | ||
|
||
launchHaskellLanguageServer :: Arguments -> IO () | ||
|
||
launchHaskellLanguageServer :: Arguments -> IO (Either WrapperSetupError ()) | ||
launchHaskellLanguageServer parsedArgs = do | ||
case parsedArgs of | ||
Ghcide GhcideArguments{..} -> whenJust argsCwd setCurrentDirectory | ||
|
@@ -64,7 +80,10 @@ launchHaskellLanguageServer parsedArgs = do | |
|
||
case parsedArgs of | ||
Ghcide GhcideArguments{..} -> | ||
when argsProjectGhcVersion $ getRuntimeGhcVersion' cradle >>= putStrLn >> exitSuccess | ||
when argsProjectGhcVersion $ do | ||
runExceptT (getRuntimeGhcVersion' cradle) >>= \case | ||
Right ghcVersion -> putStrLn ghcVersion >> exitSuccess | ||
Left err -> T.putStrLn (prettyError err NoShorten) >> exitFailure | ||
_ -> pure () | ||
|
||
progName <- getProgName | ||
|
@@ -83,51 +102,53 @@ launchHaskellLanguageServer parsedArgs = do | |
hPutStrLn stderr "" | ||
-- Get the ghc version -- this might fail! | ||
hPutStrLn stderr "Consulting the cradle to get project GHC version..." | ||
ghcVersion <- getRuntimeGhcVersion' cradle | ||
hPutStrLn stderr $ "Project GHC version: " ++ ghcVersion | ||
|
||
let | ||
hlsBin = "haskell-language-server-" ++ ghcVersion | ||
candidates' = [hlsBin, "haskell-language-server"] | ||
candidates = map (++ exeExtension) candidates' | ||
|
||
hPutStrLn stderr $ "haskell-language-server exe candidates: " ++ show candidates | ||
|
||
mexes <- traverse findExecutable candidates | ||
|
||
case asum mexes of | ||
Nothing -> hPutStrLn stderr $ "Cannot find any haskell-language-server exe, looked for: " ++ intercalate ", " candidates | ||
Just e -> do | ||
hPutStrLn stderr $ "Launching haskell-language-server exe at:" ++ e | ||
callProcess e args | ||
|
||
-- | Version of 'getRuntimeGhcVersion' that dies if we can't get it, and also | ||
-- checks to see if the tool is missing if it is one of | ||
getRuntimeGhcVersion' :: Show a => Cradle a -> IO String | ||
runExceptT (getRuntimeGhcVersion' cradle) >>= \case | ||
Left err -> pure $ Left err | ||
Right ghcVersion -> do | ||
hPutStrLn stderr $ "Project GHC version: " ++ ghcVersion | ||
|
||
let | ||
hlsBin = "haskell-language-server-" ++ ghcVersion | ||
candidates' = [hlsBin, "haskell-language-server"] | ||
candidates = map (++ exeExtension) candidates' | ||
|
||
hPutStrLn stderr $ "haskell-language-server exe candidates: " ++ show candidates | ||
|
||
mexes <- traverse findExecutable candidates | ||
|
||
case asum mexes of | ||
Nothing -> pure $ Left $ NoLanguageServer ghcVersion candidates | ||
Just e -> do | ||
hPutStrLn stderr $ "Launching haskell-language-server exe at:" ++ e | ||
callProcess e args | ||
pure $ Right () | ||
|
||
-- | Version of 'getRuntimeGhcVersion' that throws a 'WrapperSetupError' if we | ||
-- can't get it, and also checks if run-time tool dependencies are missing. | ||
getRuntimeGhcVersion' :: Cradle Void -> ExceptT WrapperSetupError IO String | ||
getRuntimeGhcVersion' cradle = do | ||
|
||
let cradleName = actionName (cradleOptsProg cradle) | ||
-- See if the tool is installed | ||
case actionName (cradleOptsProg cradle) of | ||
case cradleName of | ||
Stack -> checkToolExists "stack" | ||
Cabal -> checkToolExists "cabal" | ||
Default -> checkToolExists "ghc" | ||
Direct -> checkToolExists "ghc" | ||
_ -> pure () | ||
|
||
ghcVersionRes <- HieBios.getRuntimeGhcVersion cradle | ||
ghcVersionRes <- liftIO $ HieBios.getRuntimeGhcVersion cradle | ||
case ghcVersionRes of | ||
CradleSuccess ver -> do | ||
return ver | ||
CradleFail error -> die $ "Failed to get project GHC version:" ++ show error | ||
CradleNone -> die "Failed get project GHC version, since we have a none cradle" | ||
CradleFail error -> throwE $ FailedToObtainGhcVersion cradleName error | ||
CradleNone -> throwE $ NoneCradleGhcVersion cradleName | ||
where | ||
checkToolExists exe = do | ||
exists <- findExecutable exe | ||
exists <- liftIO $ findExecutable exe | ||
case exists of | ||
Just _ -> pure () | ||
Nothing -> | ||
die $ "Cradle requires " ++ exe ++ " but couldn't find it" ++ "\n" | ||
++ show cradle | ||
Nothing -> throwE $ ToolRequirementMissing exe (actionName (cradleOptsProg cradle)) | ||
|
||
findProjectCradle :: IO (Cradle Void) | ||
findProjectCradle = do | ||
|
@@ -142,3 +163,35 @@ findProjectCradle = do | |
Nothing -> hPutStrLn stderr "No 'hie.yaml' found. Try to discover the project type!" | ||
|
||
Session.loadCradle def hieYaml d | ||
|
||
data WrapperSetupError | ||
= FailedToObtainGhcVersion (ActionName Void) CradleError | ||
| NoneCradleGhcVersion (ActionName Void) | ||
| NoLanguageServer String [FilePath] | ||
| ToolRequirementMissing String (ActionName Void) | ||
deriving (Show) | ||
|
||
data Shorten = Shorten | NoShorten | ||
|
||
-- | Pretty error message displayable to the future. | ||
-- Extra argument 'Shorten' can be used to shorten error message. | ||
-- Reduces usefulness, but allows us to show the error message via LSP | ||
-- as LSP doesn't allow any newlines and makes it really hard to read | ||
-- the message otherwise. | ||
prettyError :: WrapperSetupError -> Shorten -> T.Text | ||
prettyError (FailedToObtainGhcVersion name crdlError) shorten = | ||
"Failed to find the GHC version of this " <> T.pack (show name) <> " project." <> | ||
case shorten of | ||
Shorten -> | ||
"\n" <> T.pack (fromMaybe "" . listToMaybe $ cradleErrorStderr crdlError) | ||
NoShorten -> | ||
"\n" <> T.pack (intercalate "\n" (cradleErrorStderr crdlError)) | ||
prettyError (NoneCradleGhcVersion name) _ = | ||
"Failed to get the GHC version of the " <> T.pack (show name) <> | ||
" project, since we have a none cradle" | ||
prettyError (NoLanguageServer ghcVersion candidates) _ = | ||
"Failed to find a HLS version for GHC " <> T.pack ghcVersion <> | ||
"\nExecutable names we failed to find: " <> T.pack (intercalate "," candidates) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wording feels bad There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are slightly different though, we should improve the error message!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I was paying close enough attention I didn't see the extra details added to FailedToObtainGHCVersion... Either way, the message could be identical and generic. And then the extra information could be appended. The wording just seems clunky is all. |
||
prettyError (ToolRequirementMissing toolExe name) _ = | ||
"This is a " <> T.pack (show name) <> " Project, but we failed to find \"" <> | ||
T.pack toolExe <> "\" on the $PATH" | ||
Comment on lines
+195
to
+197
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this can be a shortened version, and we can dump the Search Path to stderr, too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dumping a search path would be hugely beneficial. I know I've been stuck trying to find out what Path is visible to HLS before. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe? Probably not worth it, though