haskell-language-server
haskell-language-server copied to clipboard
Allow disabling the formattingProvider
Is your enhancement request related to a problem? Please describe.
Disabling the formattingProvider
configuration does not seem possible. There is a handful of options but it can't be disabled.
The current implementation reacts formatting requests and then figures out there is plugin available. It will then result in an error
Options
Formatting provider (haskell.formattingProvider, default ormolu): what formatter to use; one of floskell, ormolu, fourmolu, stylish-haskell, or brittany (if compiled with the brittany plugin).
https://github.com/haskell/haskell-language-server/blob/aee737237c7f4542bddc7ab31a2bdd7ee0cab48d/docs/configuration.md#language-specific-server-options
Why would one want to disable the formattingProvider?
Followup of https://github.com/haskell/haskell-language-server/issues/3282
In order to configure a separate generic language server (https://github.com/iamcco/diagnostic-languageserver) to deal with formatting, Haskell language server should not "catch" the formatting requests. Instead it has to be possible to disable formatting without completely disabling the language server.
Another possibility would be - this is a bit made up - that people could have arbitrary reasons to to configure no "formattingProvider", e.g. they have configured "format on save" or they often press the format shortcut out of habit, but don't want formatting to happen temporarily for a specific or Haskell projects in general.
Describe the solution you'd like
Either ""
or "none"
should be handled in a way where the language server behaves as if the formatting handler wouldn't be implemented.
- Ideally the server capabilities wouldn't include formatting when no formatting provider is configured
- An alternative solution would be a Noop-Formatter, that requests requests in a way where they're not "swallowed" (from lsp client perspective). Downside is to add a plugin that does nothing, but would allow other code places to not care about formatting.
- Another alternative is to not fail with an error in case no plugin for the formatting request was found, and the config is set to "none".
Maybe it is as simple as this...
when (formattingProvider config == "none") $
pure $ Left $ ResponseError MethodNotFound "No formattingProvider configured" Nothing
Additional context
There is a test that defines how "none"
is handled. It succeeds for MethodNotFound
but also ""No plugin enabled for STextDocumentFormatting""
what is shown above. But this is not the wanted behavior since from the LSP client's perspective the request was handled.
https://github.com/haskell/haskell-language-server/blob/b378de2d42d1c78d0119d54e65646b475a9dc6c5/test/functional/Format.hs#L54
Update
Rejecting requests with ResponseError MethodNotFound
won't help. It will result in a different error message, but has the same effect of swallowing the format request (and second language server wouldn't handle it anymore).
Therefore a potential solution should be to not propagate the format server capability in the first place.
In order to configure a separate generic language server (https://github.com/iamcco/diagnostic-languageserver) to deal with formatting, Haskell language server should not "catch" the formatting requests.
:o what... is this? I have never seen something where people try to stitch together multiple language servers. Is that even a thing?? I'm a little unclear what the behaviour you expect is. I think it's correct for a server to respond with an error if it gets a request it can't handle.
I don't object to having a "none" option for formatting provider, though.
:o what... is this?
Slightly offtopic but diagnostic-languageserver
is quite handy: It allows you to add any linter or formatter to a generic language server implementaiton, without the hassle of installing plugins or configuring/overwriting shortcuts. Here is my config where I'm using it to format nix, dhall, shell and cabal files or run spell checks and writing recommendations.
I have never seen something where people try to stitch together multiple language servers
I'm not sure if there are many places where it would make sense to have multiple servers for a single file, but I think it's technically possible and supported by clients. In is no 1:1 mapping from filetype to server in the client configuration, but each server typically is defined with a list of file types. For example having a spell checker and linter for markdown files or commit messages, or having a more general purpose language server (e.g. yaml syntax) combined with a more specific solution (e.g. AWS CoudFormation).
I'm a little unclear what the behaviour you expect is
If I configure two servers and both can format the file, I'd say the behavior is not defined.
I think it's correct for a server to respond with an error if it gets a request it can't handle.
Correct, but ...
I don't object to having a "none" option for formatting provider, though.
There are ServerCapabilites
. I haven't yet figured out where and how they're defined in HLS.
/**
* The server provides document formatting.
*/
documentFormattingProvider?: boolean | DocumentFormattingOptions
And my assumption would be if there are two language servers defined for a file, but only one of them provides document formatting, then there is no conflict and formatting should work as expected.
Long story short
Ignoring the background, ideally if the server is configured with formattingProvder: "none"
in the config, the server capabilities would be documentFormattingProvider: false
, so the client knows there is no formatting handler in place and doesn't send requests (which would fail with an error).
There are ServerCapabilites. I haven't yet figured out where and how they're defined in HLS.
I think the lsp library automatically determines them based on whether you have a handler for the corresponding functionality. So I think it might just work out if there is no formatting handler at all.
And my assumption would be if there are two language servers defined for a file, but only one of them provides document formatting, then there is no conflict and formatting should work as expected.
This is the bit I don't understand. What is a "conflict"? I think there's some implicit model here where you can enable multiple servers, and then you expect them to advertise their capabilities very precisely (?) so that if you want to take an action you just look for the unique server that exposes that capability (?) and then send the request to that one (?). Or you send it to all of them, but you expect them to just ignore it if they don't handle it? I really don't know what the model is here, which makes it hard to know what to do...
I think the lsp library automatically determines them based on whether you have a handler for the corresponding functionality
Might be the case. But it should also be possible to explicitly define them (like purescript-language-server
):
https://github.com/nwolverson/purescript-language-server/blob/ff6aa80bd467abe387a6d67dacb1cba8082ffeb9/src/LanguageServer/Protocol/Setup.js#L47
Or you send it to all of them, but you expect them to just ignore it if they don't handle it?
My assumption would be that the client is smart enough to not send a formatting request to a server that states there is no formatting provider in it's capabilities.
But it's just guessing at this point. Here seems to be a good starting point to see if that's the case for CoC.
https://github.com/neoclide/coc.nvim/blob/44ed764db936ad831c5ee105773b0fbee68185e2/src/language-client/formatting.ts#L90
Might be the case. But it should also be possible to explicitly define the (like purescript-language-server):
I mean you certainly can, I just mean that our library setup does that.
I did some further investigation and had some progress.
Yes, in case the language server responds initialization with ServerCapabilities
and documentFormattingProvider: false
the client (tested with Coc) will know there is none configured and treat it like no language server is set up for formatting. HLS wouldn't get formatting requests.
2022-10-13T19:00:37.400 ERROR (pid:3323017) [attach] - Error: Format provider not found for buffer: 3
at Bb.documentFormat (/home/andreas/.vim/plugged/coc.nvim/build/index.js:264:20597)
Also if there is a second language server that does support formatting, formatting will work with the second LS and ignore the first.
Since I mentioned purescript-language-server
as an example that does provide explicit server capabilities, I looked up how NoFormatter
is implemented. The implementation does nothing, but also doesn't throw an error. And there is a TODO
in the code that mentions "for NoFormatter don't provide formatting provider", but it's not implemented (yet).
It might be tricky (or not possible?) to get access to the configuration before the server is initialized. On first glance it doesn't seem to be as easy as grabbing the current config and setting the value to false
in the capabilities. One might have to deal with dynamic registration of server capabilities to solve this instead.
Sorry, I think I haven't communicated it properly. HLS uses the lsp
library. That handles setting the ServerCapabilities
for us by looking at the handlers we provide. So we do not set them directly in HLS. (Might be wrong about this). On the other hand, I think that means that if we just don't provide a formatting handler when we set up the server, it might just work and not set the capability.
Nope, your explanation was clear and I understood it. I would also assume that's the case 😌
I did my testing mostly with purescript-language-server since the project is easy to work with, there already exists an explicit NoFormatter
configuration and explicit ServerCapabilities
which makes it easy to explicitly configure the case without doubts and test how the setting behaves.
For haskell-language-server one option could be to explicitly define the capabilities but another would be to filter out formatting handlers (if the assumption is correct).
This is supported already, just not in an obvious way. To disable the formatter capability, you need to disable all the plugins that carry a documentFormattingProvider
.
EDIT: oh well, this won't work, as we register the request handlers unconditionally and then only run the ones that are enabled. Some work needed to fix that
Is there any update to this?
I'm confused about why formatting has anything to do with the responsibilities of HLS. All I ever want is for my "format document" command in vscode to do exactly the same thing that running the formatting program would do on the command line, and it never does. I'm perpetually frustrated that either because the formatter I need isn't supported, or HLS is for some reason invoking it differently. I just want HLS to back off and stop interfering on this.
@chris-martin please weigh in on https://github.com/haskell/haskell-language-server/issues/411
This plugin is what my organization recommends for setting up our formatter:
https://marketplace.visualstudio.com/items?itemName=SteefH.external-formatters
I haven't been able to use it because HLS's formatter seems to take precedence over what I need to be using.
The minimal thing I need is just to be able to set formattingProvider = null
Workaround for using an external fourmolu
executable in VSCode to override HLS:
Install the jkillian.custom-local-formatters extension.
Add the following to .vscode/settings.json:
{
"[haskell]": {
"editor.defaultFormatter": "jkillian.custom-local-formatters"
},
"customLocalFormatters.formatters": [
{
"command": "fourmolu --stdin-input-file ${file}",
"languages": ["haskell"]
}
]
}
I've had issues with the formatting capabilities of HLS, specifically using formattingProvider = "fourmolu"
I do not understand why it did not respect my fourmolu.yaml
configuration. I am a neovim user and there are some layers of software that could cause this if it's not an HLS (which I am not sure about).
I found a way to stop sending formatting requests to HLS in neovim and instead use something like null-ls
(now none-ls
) to use fourmolu formatter.
I wanted to share the solution that worked for me for anyone else getting frustrated by it:
I added the filter
argument to the vim.lsp.buf.format
command:
vim.keymap.set(
'n',
'<space>lf',
function()
vim.lsp.buf.format {
async = true,
filter = function(client) return client.name ~= "haskell-tools.nvim" end
}
end,
{ noremap = true, silent = true, buffer = bufnr }
)
In my case I am using the haskell-tools.nvim plugin that registers a custom hls intance, thus the client.name ~= "haskell-tools.nvim"
. If you are registering hls normally you will have the client name "hls"
.
The filter workaround is nice. but we can easily disable the formatting directly from the lsp config.
inside the hls's on_attach
set
client.server_capabilities.documentFormattingProvider = false
that's all, I do the same with tsserver, I use biome instead for formatting.
I hope it helped anyone who was trying to disable it.