Tomas Vik

Neovim LSP rename like a boss (with normal mode keymaps)

TL;DR: If you want to use normal keybindings when LSP-renaming symbols in Neovim, use my config snippet below.

You set up your Neovim with LSP. You set up the neovim/nvim-lspconfig plugin and map space+r to the LSP rename (vim.keymap.set("n", "<leader>r", vim.lsp.buf.rename)). Now you are ready. But when you press space+r, instead of seeing an in-place popup like you’d see in the VS Code, you see a prompt at the bottom of the screen:

VS Code Neovim
image.png image.png

This, by itself, wouldn’t be the end of the world. You are a vimmer after all (not to be confused with Rimmer). The command line prompt is a badge of honor, not a UX hindrance.

But it’s not that simple, this command line input doesn’t let you use normal mode keybindings. What are you? Some pleb that stoops to using arrow keys? Or worse, hold backspace till you delete the word?

This post will help you solve this problem like a pro. Credit for the idea goes to u/vorenyon reddit.

Some plugins improve the rename UI, but it can be simpler than that.

I didn’t know that every time you use the command line (whether it’s a prompt, : command or a / search expression), you can press CTRL-F (:help c_CTRL-F) which takes you to a special Command-line window. Here you can edit your current line or any line in the history and when you press Enter, it will use the current line.

The important detail is that in the Command-line window, you can use the normal mode keybindings that you are used to 🎉

You can trigger the rename and then press CTRL-F to be able to edit it properly.

I always want to use normal mode keybindings when I rename symbols so I wrote this helper to automatically switch to the Command-line window after I start renaming:

  vim.keymap.set("n", "<leader>r", function()
    -- when rename opens the prompt, this autocommand will trigger
    -- it will "press" CTRL-F to enter the command-line window `:h cmdwin`
    -- in this window I can use normal mode keybindings
    local cmdId
    cmdId = vim.api.nvim_create_autocmd({ "CmdlineEnter" }, {
        callback = function()
          local key = vim.api.nvim_replace_termcodes("<C-f>", true, false, true)
          vim.api.nvim_feedkeys(key, "c", false)
          vim.api.nvim_feedkeys("0", "n", false)
          -- autocmd was triggered and so we can remove the ID and return true to delete the autocmd
          cmdId = nil
          return true
    -- if LPS couldn't trigger rename on the symbol, clear the autocmd
        -- the cmdId is not nil only if the LSP failed to rename
        if cmdId then
      end, 500)
  end, bufoptsWithDesc("Rename symbol"))

And here is the final product: