Skip to content

ranges for textDocument/rangeFormatting are wrong #19123

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

Closed
jyn514 opened this issue Feb 9, 2025 · 2 comments · Fixed by #19124
Closed

ranges for textDocument/rangeFormatting are wrong #19123

jyn514 opened this issue Feb 9, 2025 · 2 comments · Fixed by #19124
Labels
C-bug Category: bug

Comments

@jyn514
Copy link
Member

jyn514 commented Feb 9, 2025

rust-analyzer version: rust-analyzer 1.86.0-nightly (f7cc13a 2025-01-25)

rustc version: rustc 1.86.0-nightly (f7cc13af8 2025-01-25)

rustfmt version: rustfmt 1.8.0-nightly (f7cc13af82 2025-01-25)

editor or extension: neovim 0.10.4, lspconfig 1.6.0 00dae9f

relevant settings:

-- can skip these first two lines if you have a plugin manager
local lazypath = vim.fn.stdpath("data") .. "/lazy/nvim-lspconfig"
vim.opt.rtp:prepend(lazypath)

local lspconfig = require('lspconfig')
local settings = { ['rust-analyzer'] = { rustfmt = { rangeFormatting = { enable = true } } } }
lspconfig.rust_analyzer.setup {
    settings = settings,
    -- https://github.com/rust-lang/rust-analyzer/issues/17301
    init_options = settings,
    on_init = function(client)
        -- the real code checks if rustfmt is nightly or not
        vim.opt_local.formatexpr = 'v:lua.vim.lsp.formatexpr()'
        client.on_attach = function()
            vim.opt_local.formatexpr = 'v:lua.vim.lspformatexpr()'
        end
    end
}

code snippet to reproduce:

fn main() {
    let unit_offsets_cache = collect(dwarf.units  ())  ?;
}

steps to reproduce:

  1. verify that formatting with RA is enabled: set formatexpr? (should show formatexpr=v:lua.vim.lsp.formatexpr())
  2. move your cursor to line 2. run Vgq to format the current line. observe that nothing happens.
  3. move your cursor to line 3. run Vgq. observe that line 2 is formatted.

I debugged that nvim is sending 0-indexed lines, so maybe RA or rustfmt is expecting 1-indexed lines?

@jyn514 jyn514 added the C-bug Category: bug label Feb 9, 2025
@jyn514
Copy link
Member Author

jyn514 commented Feb 9, 2025

if i fix that off by one error

(you don't want to know how)

-- copied directly from vim.lsp.formatexpr, except not off-by-one
function formatexpr(opts)
  opts = opts or {}
  local timeout_ms = opts.timeout_ms or 500
  local ms = vim.lsp.protocol.Methods

  if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
    -- `formatexpr` is also called when exceeding `textwidth` in insert mode
    -- fall back to internal formatting
    return 1
  end

  local start_lnum = vim.v.lnum
  local end_lnum = start_lnum + vim.v.count - 1

  if start_lnum <= 0 or end_lnum <= 0 then
    return 0
  end
  local bufnr = vim.api.nvim_get_current_buf()
  for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
    if client.supports_method(ms.textDocument_rangeFormatting) then
      local params = vim.lsp.util.make_formatting_params()
      local end_line = vim.fn.getline(end_lnum) --[[@as string]]
      local end_col = vim.lsp.util._str_utfindex_enc(end_line, nil, client.offset_encoding)
      --- @cast params +lsp.DocumentRangeFormattingParams
      params.range = {
        start = {
          line = start_lnum,
          character = 0,
        },
        ['end'] = {
          line = end_lnum,
          character = end_col,
        },
      }
      print('line_start: '..tostring(params.range.start.line)..', line_end: '..tostring(params.range['end'].line), 'column_start: '..tostring(0)..', column_end: '..tostring(end_col))
      local response =
        client.request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr)
      if response and response.result then
        vim.lsp.util.apply_text_edits(response.result, bufnr, client.offset_encoding)
        return 0
      end
    end
  end

  -- do not run builtin formatter.
  return 0
end

then things mostly work. but consider this statement:

        let sup_offsets_cache = if let Some(sup) = dwarf.sup( ) {
            collect(sup.units() )?
        } else {
            Vec::new( )
        };

highlighting all 5 lines at once works fine. and highlighting the first 2 lines also works fine. but highlighting only line 4, or only line 2, or lines 3-5, does not work. probably this is a rustfmt bug because it can't work with partial ASTs?

@jyn514
Copy link
Member Author

jyn514 commented Feb 10, 2025

highlighting all 5 lines at once works fine. and highlighting the first 2 lines also works fine. but highlighting only line 4, or only line 2, or lines 3-5, does not work. probably this is a rustfmt bug because it can't work with partial ASTs?

yeah this is rust-lang/rustfmt#4053, not an RA bug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant