Skip to content
This repository has been archived by the owner on Aug 12, 2023. It is now read-only.

Incorrectly sets formatexpr #1131

Closed
3 tasks done
sQVe opened this issue Sep 22, 2022 · 16 comments
Closed
3 tasks done

Incorrectly sets formatexpr #1131

sQVe opened this issue Sep 22, 2022 · 16 comments
Labels
bug Something isn't working

Comments

@sQVe
Copy link
Contributor

sQVe commented Sep 22, 2022

FAQ

  • I have checked the FAQ and it didn't resolve my problem.

Issues

  • I have checked existing issues and there are no issues with the same problem.

Neovim Version

NVIM v0.8.0-dev-1132-g37a71d1f28

Operating System

Arch Linux 5.19.10-arch1-1

Minimal config

-- this template is borrowed from nvim-lspconfig
local on_windows = vim.loop.os_uname().version:match("Windows")

local function join_paths(...)
local path_sep = on_windows and "\" or "/"
local result = table.concat({ ... }, path_sep)
return result
end

vim.g.loaded_remote_plugins = ""
vim.cmd([[set runtimepath=$VIMRUNTIME]])

local temp_dir = vim.loop.os_getenv("TEMP") or "/tmp"

vim.cmd("set packpath=" .. join_paths(temp_dir, "nvim", "site"))

local package_root = join_paths(temp_dir, "nvim", "site", "pack")
local install_path = join_paths(package_root, "packer", "start", "packer.nvim")
local compile_path = join_paths(install_path, "plugin", "packer_compiled.lua")

local null_ls_config = function()
local null_ls = require("null-ls")
-- add only what you need to reproduce your issue
null_ls.setup({
sources = {},
debug = true,
})
end

local function load_plugins()
-- only add other plugins if they are necessary to reproduce the issue
require("packer").startup({
{
"wbthomason/packer.nvim",
{
"jose-elias-alvarez/null-ls.nvim",
requires = { "nvim-lua/plenary.nvim" },
config = null_ls_config,
},
},
config = {
package_root = package_root,
compile_path = compile_path,
},
})
end

if vim.fn.isdirectory(install_path) == 0 then
vim.fn.system({ "git", "clone", "https://github.com/wbthomason/packer.nvim", install_path })
load_plugins()
require("packer").sync()
else
load_plugins()
require("packer").sync()
end

Steps to reproduce

  • Use null-ls with the internal LSP.
  • Open a file that you haven't configured a server for, in my case markdown.
  • Run :lua =vim.bo.formatexpr

Expected behavior

It should be set to "".

Actual behavior

It is set to v:lua.vim.lsp.formatexpr()

Debug log

