RE:137

时间是唯一致死的毒药。

neovim 笔记

写在前面#

之前主要使用的文本编辑器是 VS Code,但是在使用 ssh 插件连接研究所的服务器和超级计算机时总会遇到各种问题,因此我开始寻找一个 CLI 的替代方案。因为之前学过一点 Vim(并且偶尔会用用),就抱着试一试的心态在服务器上配置了 neovim,结果经过测试发现:

  1. 插件的安装没有任何问题
  2. 即使隔了一层 login shell,配色方案也依旧可以保留(非常重要)
  3. 使用 LSP 后,定义查找和补全体验与 VS Code 相差无几
  4. 纯键盘操作很流畅非常 geek ,但想用鼠标的时候也很方便

基于这些原因,我准备将 neovim 作为服务器端主要的编辑器(在本地写代码实在很难有能超越 VS Code 的方案)。现简要记录作为备忘录。

参考资料#

最实用入门教程:$ vimtutor#

“从入门到放弃类”教程#

neovim 官方文档#

配置参考#

Vim 基础笔记#

Buffer, window and tab#

摘录自官方文档

  • A buffer is the in-memory text of a file.
  • A window is a viewport on a buffer.
  • A tab page is a collection of windows.

快捷键拾遗(nvim)#

  • U:撤消在一行中所做的改动
    视为 操作 而非 撤销;不可用CTRL-R来重做
  • J:将下一行的内容合并至本行行末
  • CTRL-G:显示当前编辑文件中当前光标所在行位置以及文件状态信息
  • CTRL-O:回到之前的位置
    CTRL-I: 跳转到较新的位置
  • %:查找配对的括号
  • :s/old/new/g:替换 old 为 new
    :#,#s/old/new/g:#,# 代表替换操作的若干行中首尾两行的行号
    :%s/old/new/g:替换整个文件中的每个匹配串
    :%s/old/new/gc:找到整个文件中的每个匹配串,并且对每个匹配串提示是否进行替换
  • Command 模式下:
    Ctrl+R " 插入最近一次复制/剪切/删除的内容。
    Ctrl+R 0 插入最近一次复制的内容。
    Ctrl+F 选择一条历史命令(包括当前正在键入的命令)来编辑。(normal 模式下输入 q: 也可达到同样效果)

neovim 配置#

  • 2024/04/03 更新 :AstroNvim 在更新到 v4 后把自己变成了 LazyVim 的“又一个插件”,所以也可以直接用 LazyVim 来配置。

    我在尝试之后,还是乖乖地把 AstroNvim 从 v3 的配置迁移到了 v4。具体配置可以在我的 dotfiles 仓库找到。AstroNvim 好!

  • 2023/10/14 更新 :已经转投 AstroNvim 的怀抱!

    以前根据掘金小册摸索出来的配置有时不太稳定,并且如果要跟上各个插件的更新,配置的定期维护成本也非常高,所以建议还是从社区提供的框架(比如我用的 AstroNvim)开始配置。

完全由自己折腾的配置已弃用,如果有兴趣的话,可以参考本文附录掘金小册和我的 neovim-config 仓库。

结语#

如此这般,Vim 的用法也大概会了,neovim 也总算是美化得差不多了,终于可以开始享受快乐 geek 编程啦!话虽如此,刚开始时适应 Vim 的操作逻辑很困难,并且折腾一个漂亮好用的 neovim 环境也非常麻烦。对比之下,VS Code 不用折腾几乎开箱即用,并且在不少场景下很难被 neovim 替代,所以依旧是一个非常推荐的本地文本编辑器。

总之,不管用哪一款软件,它们终究是工具,适合好用才是最重要的,可不要有 xxx 使用者的优越感哦~


附录:从零折腾 nvim 配置#

(已弃用,仅作纪念)

网上的资料给出了一些基本配置,但是我还是对照着官方文档自己重新折腾了一遍(还是得自己花时间调整)。完整配置请参考我的 github 仓库

我的 ~/.config/nvim 文件夹大致结构如下:

.
├── init.lua
└── lua
    ├── basic.lua
    ├── colorscheme.lua
    ├── keymaps.lua
    ├── plugin-config
           ⋮
    └── plugins.lua

所有 lua 文件夹中的配置都需要在 init.lua 中导入,具体例子为:

…
require('basic')
require('plugin-config.nvim-tree')
…

基础设置(UI、缩进、搜索及其他)#

细节请参考 neovim 官方文档。创建 lua/basic.lua,添加:

-- Hint: use `:h <option>` to figure out the meaning if needed

-- Basic UI config
vim.opt.number = true        -- Show absolute line number
vim.opt.signcolumn = "yes"   -- Sign column for showing status
vim.opt.colorcolumn = "80"   -- Reference for the line length
vim.opt.scrolloff = 8        -- Number of screen lines to keep above and below the cursor
vim.opt.cursorline = true    -- Highlight cursor line
vim.opt.cmdheight = 1        -- Number of screen lines to use for the command-line
vim.opt.termguicolors = true -- Enable 24-bit RGB color in the TUI
vim.opt.splitbelow = true    -- open new vertical split bottom
vim.opt.splitright = true    -- open new horizontal splits right
vim.opt.showtabline = 2      -- Always show tabline
vim.opt.showmode = false     -- Hide mode hint like "-- INSERT --"

-- Completion UI config
vim.opt.completeopt = "menuone,noselect,noinsert" -- List of options for Insert mode completion
vim.opt.shortmess = vim.o.shortmess .. 'c'        -- Don't give ins-completion-menu messages
vim.opt.pumheight = 10                            -- Number of lines for the completion menu

-- CMD UI config
vim.cmd([[ autocmd TermOpen * setlocal nonumber ]]) -- Turn off the terminal line number

-- Show space as dot
vim.opt.list = true -- Show invisible characters
vim.opt.listchars = "space:·"

-- Indent config
vim.opt.tabstop = 8        -- Number of spaces that a <Tab> in the file counts for
vim.opt.softtabstop = 2    -- Number of spaces that a <Tab> counts for while performing editing operations
vim.opt.shiftwidth = 2     -- Number of spaces to use for each step of (auto)indent
vim.opt.expandtab = true   -- Use the appropriate number of spaces to insert a <Tab>
vim.opt.shiftround = true  -- Round indent to multiple of 'shiftwidth' (N * shiftwidth)
vim.opt.autoindent = true  -- Copy indent from current line when starting a new line
vim.opt.smartindent = true -- Do smart autoindenting when starting a new line

-- Search config
vim.opt.incsearch = true  -- Search as characters are entered
vim.opt.ignorecase = true -- ignore case in searches by default
vim.opt.smartcase = true  -- but make it case sensitive if an uppercase is entered
vim.opt.hlsearch = false  -- Do not highlight matches

-- Disable netrw to use nvim-tree
vim.g.loaded_netrw = 1
vim.g.loaded_netrwPlugin = 1

-- Set leader key (for keymaps)
vim.g.mapleader = ' '
vim.g.maplocalleader = ' '

-- Misc
vim.opt.clipboard = 'unnamedplus' -- Use system clipboard
vim.opt.autoread = true           -- Automatically read the file when it is modified outside
vim.opt.whichwrap = '<,>,[,]'     -- Use <Left> <Right> to jump to next line
vim.opt.mouse = "a"               -- Allow the mouse to be used in neovim
vim.opt.updatetime = 1000         -- If this many milliseconds nothing is typed the swap file will be written to disk
vim.opt.timeoutlen = 500          -- Time in milliseconds to wait for a mapped sequence to complete

基础快捷键设置#

细节请参考 neovim 官方文档。创建 lua/keymaps.lua,添加:

-- Alias
local map = vim.keymap.set
local opts = { noremap = true, silent = true }

-- Split screen
map('n', '<leader>s', '<C-w>s', opts)
map('n', '<leader>v', '<C-w>v', opts)
map('n', '<leader>c', '<C-w>c', opts) -- Close current
map('n', '<leader>o', '<C-w>o', opts) -- Close Others

-- Move cursor amoung windows
map('n', '<A-j>', '<C-w>j', opts)
map('n', '<A-k>', '<C-w>k', opts)
map('n', '<A-h>', '<C-w>h', opts)
map('n', '<A-l>', '<C-w>l', opts)

-- Window resizing
map('n', '<C-Up>', ':resize +2<CR>', opts)
map('n', '<C-Down>', ':resize -2<CR>', opts)
map('n', '<C-Left>', ':vertical resize -2<CR>', opts)
map('n', '<C-Right>', ':vertical resize +2<CR>', opts)

-- Terminal mode
map('n', '<leader>t', ':new | resize -6 | terminal<CR>', opts)
map('t', '<Esc>', '<C-\\><C-n>', opts)

-- Continuous indentation in Visual mode
map('v', '<', '<gv', opts)
map('v', '>', '>gv', opts)

-- Move multiple lines in Visual mode
map('v', 'J', ":move '>+1<CR>gv=gv")
map('v', 'K', ":move '<-2<CR>gv=gv")

-- Scroll window
map('n', '<C-j>', '4j', opts)
map('v', '<C-j>', '4j', opts)
map('n', '<C-k>', '4k', opts)
map('v', '<C-k>', '4k', opts)

后续插件的快捷键设置也会保存在这个文件中。

插件管理器及 UI 美化#

默认已安装某一款 Nerd Fonts

首先安装插件管理器 lazy.nvim。根据 README,创建 lua/plugins.lua 并添加如下内容:

-- Bootstrap for lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  …
})

完成后重启 nvim,然后输入 :Lazy应该就能看到效果了!接下来就可以美化 UI 了!

