Neovim experience

Original link: https://luyuhuang.tech/2023/03/21/nvim.html

I like Vim’s pure keyboard interaction method, but I have never used Vim very much. Although I am familiar with Vim’s keys, I don’t know Vimscript, how to install plug-ins, and how to customize it. Therefore, I have been using VSCode as The main development tool, with the VSCodeVim plug-in, can enjoy the features of Vim and VSCode at the same time. But I always feel that I need to use real Vim.

Until I came into contact with Neovim , as an improved version of Vim, it uses Lua (one of the languages ​​I am most familiar with) as a configuration file, has very modern features, and solves many legacy problems of Vim. So I spent time learning Neovim, And tune it to my liking. Now I have used Neovim as the main development tool for nearly three months, this article talks about some of my configuration and experience. My Neovim configuration is in the warehouse luyuhuang/nvim.

plug-in

There are many plug-ins I use, and I cannot introduce them one by one. Here are a few that I think are more important.

Plugin Manager: Lazy.nvim

Neovim can install plugins without a plugin manager. But the plugin manager can bring many benefits: plugins can be automatically installed according to the configured plugin name, plugins can be automatically updated, and most importantly: lazy loading can be implemented. If installed Too many plugins will cause Neovim to start slowly. Use the plugin manager to load the plugin when it is really needed.

The plugin manager I am currently using is lazy.nvim. I also used packer.nvim at the beginning, but when there are dependencies between plugins, it does not support lazy loading very well. lazy.nvim solves it very well Solved this problem. When a plug-in is loaded, the plug-ins it depends on will be automatically loaded. That is, I only need to configure the dependencies of the plug-in and when the plug-in needs to be loaded, without caring about what the dependencies are loaded.

Lazy.nvim provides a management interface, which can display the loading status of plugins, install and update plugins, analyze the loading time of plugins, etc.

nvim nvim

The Profile function is very useful. It shows how each plug-in is loaded, time-consuming, and effectively helps us optimize performance. Although I have installed a total of 31 plug-ins, only 3 plug-ins are loaded at startup; other plug-ins are lazy loaded . Startup time is only 35 milliseconds.

Fuzzy Search: Telescope

No one likes to enter a complete file name to open and edit a file. I am more used to the way of vscode and sublime, Ctrl-P to search for files fuzzily. The plug-in I use is telescope.nvim. It is implemented by Lua, supports extensions, and has very powerful functions. It supports almost everything that can be listed, for example:

  • File content fuzzy search
  • Tag symbol search
  • LSP Definition/Reference Search
  • Diagnostic information preview
  • Treesitter Symbol Search
  • Git commit history search
  • Git file change preview

nvimnvim

Telescope needs to manually set the key mapping. I set leader + / to fuzzy search the content of the current file, Ctrl-P to find the file, and Ctrl-O to open the symbol of the current file; gs is set to use the whole word match in normal mode Search the word under the cursor (pass the parameter word_match = '-w' ), set the visual mode to not use the whole word match to search the selected content.

 1
2
3
4
5
6
7
8
9
10
11
12
 local builtin = require ( 'telescope.builtin' )

vim.keymap.set( 'n' , '<leader>/' , builtin.current_buffer_fuzzy_find)
vim.keymap.set( 'n' , '<Cp>' , builtin.find_files)
vim.keymap.set( 'n' , '<Co>' , builtin.current_buffer_tags)
vim.keymap.set( 'n' , 'gs' , function ()
builtin.grep_string({word_match = '-w' })
end )
vim.keymap.set( 'v' , 'gs' , function ()
vim.cmd.normal( '"fy' )
builtin.grep_string({search = vim.fn.getreg( '"f' )})
end )

Telescope is very customizable. For example, global search, sometimes we need whole word matching, sometimes we need to be case-sensitive, and sometimes we need to enable regular expressions. My setting is to use the count mechanism of vim: before executing the command, you can press one A string of numbers indicates how many times the command is repeated. For custom mapping, we can naturally get this string of numbers. However, my definition does not indicate how many times to repeat, but is used to set options: If 1 is pressed, it means that regular matching is enabled ; If you press 2, it means open the whole word match; if you press 3, it means case sensitive.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
twenty one
twenty two
twenty three
twenty four
25
 function live_grep_opts (opts)
local flags = tostring (vim.v.count)
local additional_args = {}
local prompt_title = 'Live Grep'
if flags: find ( '1' ) then
prompt_title = prompt_title .. ' [.*]'
else
table . insert (additional_args, '--fixed-strings' )
end
if flags: find ( '2' ) then
prompt_title = prompt_title .. ' [w]'
table . insert (additional_args, '--word-regexp' )
end
if flags: find ( '3' ) then
prompt_title = prompt_title .. ' [Aa]'
table . insert (additional_args, '--case-sensitive' )
end

opts = opts or {}
opts.additional_args = function () return additional_args end
opts.prompt_title = prompt_title
return opts
end

vim.keymap.set( 'n' , '<leader>s' , function () builtin.live_grep(utils.live_grep_opts{}) end )

Change the logic of tabs: Bufferline.nvim

Vim / Neovim has a concept of buffer (Buffer). Every open file is a buffer, and the buffer can be bound to the window, even if the window is closed, the buffer still exists. The Bufferline.nvim plugin can use the buffer as a page Check it out and display it in the tab bar, replacing the original tab bar.

nvim nvim

This approach changes the logic of the native tab bar, but it has great benefits. In fact, I think the buffer of vim is approximately equal to the tabs of editors such as vscode and sublime. First, when opening a file, if the target If the buffer does not exist, it will be created, otherwise it will switch to the existing buffer. The same is true for jump definition and other operations: jump if the file is already open, otherwise open it in a new tab. This operation logic is the same as vscode.

Since panes can freely bind buffers, this makes the operation logic of multiple panes smooth.

Bufferline needs some simple configuration. My configuration is leader plus number keys to switch tabs, and the leader key is a space. In addition, leader + j and leader + k are used to switch adjacent tabs, and Ctrl-j and Ctrl-k are used to move Tab. ZZ close the current tab (actually delete the current buffer)

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 for i = 1 , 9 do
vim.keymap.set( 'n' , '<leader>' .. i, function () bufferline.go_to_buffer(i, true ) end )
end

vim.keymap.set( 'n' , '<leader>j' , '<Cmd>BufferLineCyclePrev<CR>' )
vim.keymap.set( 'n' , '<leader>k' , '<Cmd>BufferLineCycleNext<CR>' )
vim.keymap.set( 'n' , 'gT' , '<Cmd>BufferLineCyclePrev<CR>' )
vim.keymap.set( 'n' , 'gt' , '<Cmd>BufferLineCycleNext<CR>' )
vim.keymap.set( 'n' , '<Cj>' , '<Cmd>BufferLineMovePrev<CR>' )
vim.keymap.set( 'n' , '<Ck>' , '<Cmd>BufferLineMoveNext<CR>' )
vim.keymap.set( 'n' , 'ZZ' , function ()
if vim.bo.modified then
vim.cmd. write ()
end
local buf = vim.fn.bufnr()
bufferline.cycle( -1 )
vim.cmd.bdelete(buf)
end )

Like an IDE: LSP and Completion

LSP (Language Server Protocol) is a great invention. In the past, we needed C++ IDEs, Java IDEs, C++ IDEs… Different IDEs have different operations, and the functions we need to use are basically jump definitions, find references, Error detection, auto-completion, etc. LSP separates these two parts. Functions such as jumping to definition and auto-completion are completed by an independent process called language server, which uses LSP between it and the editor Communication. In this way, each language only needs to implement its own language server, and developers can use their favorite editors to develop.

Neovim natively supports LSP, and it only needs a few lines of configuration to connect to the language server. But if you are like me and don’t even want to write these lines of code, you can use the nvim-lspconfig plugin. It integrates more than 200 The configuration of the mainstream language server can be accessed with only one line of code.

 1
 lspconfig.clangd.setup{on_attach = on_attach}

on_attach is used to specify the callback function when the language server is connected. Some key mappings can be specified here. For example, I map gd to jump to definition, gr to find references, F2 to rename, etc.

 1
