-
-
Notifications
You must be signed in to change notification settings - Fork 368
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
Catch exceptions in commands and use lsp null #3696
Changes from 3 commits
a774ba9
d511957
0425469
28f2e6c
e6f408c
bcd8538
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 |
---|---|---|
|
@@ -51,15 +51,18 @@ import UnliftIO.Exception (catchAny) | |
-- | ||
|
||
data Log | ||
= LogPluginError PluginId ResponseError | ||
= LogPluginError PluginId ResponseError | ||
| LogNoPluginForMethod (Some SMethod) | ||
| LogInvalidCommandIdentifier | ||
| ExceptionInPlugin PluginId (Some SMethod) SomeException | ||
instance Pretty Log where | ||
pretty = \case | ||
LogPluginError (PluginId pId) err -> pretty pId <> ":" <+> prettyResponseError err | ||
LogNoPluginForMethod (Some method) -> | ||
"No plugin enabled for " <> pretty (show method) | ||
LogInvalidCommandIdentifier-> "Invalid command identifier" | ||
ExceptionInPlugin plId (Some method) exception -> | ||
"Exception in plugin " <> viaShow plId <> " while processing "<> viaShow method <> ": " <> viaShow exception | ||
|
||
instance Show Log where show = renderString . layoutCompact . pretty | ||
|
||
|
@@ -92,13 +95,24 @@ failedToParseArgs (CommandId com) (PluginId pid) err arg = | |
"Error while parsing args for " <> com <> " in plugin " <> pid <> ": " | ||
<> T.pack err <> ", arg = " <> T.pack (show arg) | ||
|
||
exceptionInPlugin :: PluginId -> SMethod m -> SomeException -> Text | ||
exceptionInPlugin plId method exception = | ||
"Exception in plugin " <> T.pack (show plId) <> " while processing "<> T.pack (show method) <> ": " <> T.pack (show exception) | ||
|
||
-- | Build a ResponseError and log it before returning to the caller | ||
logAndReturnError :: Recorder (WithPriority Log) -> PluginId -> (LSPErrorCodes |? ErrorCodes) -> Text -> LSP.LspT Config IO (Either ResponseError a) | ||
logAndReturnError recorder p errCode msg = do | ||
let err = ResponseError errCode msg Nothing | ||
logWith recorder Warning $ LogPluginError p err | ||
pure $ Left err | ||
|
||
-- | Logs the provider error before returning it to the caller | ||
logAndReturnError' :: Recorder (WithPriority Log) -> (LSPErrorCodes |? ErrorCodes) -> Log -> LSP.LspT Config IO (Either ResponseError a) | ||
logAndReturnError' recorder errCode msg = do | ||
let err = ResponseError errCode (fromString $ show msg) Nothing | ||
logWith recorder Warning $ msg | ||
pure $ Left err | ||
|
||
-- | Map a set of plugins to the underlying ghcide engine. | ||
asGhcIdePlugin :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Plugin Config | ||
asGhcIdePlugin recorder (IdePlugins ls) = | ||
|
@@ -177,9 +191,9 @@ executeCommandHandlers recorder ecs = requestHandler SMethod_WorkspaceExecuteCom | |
-- If we have a command, continue to execute it | ||
Just (Command _ innerCmdId innerArgs) | ||
-> execCmd ide (ExecuteCommandParams Nothing innerCmdId innerArgs) | ||
Nothing -> return $ Right $ InL A.Null | ||
Nothing -> return $ Right $ InR Null | ||
|
||
A.Error _str -> return $ Right $ InL A.Null | ||
A.Error _str -> return $ Right $ InR Null | ||
|
||
-- Just an ordinary HIE command | ||
Just (plugin, cmd) -> runPluginCommand ide plugin cmd cmdParams | ||
|
@@ -197,7 +211,9 @@ executeCommandHandlers recorder ecs = requestHandler SMethod_WorkspaceExecuteCom | |
Nothing -> logAndReturnError recorder p (InR ErrorCodes_InvalidRequest) (commandDoesntExist com p xs) | ||
Just (PluginCommand _ _ f) -> case A.fromJSON arg of | ||
A.Error err -> logAndReturnError recorder p (InR ErrorCodes_InvalidParams) (failedToParseArgs com p err arg) | ||
A.Success a -> fmap InL <$> f ide a | ||
A.Success a -> | ||
f ide a `catchAny` | ||
(\e -> logAndReturnError' recorder (InR ErrorCodes_InternalError) (ExceptionInPlugin p (Some SMethod_WorkspaceApplyEdit) e)) | ||
fendor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
-- --------------------------------------------------------------------- | ||
|
||
|
@@ -225,9 +241,8 @@ extensiblePlugins recorder xs = mempty { P.pluginHandlers = handlers } | |
msg = pluginNotEnabled m fs' | ||
return $ Left err | ||
Just fs -> do | ||
let msg e pid = "Exception in plugin " <> T.pack (show pid) <> " while processing " <> T.pack (show m) <> ": " <> T.pack (show e) | ||
handlers = fmap (\(plid,_,handler) -> (plid,handler)) fs | ||
es <- runConcurrently msg (show m) handlers ide params | ||
let handlers = fmap (\(plid,_,handler) -> (plid,handler)) fs | ||
es <- runConcurrently exceptionInPlugin m handlers ide params | ||
|
||
let (errs,succs) = partitionEithers $ toList $ join $ NE.zipWith (\(pId,_) -> fmap (first (pId,))) handlers es | ||
unless (null errs) $ forM_ errs $ \(pId, err) -> | ||
|
@@ -261,22 +276,25 @@ extensibleNotificationPlugins recorder xs = mempty { P.pluginHandlers = handlers | |
Just fs -> do | ||
-- We run the notifications in order, so the core ghcide provider | ||
-- (which restarts the shake process) hopefully comes last | ||
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. 🙈 |
||
mapM_ (\(pid,_,f) -> otTracedProvider pid (fromString $ show m) $ f ide vfs params) fs | ||
mapM_ (\(pid,_,f) -> otTracedProvider pid (fromString $ show m) $ f ide vfs params | ||
`catchAny` | ||
(\e -> logWith recorder Warning (ExceptionInPlugin pid (Some m) e))) fs | ||
fendor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
-- --------------------------------------------------------------------- | ||
|
||
runConcurrently | ||
:: MonadUnliftIO m | ||
=> (SomeException -> PluginId -> T.Text) | ||
-> String -- ^ label | ||
=> (PluginId -> SMethod method -> SomeException -> T.Text) | ||
-> SMethod method -- ^ label | ||
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. wrong comment |
||
-> NonEmpty (PluginId, a -> b -> m (NonEmpty (Either ResponseError d))) | ||
-- ^ Enabled plugin actions that we are allowed to run | ||
-> a | ||
-> b | ||
-> m (NonEmpty(NonEmpty (Either ResponseError d))) | ||
runConcurrently msg method fs a b = forConcurrently fs $ \(pid,f) -> otTracedProvider pid (fromString method) $ do | ||
runConcurrently msg method fs a b = forConcurrently fs $ \(pid,f) -> otTracedProvider pid (fromString (show method)) $ do | ||
f a b | ||
`catchAny` (\e -> pure $ pure $ Left $ ResponseError (InR ErrorCodes_InternalError) (msg e pid) Nothing) | ||
`catchAny` (\e -> pure $ pure $ Left $ ResponseError (InR ErrorCodes_InternalError) (msg pid method e) Nothing) | ||
|
||
combineErrors :: [ResponseError] -> ResponseError | ||
combineErrors [x] = x | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -873,7 +873,7 @@ data PluginCommand ideState = forall a. (FromJSON a) => | |
type CommandFunction ideState a | ||
= ideState | ||
-> a | ||
-> LspM Config (Either ResponseError Value) | ||
-> LspM Config (Either ResponseError (Value |? Null)) | ||
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. tbh, since they're represented the same, I'm not sure this really adds much except making things more complex for the users 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. I would say less complex. LSP Types are used everywhere, and now command users don't need to use Aeson if they don't use it anywhere else (Pretty much every command returns null). 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. It doesn't matter too much, but I would hope that we can fix this upstream, and then we'd have to go back again... 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. Good point. Is there any reason why we can't use Aeson.Null for Null in lsp-types, perhaps by reexporting it instead of declaring our own? that would remove a source of name conflicts. 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. Okay right, because it's only a constructor, not a type in its own right. |
||
|
||
-- --------------------------------------------------------------------- | ||
|
||
|
@@ -1093,7 +1093,7 @@ mkCodeActionWithResolveAndCommand plId codeActionMethod codeResolveMethod = | |
case resolveResult of | ||
Right CodeAction {_edit = Just wedits } -> do | ||
_ <- sendRequest SMethod_WorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing wedits) (\_ -> pure ()) | ||
pure $ Right Data.Aeson.Null | ||
pure $ Right $ InR Null | ||
Right _ -> pure $ Left $ responseError "No edit in CodeAction" | ||
Left err -> pure $ Left err | ||
|
||
|
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 is fine but I it's literally the same behaviour. The upstream spec is weird, I made an issue: microsoft/language-server-protocol#1766