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

Usage alongside pyright: ruff_lsp executes command of pyright, vice versa #295

Closed
xuanhung1509 opened this issue Oct 25, 2023 · 6 comments
Closed
Labels
question Further information is requested vim Related to the Neo(Vim) editor

Comments

@xuanhung1509
Copy link

The issue

Inside a Python file, if I run PyrightOrganizeImports, which is a command from pyright, pyright will sort the imports, but ruff will throw an error ruff_lsp: -32603: KeyError: 'pyright.organizeimports'.

The same thing happens when I try to run RuffOrganizeImports, which is a user-command I created for ruff.applyOrganizeImports (more on that below), ruff will sort the imports, but pyright will throw an error pyright: 1: Unsupported command.

I suppose the reason for this is that both servers try to execute a command.

How could I constrain a command to a server, so only that server executes the command and not the others?

Lsp settings

local capabilities = vim.lsp.protocol.make_client_capabilities()
local on_attach = function(client, bufnr)
  -- ...
end

local servers = {
  pyright = {},
  ruff_lsp = {
    init_options = {
      -- I use `nvim-lightbulb`, so disable these two to prevent the lightbulb always show
      -- Note that if I leave these two on, and select `Ruff: Organize imports` from the code action menu, pyright won't complain
      settings = {
        organizeImports = false,
        fixAll = false,
      },
    },
    commands = {
      RuffAutofix = {
        function()
          vim.lsp.buf.execute_command({
            command = "ruff.applyAutofix",
            arguments = {
              { uri = vim.uri_from_bufnr(0) },
            },
          })
        end,
        description = "Ruff: Fix all auto-fixable problems",
      },
      RuffOrganizeImports = {
        function()
          vim.lsp.buf.execute_command({
            command = "ruff.applyOrganizeImports",
            arguments = {
              { uri = vim.uri_from_bufnr(0) },
            },
          })
        end,
        description = "Ruff: Format imports",
      },
    },
    on_attach = function(client, bufnr)
      on_attach(client, bufnr)
      -- Disable hover in favor of Pyright
      client.server_capabilities.hoverProvider = false

      vim.keymap.set("n", "<A-O>", "<cmd> RuffOrganizeImports <cr>", { desc = "Organize imports", buffer = bufnr })
    end,
  },
}

require("mason-lspconfig").setup({
  ensure_installed = vim.tbl_keys(servers),
  handlers = {
    function(server_name)
      require("lspconfig")[server_name].setup({
        capabilities = capabilities,
        on_attach = servers[server_name].on_attach or on_attach,
        settings = servers[server_name].settings or {},
        init_options = servers[server_name].init_options or {},
        commands = servers[server_name].commands or {},
      })
    end,
  },
})

Ruff version: 0.0.41

@xulongwu4
Copy link

I think this is more of a question to the neovim community than to ruff-lsp. neovim's API vim.lsp.buf.execute_command sends the requested command to all connected LSP, and as far as I know, there is currently no way of applying a filter. I suggest you look at the implementation of EslintFixAll command in the eslint.lua setup, where it uses client.request() rather than vim.lsp.buf.execute_command so that the command only applies to a specific LSP client.

@xuanhung1509
Copy link
Author

I've realized this is more or less the same issue as here and here.

At first, I came up with a hack like:

vim.lsp.handlers["workspace/executeCommand"] = function(err)
  if err and err.message == "Unsupported command" then
    return
  end
end

However, this seems fragile (the fact that I do not handle anything other than the err, but they still works), so I decided to use nvim-notify to filter out the messages, as the above issue suggested.

Regarding your recommendation, is that from eslint-lsp? I installed the language server but found nothing like that.

image
image

@dhruvmanila
Copy link
Member

dhruvmanila commented Oct 27, 2023

Hey, thanks for the detailed issue report.

I suppose the reason for this is that both servers try to execute a command.

Yes, the reason is as explained by @xulongwu4. I would recommend you to use either one of the server for import sorting whichever you prefer.