2
3
4
5
6
7
8
 local function on_attach (client, bufnr)
local bufopts = {noremap= true , silent= true , buffer=bufnr}
vim.keymap.set( 'n' , '<Co>' , function () builtin.lsp_document_symbols{symbol_width = 0.8 } end , bufopts) -- 打开当前文件的符号
vim.keymap.set( 'n' , 'gd' , function () builtin.lsp_definitions{fname_width = 0.4 } end , bufopts) -- 跳转定义
vim.keymap.set( 'n' , 'K' , vim.lsp.buf.hover, bufopts) -- 模拟鼠标悬停
vim.keymap.set( 'n' , 'gr' , function () builtin.lsp_references{fname_width = 0.4 } end , bufopts) -- 查找引用
vim.keymap.set( 'n' , '<F2>' , vim.lsp.buf. rename , bufopts) -- 重命名
end

In addition, you can set the variable name to be highlighted when the cursor moves, and the function signature prompt to be displayed when typing, etc.

 1
2
3
4
5
6
7
 local function on_attach (client, bufnr)
...

vim.api.nvim_create_autocmd({ 'CursorHold' , 'CursorHoldI' }, {callback = vim.lsp.buf.document_highlight, buffer = bufnr}) -- 光标不动时高亮变量名
vim.api.nvim_create_autocmd({ 'CursorMoved' , 'CursorMovedI' }, {callback = vim.lsp.buf.clear_references, buffer = bufnr}) -- 移动光标时清除高亮
vim.api.nvim_create_autocmd({ 'TextChangedI' , 'TextChangedP' }, {callback = vim.lsp.buf.signature_help, buffer = bufnr}) -- 输入时显示函数签名提示
end

Completion

Neovim’s native completion can also be used, but plug-ins can bring a better experience. I use nvim-cmp, which supports multiple completions, and different completion capabilities are provided by plug-ins. For example:

  • According to the semantic analysis of language server, it can be provided by the plug-in hrsh7th/cmp-nvim-lsp .
  • According to word completion in buffer, it can be provided by plugin hrsh7th/cmp-buffer .
  • According to the symbol completion in tags, it can be provided by the plugin quangnguyen30192/cmp-nvim-tags .

nvim-cmp needs to cooperate with the snippet plug-in to realize LSP code snippet completion. I use saadparwaiz1/cmp_luasnip . Just do some simple configuration

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 require ( 'cmp' ).setup{
preselect = cmp.PreselectMode.None,
snippet = { -- snippet 插件设置
expand = function (args)
require ( 'snippet' ).lsp_expand(args.body)
end ,
},
mapping = cmp.mapping.preset. insert ({ -- 快捷键
[ '<Tab>' ] = cmp.mapping.select_next_item(),
[ '<S-Tab>' ] = cmp.mapping.select_prev_item(),
[ '<CR>' ] = cmp.mapping.confirm(),
}),
sources = { -- 补全优先级. LSP 优先级最高, 其次是tags 和buffer
{name = 'nvim_lsp' },
{name = 'tags' },
{name = 'buffer' },
},
}

The effect of LSP and completion can be seen in the video at the beginning of this article.

Where to hit: Hop.nvim

Finally, I will introduce a plug-in that I like very much: hop.nvim. Using it, you can easily jump to any position on the screen:

The example in the video is to jump to the starting position of a word. After pressing the shortcut key, several letters will appear at the starting position of each word on the screen. Press the corresponding letter to jump to the corresponding position. Hop. nvim supports multiple jump methods:

  • Jump to full screen word start position
  • Jump to the beginning of the word in the current line
  • Jump to the beginning of each line
  • Jump to the position of the specified single letter in the full screen
  • Jump to the position of the specified double letter in the full screen
  • Jump to any position in the current column

In short, its functions are very rich. For me, the two jump methods of “jump full screen word” and “jump current line word” are enough. I map them to leader+ h and leader respectively + f.

practical configuration

open the clipboard

Vim has its own clipboard system, and the copied content will be put into the vim register. But the incompatibility between the editor’s clipboard and the system’s clipboard has brought us a lot of trouble. Fortunately, Neovim has a support.

