-
-
Notifications
You must be signed in to change notification settings - Fork 390
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
Conversation
I was unable to write tests for this... so only a POC image to show I tested it at least locally. |
Another noteable improvement: the logs are better structured now:
|
Converted to draft until the lsp change |
758dc37
to
0a5dc78
Compare
2280b6d
to
ab88d2b
Compare
Turn HLS-wrapper into a full-blown LSP server, capable of sending requests and handling them appropriately. Introduces better error handling to HLS-wrapper to show LSP clients dedicated error messages. This should help users understand why their Language Server isn't starting.
ab88d2b
to
9304319
Compare
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.
Most stuff is basically copy-pasted from ghcide's runLangaugeServer
with all shake stuff stripped out.
|
||
-- | 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 |
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.
getRuntimeGhcVersion' :: Cradle Void -> ExceptT WrapperSetupError IO String | |
getRuntimeGhcVersion' :: MonadIO m => Cradle Void -> ExceptT WrapperSetupError m String |
Maybe? Probably not worth it, though
prettyError (ToolRequirementMissing toolExe name) _ = | ||
"This is a " <> T.pack (show name) <> " Project, but we failed to find \"" <> | ||
T.pack toolExe <> "\" on the $PATH" |
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 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 comment
The 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.
" 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
FailedToObtainGhcVersion
and NoneCradleGhcVersion
look they could be identical? i.e. something like Failed to find GHC version for project: {name}
(unless name implies something else?) and then for NoneCradle
keep the same but with (NoneCradle)
or some other marker attached?
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.
They are slightly different though, we should improve the error message!
- In the case of a
none
cradle, we can not figure out the GHC version to use, thus can't decide which HLS version we want to launch. The outside world specifically instructed us not to load this project, probably via ahie.yaml
- FailedToObtainGhcVersion entails that some command has failed invocation, e.g.
cabal v2-exec -- ghc -v0 --numeric-version
, e.g. we attempted to load the project, but we couldn't.
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.
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.
outH <- Main.argsHandleOut realArguments | ||
|
||
runLanguageServer (Main.argsLspOptions realArguments) inH outH (Main.argsDefaultHlsConfig realArguments) getConfigFromNotification $ \env exitFun -> do | ||
-- Send a message to the client to tell about our problems |
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.
WIP comment, will be changed
whenJust argsCwd IO.setCurrentDirectory | ||
dir <- IO.getCurrentDirectory | ||
LSP.setupLogger argsLogFile ["hls"] |
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.
There were some earlier inconsistencies with pathing when --cwd
was supplied.
haskell-language-server/ghcide/exe/Main.hs
Lines 53 to 56 in 47cb213
-- if user uses --cwd option we need to make this path absolute (and set the current directory to it) | |
argsCwd <- case argsCwd of | |
Nothing -> IO.getCurrentDirectory | |
Just root -> IO.setCurrentDirectory root >> IO.getCurrentDirectory |
inH <- Main.argsHandleIn realArguments | ||
outH <- Main.argsHandleOut realArguments | ||
|
||
runLanguageServer (Main.argsLspOptions realArguments) inH outH (Main.argsDefaultHlsConfig realArguments) getConfigFromNotification $ \env exitFun -> do |
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.
Duplicating runLanguageServer
here is not great. This code should either:
- call the real
runLanguageServer
(you can always parameterise it even more) - fork it (as done here) and clean up.
In other words, I'd prefer if you could clean this up or just call the original
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.
calling the original is a bit tricky, as the original runLanguageServer
handles the initialisation request and sends a response which we probably don't want to... If we don't care about that (when I think about it, we probably don't have to), we can re-use runLanguageServer
with basically a startup continuation.
I think we can re-use the code, we would open a shake database, etc... but that doesn't matter too much, right?
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.
The shake database thing is just a legacy concept - we are not using Shake anymore so all it is now is a bunch of concurrent mutable hash maps.
-> (Config -> Value -> Either T.Text Config) | ||
-> (LSP.LanguageContextEnv Config -> IO () -> IO ()) | ||
-> IO () | ||
runLanguageServer options inH outH defaultConfig onConfigurationChange onRun = do |
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.
Why all these arguments?
runLanguageServer options inH outH defaultConfig onConfigurationChange onRun = do | |
runLanguageServer onRun = do |
clientMsgChan :: Chan ReactorMessage <- newChan | ||
|
||
let asyncHandlers = mconcat | ||
[ cancelHandler cancelRequest |
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.
Why handle cancellation?
I think we have to think up some way to do this without the massive duplication. I don't think we can justify all that just for some better reporting at startup :/ Another thing: how will running a whole LSP server here look to the client? Will we e.g. respond to their |
True, but I think we can reduce it.
No we do not.
Once the HLS-wrapper LSP server starts, the user has only one possible interaction: press restart. I just realise, we can't rely on that every lsp-client has the same behaviour as VSCode (e.g. try to restart the language server if it exits), which makes it a bit trickier... Afaict, the LSP server always parses the initialisation request (unless we can introduce some way to avoid that). |
I wouldn't worry too much about this. If the LSP client doesn't try to restart, we have already notified the user that something was wrong and given them a strong hint about what to do |
I had a possibly insane idea. What if:
Then we can hopefully avoid the duplication: because we'll reuse the LSP setup that we normally do. And we can probably inherit e.g. arguments for log files etc. |
That requires either:
|
True. But having a wrapper script that just calls |
Initially I really liked this idea, but then I realised that the main HLS binary is just calling So most of the duplication is already abstracted, and what's left is due to the need to inline @fendor did you consider my suggestion in #2591 (comment) yet? |
Hmm, good point. So maybe we just need to paramterize that by something to say "don't really start up" 🤔 |
... which is what you suggested already. |
Yes, I think it is a good idea to parameterise |
Actually, instead of adding a subcommand, we can simply re-use the strategy many unix cli programs use: rely on |
Closed in favour of #2960 |
Introduces better error handling to HLS-wrapper to show LSP clients
dedicated error messages.
This should help users understand why their Language Server isn't
starting.
Closes #2589