Format a text-object/motion using LSP

The function vim.lsp.buf.format({ async = true }) formats the entire buffer. However, In most cases, I’m interested in formatting only a small part of it. Is there a way to format only a text object instead? E.g. I would like gfip to format an inner paragraph, where gf is the formatting action and ip the text object.

@mroavi

There is the " range formatting " option in vim.lsp.buf.format() ;

format({options})                                       *vim.lsp.buf.format()*
    Formats a buffer using the attached (and optionally filtered) language
    server clients.

    Parameters:  
        {options}  table|nil Optional table which holds the following optional
                   fields:
                   .....
            
                   • 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

Does it work for you?

Thanks @kay-adamof . This is just what I need to create the text-object myself. However, I’ve only created text-objects in vimscript. I’ll try to do it in Lua this time.

Edit: for reference, here is an amazing guide on how to code text-objects in Lua: How to Create Vim Text-Objects in Lua

Edit 2: I realized that rather than writing a “text-object”, what I actually need is to write an operator (:h map-operator).

Edit 3: This problem has been solved here:

and here:

Edit 4: the solution in the links above generates a warning because vim.lsp.buf.range_formatting has been deprecated. I will replace it with @kay-adamof’s suggestion and post the code.

1 Like
function format_range_operator()
  local old_func = vim.go.operatorfunc
  _G.op_func_formatting = function()
    local opts = {
      range = { 
        ['start'] = vim.api.nvim_buf_get_mark(0, '['),
        ['end'] = vim.api.nvim_buf_get_mark(0, ']'),
      }
    }
    vim.lsp.buf.format(opts)
    vim.go.operatorfunc = old_func
    _G.op_func_formatting = nil
  end
  vim.go.operatorfunc = 'v:lua.op_func_formatting'
  vim.api.nvim_feedkeys('g@', 'n', false)
end
vim.keymap.set("n", "gf", "<Cmd>lua format_range_operator()<Cr>")

This works, but the support of the range argument of the different LSP servers is not that good at the moment. Hopefully, it will improve with time.

I am not sure if I am getting this right.

formatexpr is already mapped to vim.lsp.formatexpr() which leverages the vim.lsp.buf.format(opts)

So why not just do gqip or gq and any textobject?

I had no idea this was the case. You are right, there was no need to implement a custom format operator :smiling_face_with_tear:. But ok, at least I learned something. Thanks, @ranjithshegde.