For decoupling, Neovim does not directly provide clipboard support, but relies on independent tools. When data is written to the + or * register, Neovim will look for the clipboard tool in the system, and if there is one, it will synchronize the data Give them. Similarly, when reading + or * , Neovim will also try to read data from the clipboard tools. Neovim natively supports tools such as pbcopy , pbpaste , xclip win32yank tmux etc., as long as these tools are installed, they will automatically enable.

In Windows/WSL, we can use win32yank. It is included in the Windows version of Neovim, just make sure it is under PATH. That is to say, basically as long as the Windows version of Neovim is installed, the clipboard has been opened . We just need to set up the key mapping, let copy and paste use + or * register.

 1
2
3
4
 vim.keymap.set({ 'i' , 'c' }, '<Cv>' , '<Cr>+' )
vim.keymap.set( 'v' , '<Cv>' , '"+p' )
vim.keymap.set( 't' , '<Cv>' , '<C-\\><Cn>"+pa' )
vim.keymap.set( 'v' , '<Cc>' , '"+y' )

I map Ctrl-C in visual mode to copy; map Ctrl-V in insert mode, command mode, visual mode, and terminal mode to paste. Ctrl-V in normal mode still needs to retain the original behavior for entering blockwise visual model.

Exe can be executed directly in WSL, so you only need to make win32yank under the PATH of WSL to open the clipboard.

For Neovim with ssh remote access, you can use tmux. tmux also has its own clipboard system called buffer. tmux load-buffer is used to load content from files to buffer, which is equivalent to setting clipboard content; tmux save-buffer is used to save the content in the buffer to a file, which is equivalent to reading the content of the clipboard. The latest version of tmux (3.3a) supports sending the content of the buffer to the clipboard of the target client, just execute load-buffer Just use the -w option when load-buffer . This opens up the clipboard of the remote Neovim and the local machine.

Neovim natively supports tmux as a clipboard tool, but does not support -w parameter. However, Neovim supports user-defined clipboard tools, which can specify the behavior of accessing registers when copying and pasting:

 1
2
3
4
5
6
7
8
9
10
11
12
 if vim.env.TMUX then
vim.g.clipboard = {
name = 'tmux-clipboard' ,
copy = {
[ '+' ] = { 'tmux' , 'load-buffer' , '-w' , '-' },
},
paste = {
[ '+' ] = { 'tmux' , 'save-buffer' , '-' },
},
cache_enabled = true ,
}
end

The above configuration means that when copying to the + register, the command tmux load-buffer -w - the copied content will be passed to tmux in the form of standard input; when the content is pasted from the + register, the command will be executed tmux save-buffer - Read the content to paste from standard output. - at the end of the command tells tmux to read and write content from standard input/output.

jump to beginning of line

In Vim, press 0 to jump to the beginning of the current line, and press ^ to jump to the first non-blank character of the current line. But the position of ^ key is just in the middle of the hands, and you need to hold down the Shift key and press it It is not convenient. I think the interaction of vscode is very good: when you press the Home key, you will return to the first non-blank character of the line; press Home again, and you will jump to the beginning of the line. Such a key is realized Two operations, very convenient.

Thanks to the high customizability of Neovim, we can also write a few lines of code to implement this function in Neovim.

 1
2
3
4
5
6
7
8
9
 local function home ()
local head = (vim.api.nvim_get_current_line(): find ( '[^%s]' ) or 1 ) - 1
local cursor = vim.api.nvim_win_get_cursor( 0 )
cursor[ 2 ] = cursor[ 2 ] == head and 0 or head
vim.api.nvim_win_set_cursor( 0 , cursor)
end

vim.keymap.set({ 'i' , 'n' }, '<Home>' , home)
vim.keymap.set( 'n' , '0' , home)

It is very simple to implement, it is nothing more than judging the current cursor position, and then jumping according to the situation.

restore last session

An editor like vscode can restore the last session when it is opened, automatically open the last opened file, and the cursor will jump to the last edited position. Neovim also has this function, but it will not be executed automatically, but will be The timing and method of execution are left to the user to ensure full customization. We only need to write a few lines of code to achieve similar effects.