[TRACE Thu 22 Sep 2022 01:36:51 PM CEST] ...site/pack/packer/opt/null-ls.nvim/lua/null-ls/client.lua:110: starting null-ls client
[TRACE Thu 22 Sep 2022 01:36:51 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:123: received LSP request for method initialize
[TRACE Thu 22 Sep 2022 01:36:52 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:148: received LSP notification for method initialized
[TRACE Thu 22 Sep 2022 01:36:52 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:148: received LSP notification for method textDocument/didOpen
[TRACE Thu 22 Sep 2022 01:36:52 PM CEST] .../pack/packer/opt/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS_ON_OPEN
[DEBUG Thu 22 Sep 2022 01:36:52 PM CEST] .../pack/packer/opt/null-ls.nvim/lua/null-ls/generators.lua:24: no generators available
[TRACE Thu 22 Sep 2022 01:36:53 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:123: received LSP request for method shutdown
[TRACE Thu 22 Sep 2022 01:36:53 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:148: received LSP notification for method exit
[TRACE Thu 22 Sep 2022 01:37:01 PM CEST] ...site/pack/packer/opt/null-ls.nvim/lua/null-ls/client.lua:110: starting null-ls client
[TRACE Thu 22 Sep 2022 01:37:01 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:123: received LSP request for method initialize
[DEBUG Thu 22 Sep 2022 01:37:01 PM CEST] ...site/pack/packer/opt/null-ls.nvim/lua/null-ls/client.lua:165: unable to notify client for method textDocument/didOpen (client not active): {
textDocument = {
uri = "file:///home/sqve/notes/todo.md"
}
}
[TRACE Thu 22 Sep 2022 01:37:01 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:148: received LSP notification for method initialized
[TRACE Thu 22 Sep 2022 01:37:01 PM CEST] ...im/site/pack/packer/opt/null-ls.nvim/lua/null-ls/rpc.lua:148: received LSP notification for method textDocument/didOpen
[TRACE Thu 22 Sep 2022 01:37:01 PM CEST] .../pack/packer/opt/null-ls.nvim/lua/null-ls/generators.lua:21: running generators for method NULL_LS_DIAGNOSTICS_ON_OPEN

Help

Yes, but I don't know how to start. I would need guidance

Implementation help

No response

Requirements

  • I have read and followed the instructions above and understand that my issue will be closed if I did not provide the required information.
@sQVe sQVe added the bug Something isn't working label Sep 22, 2022
@jose-elias-alvarez
Copy link
Owner

jose-elias-alvarez commented Sep 22, 2022

Since you already commented on the PR, I imagine you're already aware, but this is default Neovim behavior as of neovim/neovim#19677. Given the way it's set up, I don't really think there's a way for us to opt out of this on our end, but it's pretty easy for users to disable it (the easiest option seems to be in an on_attach callback or using the new LspAttach event).

@sQVe
Copy link
Contributor Author

sQVe commented Sep 22, 2022

@jose-elias-alvarez Yeah, I'm aware. I'm just very confused on why it's enabling it for markdown since I'm not running a LSP server on that 🤔

The on_attack callback works but only if I actually have a LSP configured for it. The LspAttack event work to disable it fully.

@jose-elias-alvarez
Copy link
Owner

That's strange - using your minimal config, running :lua =vim.bo.formatexpr shows an empty string (which makes sense, since null-ls will not attach to any buffers if no sources are configured). Are you sure there isn't something else interfering with this?

@sQVe
Copy link
Contributor Author

sQVe commented Sep 22, 2022

@jose-elias-alvarez Sorry, I totally messed up my minimal config. This should be able to reproduce:

-- this template is borrowed from nvim-lspconfig
local on_windows = vim.loop.os_uname().version:match("Windows")

local function join_paths(...)
    local path_sep = on_windows and "\\" or "/"
    local result = table.concat({ ... }, path_sep)
    return result
end

vim.g.loaded_remote_plugins = ""
vim.cmd([[set runtimepath=$VIMRUNTIME]])

local temp_dir = vim.loop.os_getenv("TEMP") or "/tmp"

vim.cmd("set packpath=" .. join_paths(temp_dir, "nvim", "site"))

local package_root = join_paths(temp_dir, "nvim", "site", "pack")
local install_path = join_paths(package_root, "packer", "start", "packer.nvim")
local compile_path = join_paths(install_path, "plugin", "packer_compiled.lua")

local null_ls_config = function()
    local null_ls = require("null-ls")
    -- add only what you need to reproduce your issue
    null_ls.setup({
        sources = {
          null_ls.builtins.diagnostics.vale
        },
        debug = true,
    })
end

local function load_plugins()
    -- only add other plugins if they are necessary to reproduce the issue
    require("packer").startup({
        {
            "wbthomason/packer.nvim",

            {
                "jose-elias-alvarez/null-ls.nvim",
                requires = { "nvim-lua/plenary.nvim" },
                config = null_ls_config,
            },
        },
        config = {
            package_root = package_root,
            compile_path = compile_path,
        },
    })
end

if vim.fn.isdirectory(install_path) == 0 then
    vim.fn.system({ "git", "clone", "https://github.com/wbthomason/packer.nvim", install_path })
    load_plugins()
    require("packer").sync()
else
    load_plugins()
    require("packer").sync()
end

Just adding the vale linter sets the formatexpr. I guess this might make sense but it's really confusing when trying to disable it with the on_attach in your LSP config.

@jose-elias-alvarez
Copy link
Owner

jose-elias-alvarez commented Sep 23, 2022

I see, with this config I can replicate it. The basic issue is that null-ls declares all of its capabilities that it can potentially handle up front, even though its actual capabilities are dependent on which sources a user has actually registered and which sources can run on a given buffer. Since actual LSP servers don't behave this way, there isn't an ideal solution. A similar issue came up in #904, where I mention some potential candidates, but again, I think there's no perfect fix here, since even dynamic registration (which is part of the LSP specification) does not handle each buffer having different capabilities.

I think Neovim setting these defaults is generally a good thing, and gq formatting works (mostly) as expected for null-ls sources that support it (unfortunately Prettier does not support range formatting for Markdown files, but it works in other files). I think the best solution for now is the on_attach workaround, which I confirmed works for null-ls.

@danjenson
Copy link

danjenson commented Oct 5, 2022

Can you clarify the solution here? I'm finding that formatting.prettierd, diagnostics.write_good, and completion.spell all affect formatexpr in some fashion, i.e. they render gq a no-op (at least in markdown files).

@sQVe
Copy link
Contributor Author

sQVe commented Oct 5, 2022

@danjenson You need to set formatexpr to nil on the on_attach function of null-ls (and lspconfig if you want to disable any servers set up there) or disable it for all via a autocmd on the LspAttach event.

I went with the latter which you can see here:

-- Use internal formatting for bindings like gq. 
 vim.api.nvim_create_autocmd('LspAttach', { 
   callback = function(args) 
     vim.bo[args.buf].formatexpr = nil 
   end, 
 })

@Furkanzmc
Copy link
Contributor

I run into this issue as well and the work around I use is like this:

local function is_null_ls_formatting_enabed(bufnr)
    local file_type = api.nvim_buf_get_option(bufnr, "filetype")
    local generators = require("null-ls.generators").get_available(
        file_type,
        require("null-ls.methods").internal.FORMATTING
    )
    return #generators > 0
end

-- In a function that's called in on_attach

function on_attach(client, bufnr)
    if server_capabilities.documentFormattingProvider then
        if
            client.name == "null-ls" and is_null_ls_formatting_enabed(bufnr)
            or client.name ~= "null-ls"
        then
            api.nvim_buf_set_option(bufnr, "formatexpr", "v:lua.vim.lsp.formatexpr()")
            map("n", "<leader>gq", "<cmd>lua vim.lsp.buf.format({ async = true })<CR>", opts)
        else
            api.nvim_buf_set_option(bufnr, "formatexpr", "")
        end
    end
end

dkarter added a commit to dkarter/dotfiles that referenced this issue Oct 20, 2022
@Luis-Licea
Copy link

According to this comment, VSCodeVim/Vim#4523 (comment), gw does its own thing while gq takes into account formatexpr and formatprg.

@chuan137
Copy link

So this might be the reason 'gq' can't format the comment correctly?

engeir added a commit to engeir/stowfiles that referenced this issue Dec 9, 2022
engeir added a commit to engeir/stowfiles that referenced this issue Dec 19, 2022
* refactor: move from lspinstaller to mason
* feat: lsp formatting works alongside gqq
  - Solved from the comments of [#1131](jose-elias-alvarez/null-ls.nvim#1131).
* chore: fill in cmp when traversing list
* chore(nvim): add ruff as python diagnostic
* feat(nvim): add zen-mode plugin
* style(nvim): format file
* chore: clean up keymaps
* fix(keymap): move accept spelling to <C-M> from <C-L>
* fix: spell accept did not work on <c-m>, setting to <c-s>
* chore(lsp): add custom settings
* chore: clean up old LSP setup
* feat(nvim): add treesitter visual select
* fix(globals): the mac uname -n has changed..?
@llimllib
Copy link

@chuan137 yes, here was the fix I committed to my dotfiles to reënable gq

@smlx
Copy link

smlx commented Feb 16, 2023

In case it helps, here's my packer config to work around this issue

  use({
    "jose-elias-alvarez/null-ls.nvim",
    ...
    config = function()
      require("null-ls").setup({
        ...
        on_attach = function(client, bufnr)
          vim.api.nvim_buf_set_option(bufnr, "formatexpr", "")
        end
      })
    ...
    end,
  })

ktsuda added a commit to ktsuda/dotfiles that referenced this issue Feb 17, 2023
@cryptomilk
Copy link
Contributor

The issue is that null-ls always announce formatting capabilities and doesn't check if the builtin actually provides it or not.

Setting the lines https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/lua/null-ls/rpc.lua#L33-L34 to false fixes the issue. So they should be set according to what the builtin provides ...

willnorris added a commit to willnorris/dotfiles that referenced this issue Mar 7, 2023
unset keymap from LazyVim, and use a modified version of the workaround
for null-ls to reset vim.bo.formatexpr when there is no formatting
generator for the current buffer.

Original comment: jose-elias-alvarez/null-ls.nvim#1131 (comment)
@willnorris
Copy link

willnorris commented Mar 7, 2023

I hate to pile on with more "here's how I fixed it" (really, I do), but I think it's worth pointing out that @Furkanzmc's solution above is great because it only clears formatexpr if there is no formatting generator for the current buffer (as opposed to just clearing it globally). If that's the behavior you want, here is a slightly simplified version that I use with LazyVim, making use of hte can_run function in null-ls:

require("lazyvim.util").on_attach(function(client, buf)
  if client.name == "null-ls" then
    if not require("null-ls.generators").can_run(vim.bo[buf].filetype, require("null-ls.methods").lsp.FORMATTING) then
      vim.bo[buf].formatexpr = nil
    end
  end
end)

@reegnz
Copy link

reegnz commented Mar 10, 2023

The issue is that null-ls always announce formatting capabilities and doesn't check if the builtin actually provides it or not.

Can LSPs dynamically report whether they support formatting for the current file-type? If LSP-s can report capabilities dynamically based on what filetype you have open, then this is a null-ls issue and dynamic capability reporting based on filetype should be implemented IMHO.

But I have a feeling this might be an LSP spec limitation. Looking at efm-languageserver they also report capabilities as a whole, not per filetype: https://github.com/mattn/efm-langserver/blob/34b8f5bf9c80a361c56245131fa23b197f94b293/langserver/handle_initialize.go#L51

So this really might be an LSP spec limitation (maybe the server does not get asked about capabilities on a file-by-file basis? 🤷 )

@jose-elias-alvarez
Copy link
Owner

The issue is that null-ls always announce formatting capabilities and doesn't check if the builtin actually provides it or not.

Can LSPs dynamically report whether they support formatting for the current file-type? If LSP-s can report capabilities dynamically based on what filetype you have open, then this is a null-ls issue and dynamic capability reporting based on filetype should be implemented IMHO.

But I have a feeling this might be an LSP spec limitation. Looking at efm-languageserver they also report capabilities as a whole, not per filetype: https://github.com/mattn/efm-langserver/blob/34b8f5bf9c80a361c56245131fa23b197f94b293/langserver/handle_initialize.go#L51

So this really might be an LSP spec limitation (maybe the server does not get asked about capabilities on a file-by-file basis? 🤷 )

Yes, that’s what I tried to explain in this comment. Even with dynamic registration support, there is an inherent incompatibility between the spec and what null-ls is doing here, since there are no provisions for a language server that can theoretically handle any filetype but can only actually handle some based on arbitrary conditions.

Without a VS Code-style API to for non-LSP request handling (which Neovim maintainers have clarified is not a goal of the project) it’s unlikely that there will ever be a perfect solution here. Since the discussion isn’t really going anywhere and several users have contributed good workarounds, I’m going to close this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests