In neovim I would like to set a keybinding -- via lua config -- to use LSP to format the visual selection when I type \qf.

Using this setting, I can format the whole buffer from normal mode:

vim.api.nvim_set_keymap('n', '\\qf', '<cmd>lua vim.lsp.buf.formatting()<CR>', {noremap = true})

In order to format just the visual selection, I believe I need to use the range_formatting() function. I tried this:

vim.api.nvim_set_keymap('v', '\\qf', '<cmd>lua vim.lsp.buf.range_formatting()<CR>', {noremap = true})

Unfortunately, this gives me the following error:

r_language_server: -3
2603: Error: argument is of length zero
Call: if (!nzchar(line)) return(units) 
Stack trace:
1: code_point_from_unit(text,position$character)
2: document$from_lsp_position(params$range$start)
3: dispatch(self, id, params) 

How can I modify the keymap setting above to pass the start and end position of the selection, and format the visual selection using LSP?

  • The doc :h vim.lsp.buf.range_formatting() seems to say that you need to provide 3 arguments to the function: Some formatting options as well as the range to format. Have you tried that?
    – statox
    Commented Mar 7, 2022 at 10:49
  • Thanks, I will look into that. I am a total beginner, so I don’t know how to pass the start and end positions as arguments to a lua function, hence my question.
    – Vincent
    Commented Mar 7, 2022 at 12:54

3 Answers 3


I found this question while solving the same problem, but now the function range_formatting() is deprecated. Now should use vim.lsp.formatexpr() or vim.lsp.buf.format() instead.

You can define a function that does the same like:

function FormatFunction()
    async = true,
    range = {
      ["start"] = vim.api.nvim_buf_get_mark(0, "<"),
      ["end"] = vim.api.nvim_buf_get_mark(0, ">"),

And map this function the same way:

vim.api.nvim_set_keymap("v", "\\qf", "<Esc><cmd>lua FormatFunction()<CR>", {noremap = true})


I tried to keep the answer simple and short, but maybe that is not the right way. As wardw mentioned, it's a better way to use vim.keymap.set, you can use it for both normal and visual mode. I personally add async option:

map({"n", "v"}, "\\gf", function()
    vim.lsp.buf.format({ async = true })
end, opts)

But be aware of editing buffer while formatting, as the documentation say:

async boolean|nil If true the method won't block. Defaults to false. Editing the buffer while formatting asynchronous can lead to unexpected changes.

The difference between the function FormatFunction and vim.lsp.buf.format is that u cannot use vim.lsp.buf.format as cmd like

:lua vim.lsp.buf.format()

This will format the whole file.


It's sufficient just to do

vim.keymap.set("v", "<leader>qf", vim.lsp.buf.format, { remap = false })

From :h vim.lsp.buf.format(),

range (table|nil) Range to format. Table must contain start and end keys with {row, col} tuples using (1,0) indexing. Defaults to current selection in visual mode Defaults to nil in other modes, formatting the full buffer.

so the range already defaults to the current selection in visual mode. Super!


The documentation for the range_formatting() states:

range_formatting({options}, {start_pos}, {end_pos})
                Formats a given range.

                Parameters: ~
                    {options}    Table with valid `FormattingOptions` entries.
                    {start_pos}  ({number, number}, optional) mark-indexed
                                 position. Defaults to the start of the last
                                 visual selection.
                    {end_pos}    ({number, number}, optional) mark-indexed
                                 position. Defaults to the end of the last
                                 visual selection.

Note that the default values are the last visual selection. A simple keybinding is thus:

vim.api.nvim_set_keymap('v', '\\qf', '<ESC><cmd>lua vim.lsp.buf.range_formatting()<CR>', {noremap = true})
  • 1
    why the initial <ESC> is needed in the key mapping? How did you figured it out? Thank you.
    – Ray
    Commented Jul 26, 2022 at 2:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.