Neovim uses mksession command to save the current session state as a .vim session file. The saved content is specified by sessionoptions option, which can be buffer, current directory, window size, tab, global variables set by the current session, key mapping, etc. Default The save path is the current directory. You only need to source the saved .vim session file to restore the session state.

My approach is to set an automatic command to save the current session state when exiting neovim. There is no need to save too many things, I set sessionoptions to save only the most necessary items

 1
 vim.opt.sessionoptions = 'buffers,curdir,tabpages,winsize'

Saving in the current directory may not be very friendly to version control, so I choose to save it in a fixed location. I use the sha1 value of the current path as the file name of the session file.

 1
2
3
4
5
6
 local path = vim.fn.expand(vim.fn.stdpath( 'state' ) .. '/sessions/' )

vim.api.nvim_create_autocmd( 'VimLeavePre' , {callback = function ()
vim.fn.mkdir( path , 'p' )
vim.cmd( 'mks! ' .. path .. vim.fn.sha256(vim.fn.getcwd()) .. '.vim' )
end })

In order not to slow down the startup speed, and I don’t want to restore the last session every time I open it, I choose to set a custom command to manually restore the session. When restoring, you only need to load the session file with the source command.

 1
2
3
4
5
6
 vim.api.nvim_create_user_command( 'Resume' , function ()
local fname = path .. vim.fn.sha256(vim.fn.getcwd()) .. '.vim'
if vim.fn.filereadable(fname) ~= 0 then
vim.cmd.source(fname)
end
end , {})

The last cursor position of the file is saved in the '" mark. Create an automatic command to check this mark and jump when opening the file to restore the last position of the cursor.

 1
2
3
4
5
6
 vim.api.nvim_create_autocmd( 'BufReadPost' , {callback = function ()
local line = vim.fn.line( '\'"' )
if line > 1 and line <= vim.fn.line( '$' ) then
vim.cmd.normal( 'g\'"' )
end
end })

Summary and experience

The main points of my experience in the past few months are as follows

  • Pure keyboard interaction, easy to use. After using Neovim, the frequency of using the mouse has been greatly reduced again. Before using vscode + vscode-vim, I still use the mouse from time to time: switching to the terminal, switching different vscodes, copying and pasting each other, etc. .And Neovim can cooperate with tmux to switch between the editor and the terminal at will, and the clipboard is also interoperable. btw My keyboard is HHKB, which has a small number of keys and convenient key positions (especially the Ctrl key and ESC key) , which is very suitable for this kind of interaction logic.

  • It is highly customizable. Neovim has a rich plug-in ecology, and you can always find various plug-ins to meet your needs. However, this is not the most attractive, because the plug-in ecology of vscode is also very rich. I think Vim/Neovim’s biggest The characteristic is that its configuration file is not json, not ini, but a complete programming language. You can write code in the configuration file at any time to meet your various needs. If you want to add functions to vscode or sublime, you must do it Write plugins; while Neovim only needs to write a few lines of code in the configuration file. Neovim’s API is extremely rich, and you can even use it to implement a web server.

  • Neovim is a modern editor. Vim, which is constantly being updated, is not outdated, let alone Neovim. In fact, if your terminal emulator is too old, you may not even be able to use Neovim normally, because it requires special font rendering support, true color Terminal support, etc. Neovim has many modern features, such as LSP, Treesitter, asynchronous tasks, multi-threading, remote control, etc.; but the interface is TUI, pretending to be an old antique. In short, it is so powerful that I just I was amazed when I touched it.

  • Customization is the soul. To make good use of Neovim, you should not copy other people’s configurations, or find some “out-of-the-box” configurations, you must understand the configuration yourself. I think the positioning of Vim/Neovim is highly customizable, if you If you are looking for out-of-the-box use, you should use vscode directly. Neovim has rich help documents, and you can always find answers if you make good use of the help command; and once you understand and get started, you will find a lot of fun. This is the fun of DIY, Similar to customizing keyboards, assembling computers, ham radios… It’s okay to use the time of vibrato to do this.

This article is transferred from: https://luyuhuang.tech/2023/03/21/nvim.html
This site is only for collection, and the copyright belongs to the original author.