neoclide / coc.nvim

Nodejs extension host for vim & neovim, load extensions like VSCode and host language servers.
Other
24.37k stars 956 forks source link

New configuration like VSCode's editor.defaultFormatter #5076

Closed statiolake closed 1 month ago

statiolake commented 3 months ago

Is your feature request related to a problem? Please describe.

It is often the case that different projects require different formatters for the same language. For example, in Python there are autopep8, black, isort, ruff, and many others. There are currently two solutions to this problem.

  1. Support multiple formatters in an extension and switch formatters as an extension-specific setting. This is the approach taken by coc-pyright.
  2. Provide a configuration to specify the priority of the formatter in the extension's own configurations and adjust the priority well with the other formatters. This is the approach taken by coc-prettier.

However, both of these have some issues. For 1, the extension must support all ever-existing formatters for that language, and this is not practical (and sometimes I use coc-diagnostic to run standalone formatters.) For 2, it is difficult to determine the single perfect priority value for an extension, especially for ones that support multiple file types (just as coc-prettier and coc-diagnostic.) For example, consider the situation where you want to use coc-prettier over coc-diagnostic in JavaScript, but want to use coc-diagnostic over coc-prettier in JSON. coc-prettier has its own configuration to disable formatter per language to support such a usecase, but I think that should be handled by coc.nvim's side.

Describe the solution you'd like

To solve these problems, I want to have a new language-scope setting as VSCode's editor.defaultFormatter.

Describe alternatives you've considered

I initially tried to create a new extension to handle this (namely coc-formatter-dispatcher). If I could execute a specific formatter provided by other extensions, then registering the dispatcher with very high priority and dispatching format request to actual formatter solves the problem. But that was too difficult because there's no coc.nvim API to call another extension's formatter.

Additional context

fannheyward commented 2 months ago

It's a litter difficult to implement defaultFormatter without changes of extensions in current design, still working on this.

statiolake commented 2 months ago

Yeah... I also tried to solve the issue, but I don't even know how to get which extension is being activated within registerDocumentFormatProvider() method. It's difficult to store some "context" on activation in somewhere like global valiables because extensions are activated concurrently with Promise...

statiolake commented 2 months ago

(Memo) I thought VS Code's implementation could give me an idea, but there are fundamental differences in archtecture. VS Code manages all extension API explicitly so they can introduce extension context while coc.nvim is just exposing everything.

https://github.com/microsoft/vscode/blob/ee47c51e7bbdf3ce4fc5085ce5cac453b4281fb3/src/vs/workbench/api/common/extHost.api.impl.ts#L604-L606

statiolake commented 2 months ago

So perhaps we can insert some context variable into the environment which the extension refers on activation? I'm not sure due to my lack of JavaScript knowledge...

statiolake commented 2 months ago

I found parseExtensionName() utility, so I try implementing using this. Now it is a draft PR:

https://github.com/neoclide/coc.nvim/pull/5102

I know this is a bit hacky and not a clean solution... What do you think?

fannheyward commented 2 months ago

Similar POC in my test, save the extension name to provider and get by it. Will test your PR later.

hexh250786313 commented 1 month ago

The parseExtensionName function works really well! Now that we have the formatterExtension for a local project, can we next implement something similar to vscode's ability to configure a default formatter (extension) for different file types?

{
    "[javascript]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[typescript]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[javascriptreact]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[typescriptreact]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[json]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[jsonc]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[markdown]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    }
}
statiolake commented 1 month ago

@hexh250786313 I think that should work as expected already, just as in your example. The configuration added in #5102 is language-overridable.

hexh250786313 commented 1 month ago

@statiolake I'm looking forward to this feature merge!

I test the PR and it works like charm.

{
    "[typescript][javascript][typescriptreact][javascriptreact][javascript.jsx][typescript.tsx]": {
        "coc.preferences.formatterExtension": "coc-biome"
    },
    "[markdown][html]": {
        "coc.preferences.formatterExtension": "coc-diagnostic"
    },
    "diagnostic-languageserver.formatFiletypes": {
        "markdown": "prettier",
        "html": "prettier"
    },
    "diagnostic-languageserver.formatters": {
        "prettier": {
            "command": "npx",
            "args": ["prettier", "--stdin-filepath", "%filename"],
            "rootPatterns": [".git"],
            "isStdout": true,
            "isStderr": true
        }
    }
}