Skip to content
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

Plugins #25

Closed
alanz opened this issue Jan 30, 2020 · 30 comments
Closed

Plugins #25

alanz opened this issue Jan 30, 2020 · 30 comments
Labels
component: plugins old_type: meta Planing and organizing other issues type: support User support tickets, questions, help with setup etc.

Comments

@alanz
Copy link
Collaborator

alanz commented Jan 30, 2020

We now have a proof of concept plugin setup, demonstrated in Ide.Plugin.Example.

What are the next steps?
Should the plugins currently homed in ghcide be moved here?
Should this be used only for migrating plugins from hie (formatters, hlint, etc)?

@alanz alanz added type: support User support tickets, questions, help with setup etc. old_type: meta Planing and organizing other issues labels Jan 30, 2020
@jneira
Copy link
Member

jneira commented Jan 30, 2020

Well, i think all should be handled the same way at the end but maybe we can start with hie's ones and add value faster.

@alanz
Copy link
Collaborator Author

alanz commented Jan 30, 2020

Yes, I think the formatters are probably the best place to start, and in terms of those start with Ormolu.

@EncodePanda
Copy link

I and @lukasz-golebiewski were working in Bristol on stylish-haskell plugin (which we have almost ready, I think PR will land this weekend) in HIE. Should we port it here right away?

@alanz
Copy link
Collaborator Author

alanz commented Jan 30, 2020

@EncodePanda may as well

@EncodePanda
Copy link

we will start in HIE, and if that goes through we will add support in haskell-ide

@ndmitchell
Copy link
Collaborator

The two plugins in the ghcide repo are Completions and CodeActions. I'm ambivalent about where they live in the long term, but having them in ghcide isn't causing problems, let's us iterate the ghcide Plugin API, and they have great tests where they are - so I'd probably not move them until (at the earliest) after the first release of this project.

I know @cocreature had views too.

@cocreature
Copy link
Contributor

cocreature commented Jan 31, 2020

For now, I would prefer to keep completions and code actions in ghcide. Partially because right now, we depend only on ghcide in DAML but not ide and we want to use those plugins and partially because I think it’s somewhat helpful to have a bit more testing surface in ghcide itself to see the impact on plugins caused by changes in ghcide.

That said, if this does turn out to cause issues or there are compelling arguments for moving them over, I’m not opposed to changing this at some point.

@alanz
Copy link
Collaborator Author

alanz commented Jan 31, 2020

@cocreature I tend to agree that they stay where they are for now.

I guess the question will become more relevant if the e.g. completions functionality already in hie is different / better, and we want to make that available here.

@pepeiborra
Copy link
Collaborator

pepeiborra commented Feb 16, 2020

It seems to me that the current plugin architecture is not really extensible: it cannot handle two plugins of the same type. E.g. we cannot have two code action plugins, the first plugin that produces a response wins. Fixing this will require some architectural changes in ghcide.

Has anyone thought about this yet?
How much do we care about it?

@pepeiborra
Copy link
Collaborator

I guess the question will become more relevant if the e.g. completions functionality already in hie is different / better, and we want to make that available here.

The completions functionality in ghcide was ported from hie by @serras, so that shouldn't be an issue

@alanz
Copy link
Collaborator Author

alanz commented Feb 16, 2020

How much do we care about it?

Quite a bit. The general model in hie has been to be able to specify providers, which get chained together. So multiple sources of diagnostics, code actions, etc.

The current PR (#42) for formatters shows how to select one of n where that actually makes sense.

And these are the two "styles" we should support, in my opinion.

@ndmitchell
Copy link
Collaborator

The plugins were designed to be extensible - the important point is that https://hackage.haskell.org/package/ghcide-0.1.0/docs/Development-IDE-LSP-Server.html#t:PartialHandlers gets the existing handlers as an argument, so a partial handler for completions can both answer and get someone else to answer in the same call. It's certainly not been used that way to date, so may not work in practice, but that was the design...

@pepeiborra
Copy link
Collaborator

pepeiborra commented Feb 16, 2020

The plugins were designed to be extensible - the important point is that https://hackage.haskell.org/package/ghcide-0.1.0/docs/Development-IDE-LSP-Server.html#t:PartialHandlers gets the existing handlers as an argument, so a partial handler for completions can both answer and get someone else to answer in the same call. It's certainly not been used that way to date, so may not work in practice, but that was the design...

Right, the problem is that Handlers are not composable. Composing handlers 'A' and 'B' using their Semigroup instance yields two independent responses where we would want a single, composed response, and there is no other way of composing handlers as far as I can see.

@ndmitchell
Copy link
Collaborator

The idea was NOT to compose them using their Semigroup instance - there's a reason PartialHandler is not opaque. There's the semigroup composition (overriding) and the by-hand call on to the other handlers and do your own stuff to put them back together.

@alanz
Copy link
Collaborator Author

alanz commented Feb 16, 2020

I think it would be a good idea to put together a toy example chaining two handlers, for say code actions.

@pepeiborra
Copy link
Collaborator

pepeiborra commented Feb 16, 2020

All right, let me try to explain.

The current definition of codeActionPlugin in ghcide throws away the existing codeActionHandler:

codeActionPlugin :: (LSP.LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
codeActionPlugin f = Plugin mempty $ PartialHandlers $ \WithMessage{..} x -> return x{
    LSP.codeActionHandler = withResponse RspCodeAction g
    }
    where
      g lsp state (CodeActionParams a b c _) = fmap List <$> f lsp state a b c

I thought that this was an oversight, easily fixed by composing with the existing handler using the Semigroup instance:

codeActionPlugin f = Plugin mempty $ PartialHandlers $ \WithMessage{..} x -> return x{
    LSP.codeActionHandler = withResponse RspCodeAction g `composeHandler` LSP.codeActionHandler x
    }
    where
      g lsp state (CodeActionParams a b c _) = fmap List <$> f lsp state a b c

composeHandler :: Handler b -> Handler b -> Handler b
composeHandler = (<>)

I think that's what Neil is suggesting too, unfortunately it doesn't work because Handler itself is not composable.
The type of Handler is:

type Handler b = b -> IO ()

-- pseudocode to show what (<>) means
instance Semigroup (Handler b) where
  h1 <> h2 = \b -> h1 b >> h2 b

Code action handlers are usually defined using withResponse, which is implemented in the ghcide LanguageServer module by (indirectly) sending an immediate response to the lsp client. So the code above ends up sending potentially two responses, and it's up to the client to decide what to do with them. VSCode seems to ignore the second one.

@ndmitchell
Copy link
Collaborator

Ah yeah, that's a design that works for the first few type synonyms, but not all the way down. This part is layered on top of Ghcide, so changing it shouldn't be a big deal, once we can figure out the right API.

@serras
Copy link
Contributor

serras commented Feb 16, 2020

The completions functionality in ghcide was ported from hie by @serras, so that shouldn't be an issue

There were some additions, though, in how documentation is obtained and shown.

@jneira
Copy link
Member

jneira commented Feb 16, 2020

In that design maybe it would be nice that plugins could transform and augment the result of previous ones, although the order of plugin application will matter.

@alanz
Copy link
Collaborator Author

alanz commented Feb 17, 2020

@ndmitchell @pepeiborra I am going to give this a shot, with some example plugins in hls.

Then we can bikeshed the final API

@alanz
Copy link
Collaborator Author

alanz commented Feb 17, 2020

Right, the problem is that Handlers are not composable. Composing handlers 'A' and 'B' using their Semigroup instance yields two independent responses where we would want a single, composed response, and there is no other way of composing handlers as far as I can see.

The way we do it in hie is to effectively have a Semigroup operation on the results of each handler, and we only send the response once, at the end, when all the relevant handlers have run.

How likely are we to want to actually override a "lower" instance of a handler, given we can assemble the handler stack we want in the Main.hs?

@alanz
Copy link
Collaborator Author

alanz commented Feb 17, 2020

Because asking each plugin to be "well-behaved" and to pass on the results from the prior one in the chain seems like asking for trouble.

@alanz
Copy link
Collaborator Author

alanz commented Feb 17, 2020

In hie we have

data PluginDescriptor =
  PluginDescriptor { pluginId                 :: PluginId
                   , pluginName               :: T.Text
                   , pluginDesc               :: T.Text
                   , pluginCommands           :: [PluginCommand]
                   , pluginCodeActionProvider :: Maybe CodeActionProvider
                   , pluginDiagnosticProvider :: Maybe DiagnosticProvider
                   , pluginHoverProvider      :: Maybe HoverProvider
                   , pluginSymbolProvider     :: Maybe SymbolProvider
                   , pluginFormattingProvider :: Maybe FormattingProvider
                   } deriving (Generic)

This allows the LSP Server loop to deal with each of those categories in the way that makes sense. The pluginName and pluginDesc are for some mythical future when a UI allows you to decide which plugins to enable. They probably do not belong.

I am going to try to layer something like this on top of what already exists in ghcide, and see where it takes me.

@alanz
Copy link
Collaborator Author

alanz commented Feb 17, 2020

FYI, the germ of this just landed, via

formatterPlugins [("ormolu", Ormolu.provider)
,("floskell", Floskell.provider)] <>

and

https://github.com/haskell/haskell-language-server/blob/master/src/Ide/Plugin/Formatter.hs

alanz added a commit to alanz/haskell-language-server that referenced this issue Feb 17, 2020
@alanz alanz pinned this issue Feb 18, 2020
@pepeiborra
Copy link
Collaborator

pepeiborra commented Feb 25, 2020

A possible solution to the problem of having 2 code actions plugins generating 2 responses is to use the lsp protocol support for partial results: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#partialResults

I'm going to prototype this approach and will revert back to this thread.

UPDATE: turns out that VSCode does not support partial results yet - microsoft/vscode-languageserver-node#528

@alanz
Copy link
Collaborator Author

alanz commented Feb 25, 2020 via email

alanz added a commit to alanz/haskell-language-server that referenced this issue Mar 15, 2020
@alanz
Copy link
Collaborator Author

alanz commented Jun 14, 2020

See also #164

@alanz alanz unpinned this issue Jun 14, 2020
@jneira
Copy link
Member

jneira commented Sep 9, 2020

@alanz maybe we could close this one?

@jneira jneira added the status: needs info Not actionable, because there's missing information label Sep 9, 2020
@alanz
Copy link
Collaborator Author

alanz commented Sep 9, 2020

@jneira I am happy to do that. Do we have docs around this? I guess @pepeiborra blog posts count as that.

@jneira
Copy link
Member

jneira commented Sep 9, 2020

We could add more specific issues with changes in the design or need of documentation

@jneira jneira closed this as completed Sep 9, 2020
@jneira jneira removed the status: needs info Not actionable, because there's missing information label Sep 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: plugins old_type: meta Planing and organizing other issues type: support User support tickets, questions, help with setup etc.
Projects
None yet
Development

No branches or pull requests

7 participants