For ruff-lsp, you could use the following snippet to execute a command only through the ruff-lsp client:

---@param name string
---@return lsp.Client
local function lsp_client(name)
  return assert(
    vim.lsp.get_active_clients({ bufnr = vim.api.nvim_get_current_buf(), name = name })[1],
    ('No %s client found for the current buffer'):format(name)
  )
end

local servers = {
  ruff_lsp = {
    init_options = {
      settings = {
        -- ...
      },
    },
    commands = {
      RuffAutofix = {
        function()
          lsp_client('ruff_lsp').request("workspace/executeCommand", {
            command = 'ruff.applyAutofix',
            arguments = {
              { uri = vim.uri_from_bufnr(0) },
            },
          })
        end,
        description = 'Ruff: Fix all auto-fixable problems',
      },
      RuffOrganizeImports = {
        function()
          lsp_client('ruff_lsp').request("workspace/executeCommand", {
            command = 'ruff.applyOrganizeImports',
            arguments = {
              { uri = vim.uri_from_bufnr(0) },
            },
          })
        end,
        description = 'Ruff: Format imports',
      },
    },
  },
}

Similar snippet can be created for Pyright as well although I think the command is defined directly in the nvim-lspconfig configuration.

I hope this answers your query. I'll mark this as completed but feel free to ask any further questions or doubts.

@dhruvmanila dhruvmanila added the question Further information is requested label Oct 27, 2023
@xulongwu4
Copy link

Regarding your recommendation, is that from eslint-lsp? I installed the language server but found nothing like that.

I was talking about eslint.lua in nvim-lspconfig. The way EslintFixAll is defined there is very similar to what @dhruvmanila has showed you above.

@xuanhung1509
Copy link
Author

@dhruvmanila I copy-pasted your code and there were some errors. I found they're syntax errors. For example, there are no vim.lsp.get_clients
image
and
image

Having followed @xulongwu4 suggestion, I inspected nvim-lspconfig source code and modified the code as follow:

local function lsp_client(name)
  return assert(
    -- it's `get_active_clients`
    vim.lsp.get_active_clients({ bufnr = vim.api.nvim_get_current_buf(), name = name })[1],
    ("No %s client found for the current buffer"):format(name)
  )
end

-- ...

commands = {
  RuffOrganizeImports = {
    function()
      -- hardcode the method instead
      lsp_client("ruff_lsp").request("workspace/executeCommand", {
        command = "ruff.applyOrganizeImports",
        arguments = {
          { uri = vim.uri_from_bufnr(0) },
        },
      })
    end,
    description = "Ruff: Format imports",
  },
},

Another solution that also works:

-- I prefer creating a user command inside on_attach instead of `commands` as it is documented to be deprecated soon.
on_attach = function(client, bufnr)
  common_on_attach(client, bufnr)

  -- Disable hover in favor of Pyright
  client.server_capabilities.hoverProvider = false

  -- Steal the idea from `eslint.lua`
  local ruff_lsp_client = require("lspconfig.util").get_active_client_by_name(bufnr, "ruff_lsp")

  local request = function(method, params)
    ruff_lsp_client.request(method, params, nil, bufnr)
  end

  local organize_imports = function()
    request("workspace/executeCommand", {
      command = "ruff.applyOrganizeImports",
      arguments = {
        { uri = vim.uri_from_bufnr(bufnr) },
      },
    })
  end

  vim.api.nvim_create_user_command(
    "RuffOrganizeImports",
    organize_imports,
    { desc = "Ruff: Organize Imports" }
  )
end,

Yeah, you two indeed introduced to me a robust solution over my original hack to the issue!

@dhruvmanila
Copy link
Member

@dhruvmanila I copy-pasted your code and there were some errors. I found they're syntax errors.

Ah, sorry. I always use nightly where the new APIs are present.

Yeah, you two indeed introduced to me a robust solution over my original hack to the issue!

Glad to hear that :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested vim Related to the Neo(Vim) editor
Projects
None yet
Development

No branches or pull requests

3 participants