0%

neovim 配置迁移记录

(上一次配 vim 还是上大学之前,转眼就快本科毕业了。)

这次主要是把一部分 .vimrc 中的配置迁移到 neovim 的 lua 风格配置上,同时把一部分插件迁移到 neovim 的生态中。

基本配置

配置主要存放在 ~/.config/nvim,目录结构大致为

1
2
3
4
5
├── init.lua
└── lua
   ├── core
   └── plugins

其中 core 中存放全局配置,plugins 中存放插件相关配置,init.lua 中引用各个配置,因此后面每个文件配置时都需要在 init.luarequire

为了配置迁移的平滑性,先在 init.lua 中加入下面的代码,使其读取原本的 .vimrc 文件

1
2
3
4
5
vim.cmd [[
set runtimepath^=~/.vim
let &packpath = &runtimepath
source ~/.vimrc
]]

core 文件夹中,创建 options.lua 用于配置 neovim 的选项,我的配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
-- 基础配置
local opt = vim.opt

-- 不创建交换文件
opt.swapfile = false

-- 搜索
opt.ignorecase = true
opt.smartcase = true

-- 真彩色
opt.termguicolors = true

-- 缩进
opt.tabstop = 4
opt.shiftwidth = 4
opt.expandtab = true
opt.autoindent = true

-- 使用鼠标
opt.mouse:append("a")

-- 使 gg 等命令把光标移动到行首的第一个非空白字符
opt.startofline = true

-- 关闭自动换行
opt.wrap = false

-- 突出当前行
opt.cursorline = true

-- 使用系统剪切板
opt.clipboard:append("unnamedplus")

core 文件夹中,创建 keymaps.lua 用于配置键位映射,我的配置如下

1
2
3
4
5
6
7
8
-- 按键映射
local keymap = vim.keymap

keymap.set("i", "jk", "<ESC>", {noremap = true, silent = true})

-- 一键编译运行 (SingleCompile)
keymap.set("n", "<F9>", ":SCCompile <cr>")
keymap.set("n", "<F10>", ":SCCompileRun <cr>")

上面这些基本是从 .vimrc 中直接翻译过来

packer

我原本使用 plugged 进行插件管理,这次将新的插件使用 packer 配置,之后会把 plugged 中的插件逐步替换和迁移过来。

plugins 文件夹中创建 packer-setup.lua 用于 packer 的自动安装。我这里使用的安装目录是 ~/.local/share/nvim/site/pack/packer,后面通过 packer 安装的插件也会存放在这个目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
local packer_setup = {}

-- 检查和安装 packer
packer_setup.ensure_packer = function()
local fn = vim.fn
local install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'
if fn.empty(fn.glob(install_path)) > 0 then
fn.system({'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim', install_path})
vim.cmd [[packadd packer.nvim]]
return true
end
return false
end

return packer_setup

plugins 文件夹中创建 packer.lua 用于 packer 的插件管理。安装插件只需要在这里添加 use 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
-- 保存本文件时自动更新插件
vim.cmd([[
augroup packer_user_config
autocmd!
autocmd BufWritePost packer.lua source <afile> | PackerSync
augroup end
]])

local packer_bootstrap = require("plugins.packer-setup").ensure_packer()

return require('packer').startup(function(use)
use 'wbthomason/packer.nvim'
-- My plugins here
use 'neovim/nvim-lspconfig' -- Configurations for Nvim LSP
use 'hrsh7th/nvim-cmp' -- Autocompletion plugin

use 'hrsh7th/cmp-nvim-lsp' -- LSP source for nvim-cmp
use 'hrsh7th/cmp-nvim-lsp-signature-help' -- nvim-cmp source for displaying function signatures

use 'hrsh7th/cmp-path' -- path source for nvim-cmp
use 'hrsh7th/cmp-buffer' -- buffer source for nvim-cmp

-- LuaSnip, not used now
use({"L3MON4D3/LuaSnip", run = "make install_jsregexp"})
use 'saadparwaiz1/cmp_luasnip' -- LuaSnip source for nvim-cmp

use 'sirver/ultisnips'
use 'honza/vim-snippets' -- snippets collection for ultisnips
use 'quangnguyen30192/cmp-nvim-ultisnips' -- ultisnips source for nvim-cmp

use {'iamcco/markdown-preview.nvim', run = 'cd app && yarn install', cmd = 'MarkdownPreview'}

-- File Explorer
use {
'nvim-tree/nvim-tree.lua',
requires = {
'nvim-tree/nvim-web-devicons', -- for file icons
},
}

-- treesitter and its module
use {
'nvim-treesitter/nvim-treesitter',
run = ':TSUpdate'
}
use 'p00f/nvim-ts-rainbow'

-- statusline
use {
'nvim-lualine/lualine.nvim',
requires = { 'kyazdani42/nvim-web-devicons', opt = true }
}

-- colorsheme
use 'rebelot/kanagawa.nvim'
use { 'embark-theme/vim', as = 'embark' }
-- Automatically set up your configuration after cloning packer.nvim
-- Put this at the end after all plugins
if packer_bootstrap then
require('packer').sync()
end
end)

LSP 和补全

neovim 的主要特性之一就是添加了对 Language Server Protocol 的原生支持,我也把原本基于 YouCompleteMe 的补全迁移到 Nvim LSP client 上。用到的插件主要有 nvim-lspconfignvim-cmpcmp-nvim-lsp,还有一些其他的插件提供辅助功能,具体可以看上面的插件列表。我尝试了 LuaSnip,但是感觉不如一直使用的 ultisnips 顺手,所以目前继续使用 ultisnips。LSP 协议支持自定义 style 的 formatting,但 clangd 对这个特性的支持并不好(来源: https://clangd.llvm.org/features#formatting),因此我保留了原本使用的 vim-autoformat 插件用于 formatting。

主要的配置文件就是配置 LSP 的 lspconfig.lua 和配置补全的 nvim-cmp.lua,其中 LSP 的键位基本是默认的,补全则根据以前使用的键位做了一些配置

lspconfig.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
-- Mappings.
-- See `:help vim.diagnostic.*` for documentation on any of the below functions
local opts = { noremap=true, silent=true }
vim.keymap.set('n', '<space>e', vim.diagnostic.open_float, opts)
vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
vim.keymap.set('n', '<space>q', vim.diagnostic.setloclist, opts)

-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
-- Enable completion triggered by <c-x><c-o>
vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

-- Mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
local bufopts = { noremap=true, silent=true, buffer=bufnr }
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts)
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts)
vim.keymap.set('n', '<space>wa', vim.lsp.buf.add_workspace_folder, bufopts)
vim.keymap.set('n', '<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts)
vim.keymap.set('n', '<space>wl', function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, bufopts)
vim.keymap.set('n', '<space>D', vim.lsp.buf.type_definition, bufopts)
vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, bufopts)
vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, bufopts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts)
vim.keymap.set('n', '<space>f', function() vim.lsp.buf.format { async = true } end, bufopts)
end

local lsp_flags = {
-- This is the default in Nvim 0.7+
debounce_text_changes = 150,
}

-- Add additional capabilities supported by nvim-cmp
local capabilities = require("cmp_nvim_lsp").default_capabilities()

local lspconfig = require('lspconfig')

-- Enable some language servers with the additional completion capabilities offered by nvim-cmp
local servers = { 'clangd', 'rust_analyzer', 'pyright', 'tsserver' }
for _, lsp in ipairs(servers) do
lspconfig[lsp].setup {
on_attach = on_attach,
flags = lsp_flags,
capabilities = capabilities,
}
end

nvim-cmp.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
-- ultisnips setup
local cmp_ultisnips_mappings = require("cmp_nvim_ultisnips.mappings")

-- nvim-cmp setup
local cmp = require 'cmp'
cmp.setup {
snippet = {
expand = function(args)
-- luasnip.lsp_expand(args.body)
vim.fn["UltiSnips#Anon"](args.body)
end,
},
mapping = cmp.mapping.preset.insert({
-- 上一个
['<C-k>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
else
cmp_ultisnips_mappings.jump_backwards(fallback)
end
end, { 'i', 's' }),
['<Up>'] = cmp.mapping.select_prev_item(),
-- 下一个
['<C-j>'] = cmp.mapping.select_next_item(),
['<Down>'] = cmp.mapping.select_next_item(),

['<C-u>'] = cmp.mapping.scroll_docs(-4), -- Up
['<C-d>'] = cmp.mapping.scroll_docs(4), -- Down

['<C-z>'] = cmp.mapping.complete(), -- 打开补全列表
['<CR>'] = cmp.mapping.confirm {
behavior = cmp.ConfirmBehavior.Replace,
select = false,
},
['<C-a>'] = cmp.mapping(function(fallback)
cmp_ultisnips_mappings.expand_or_jump_forwards(fallback)
end, { 'i', 's' }),
['<Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
else
cmp_ultisnips_mappings.expand_or_jump_forwards(fallback)
end
end, { 'i', 's' }),
['<S-Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
else
cmp_ultisnips_mappings.jump_backwards(fallback)
end
end, { 'i', 's' }),
}),
sources = {
{ name = 'nvim_lsp' },
{ name = 'nvim_lsp_signature_help' },
{ name = 'ultisnips' },
{ name = 'path' },
{ name = 'buffer' },
},
}

nvim-tree

添加了在打开目录时调用 nvim-tree 的 autocmd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-- disable netrw
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

require("nvim-tree").setup({
sort_by = "case_sensitive",

view = {
width = 30,
mappings = {
list = {
{ key = "u", action = "dir_up" },
},
},
},
renderer = {
group_empty = true,
},
filters = {
dotfiles = true,
},
})

-- open tree when open or change to directory
local function open_nvim_tree(data)
-- buffer is a directory
local directory = vim.fn.isdirectory(data.file) == 1

if not directory then
return
end

-- change to the directory
vim.cmd.cd(data.file)

-- open the tree
require("nvim-tree.api").tree.open()
end

vim.api.nvim_create_autocmd({ "VimEnter" }, { callback = open_nvim_tree })

nvim-treesitter

配置了高亮、折叠和彩虹括号(需要 p00f/nvim-ts-rainbow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
require'nvim-treesitter.configs'.setup {
-- A list of parser names, or "all" (the four listed parsers should always be installed)
ensure_installed = { "c", "lua", "vim", "help", "cpp", "python" },

-- Install parsers synchronously (only applied to `ensure_installed`)
sync_install = false,

-- Automatically install missing parsers when entering buffer
-- Recommendation: set to false if you don't have `tree-sitter` CLI installed locally
auto_install = true,

highlight = {
enable = true,

-- NOTE: these are the names of the parsers and not the filetype. (for example if you want to
-- disable highlighting for the `tex` filetype, you need to include `latex` in this list as this is
-- the name of the parser)
-- list of language that will be disabled
-- disable = { "c", "rust" },
-- Or use a function for more flexibility, e.g. to disable slow treesitter highlight for large files
disable = function(lang, buf)
local max_filesize = 100 * 1024 -- 100 KB
local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
if ok and stats and stats.size > max_filesize then
return true
end
end,

-- Setting this to true will run `:h syntax` and tree-sitter at the same time.
-- Set this to `true` if you depend on 'syntax' being enabled (like for indentation).
-- Using this option may slow down your editor, and you may see some duplicate highlights.
-- Instead of true it can also be a list of languages
additional_vim_regex_highlighting = false,
},
rainbow = {
enable = true,
-- disable = { "jsx", "cpp" }, list of languages you want to disable the plugin for
extended_mode = true, -- Also highlight non-bracket delimiters like html tags, boolean or table: lang -> boolean
max_file_lines = nil, -- Do not enable for files with more than n lines, int
-- colors = {}, -- table of hex strings
-- termcolors = {} -- table of colour name strings
}
}

-- vim.api.nvim_set_hl(0, "@punctuation.bracket", { link = "" })
vim.cmd[[
set foldmethod=expr
set foldexpr=nvim_treesitter#foldexpr()
set nofoldenable
]]

其他

使用了状态栏 nvim-lualine/lualine.nvim 和配色 rebelot/kanagawa.nvim,我使用了一个 appearance.lua 文件管理这两个插件的配置

1
2
3
4
5
-- 配色
vim.cmd('colorscheme kanagawa')

-- 状态栏
require('lualine').setup()