下面的配置基本参考官方文档就可以完成,所以我只给出一个使用 lazy.nvim 安装插件的例子,其他的插件就只记录名称和作用以及一些值得一提的额外配置。我的插件快捷键设置都放在了对应的插件配置文件里,这样在想禁用某个插件的时候,只要在 init.lua 中注释掉相应的 require 就可以完全屏蔽掉所有相关配置。不明白的地方也可以参考我的 github 仓库

Colorscheme: Catppuccin(主题)#

选使用人数多的一般不会出大问题。

  1. lua/plugins.lua 中的 require("lazy").setup() 部分添加:
require("lazy").setup({
  -- catppuccin
  {
    "catppuccin/nvim"
    name = "catppuccin",
    priority = 1000,
  },
})
  1. 创建 lua/theme.lua,添加以下内容:
local colorscheme = 'catppuccin-frappe'

local status_ok, _ = pcall(vim.cmd, 'colorscheme ' .. colorscheme)
if not status_ok then
  vim.notify('colorscheme ' .. colorscheme .. ' not found!')
  return
end

更好的语法高亮支持:nvim-treesitter#

注意官方给出的 Folding 配置有点问题,要改成:

vim.opt.foldmethod = "expr"
vim.opt.foldexpr = "nvim_treesitter#foldexpr()"
vim.opt.foldlevel = 99

侧栏文件浏览:nvim-tree#

后面如果要用 project 插件的话需要在 setup 中加这么几行:

  -- For project.nvim
  sync_root_with_cwd = true,
  update_focused_file = {
    enable = true,
    update_root = true,
  },
  respect_buf_cwd = true,

有一个追加设置记录在这里:

-- Quit after last buffer has been closed
vim.cmd([[
  autocmd BufEnter * ++nested if winnr('$') == 1 && bufname() == 'NvimTree_' . tabpagenr() | quit | endif
]])

快捷键设置大概是这样的:

map("n", "<leader>m", ":NvimTreeToggle<CR>", opt) -- Open & close tree

添加顶部标签页:bufferline#

除了掘金小册里提供的美化配置,我还改了一下关闭图标:buffer_close_icon = '✕'。快捷键设置:

…
-- bufferline
map("n", "<C-t>", ":enew<CR>", opt) -- Create new buffer
map("n", "<C-h>", ":BufferLineCyclePrev<CR>", opt)
map("n", "<C-l>", ":BufferLineCycleNext<CR>", opt)
map("n", "<C-w>", ":Bdelete<CR>", opt)  --Close Tabs by vim-bbye

添加底部状态栏:lualine#

美化配置可以参考掘金小册里提供的。

模糊搜索:telescope#

注意要安装依赖:

brew install rg
brew install fd

快速注释代码:Comment#

plugin-config/comment.lua 中,我这样设置了快捷键:

comment.setup({
  toggler = {
    line = "<C-/>",
  },
  opleader = {
    line = "<C-/>",
  },
})

注意:

  • neovim 将 Ctrl + / 识别为 <C-/> 还是 <C-_> 要根据具体的系统环境来决定。

自定义启动画面:dashboard#

我使用的是 hyper 主题,plugin-config/dashboard.lua 中的配置如下:

db.setup({
  theme = "hyper",
  config = {
    header = {
      [[]],
      [[ Customed something ]],
      [[]],
    },
    project = { enable = false, },
    mru = { limit = 13 },
    shortcut = {
      {
        icon = " ",
        desc = "Projects",
        action = "Telescope projects",
        key = "p",
      },
      {
        icon = "󰊳 ",
        desc = "Update",
        action = "Lazy update",
        key = "u"
      },
      {
        icon = "󰱽 ",
        desc = "Files",
        action = "Telescope find_files",
        key = "f",
      },
    },
    footer = {
      "Customed something"
    },
  },
})

自动补全及 LSP#

这一部分的配置略有些复杂,可以交叉参考 掘金小册从零开始配置 Neovim(Nvim)(nvim-cmp 以及 nvim-lspconfig 的官方文档看起来不是很友善)。当然,最终配置还是可以参考我的 github 仓库(里面可能有一些小小的修改)。

自动补全:nvim-cmp#

请参考官方文档。安装 cmp 时还需要安装 Snippet 引擎:LuaSnip

自动格式化代码:formatter#

请参考官方文档。我的配置如下:

local status, formatter = pcall(require, "formatter")
if not status then
  vim.notify("formatter not found")
  return
end

formatter.setup {
  filetype = {
    sh = require("formatter.filetypes.sh").shfmt,
    python = require("formatter.filetypes.python").black,
    rust = require("formatter.filetypes.rust").rustfmt,
  },
}

-- Keymaps
local map = vim.keymap.set
local opts = { noremap = true, silent = true }

map("n", "<A-F>", ":Format<CR>", opts)

设置好后,可以用 Mason 来安装对应的 formatter(在菜单 5 中可以找到)。像 rustfmt 这种安装 rust 时就已经装好的则不需要再次安装。

LSP 管理:mason & mason-lspconfig#

请参考官方文档。

LSP 配置:nvim-lspconfig#

请参考官方文档。(比较麻烦!)

LSP UI 配置#