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

Allow disabling the formattingProvider #3283

Open
andys8 opened this issue Oct 11, 2022 · 19 comments
Open

Allow disabling the formattingProvider #3283

andys8 opened this issue Oct 11, 2022 · 19 comments
Labels
type: enhancement New feature or request

Comments

@andys8
Copy link
Collaborator

andys8 commented Oct 11, 2022

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

image

image

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 #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.

InvalidRequest | "No plugin enabled for STextDocumentFormatting" `T.isPrefixOf` message -> pure ()

@andys8
Copy link
Collaborator Author

andys8 commented Oct 11, 2022

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).

image

Therefore a potential solution should be to not propagate the format server capability in the first place.

@michaelpj
Copy link
Collaborator

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.

@andys8
Copy link
Collaborator Author

andys8 commented Oct 11, 2022

: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).

@michaelpj
Copy link
Collaborator

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.

@michaelpj
Copy link
Collaborator

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...

@andys8
Copy link
Collaborator Author

andys8 commented Oct 12, 2022

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

@michaelpj
Copy link
Collaborator

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.

@andys8
Copy link
Collaborator Author

andys8 commented Oct 13, 2022

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.

image

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.

@michaelpj
Copy link
Collaborator

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.

@andys8
Copy link
Collaborator Author

andys8 commented Oct 13, 2022

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).

@pepeiborra
Copy link
Collaborator

pepeiborra commented Oct 13, 2022

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

@chessai
Copy link
Member

chessai commented Jun 21, 2023

Is there any update to this?

@chris-martin
Copy link

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.

@michaelpj
Copy link
Collaborator

@chris-martin please weigh in on #411

@chris-martin
Copy link

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.

@chessai
Copy link
Member

chessai commented Jul 27, 2023

The minimal thing I need is just to be able to set formattingProvider = null

@chris-martin
Copy link

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"]
    }
  ]
}

@Enayaaa
Copy link

Enayaaa commented Feb 15, 2024

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".

@cipherlogs
Copy link

cipherlogs commented Apr 5, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants