Vim & Neovim

Quick Start

Intro to Vim

Vim is a highly configurable text editor built to make creating and changing any kind of text very efficient.

作为一款 Modal Editor,Vim 在不同模式下具有不同的目的和操作。

The default mode for Vim is normal mode, which is a command mode in which you can type execute commands (like deleting text, moving the cursor, etc).

Since the above commands can be triggered by pressing keys on the keyboard, there is no need to enter a colon or other special characters. Therefore, normal mode could be seen as a command mode for executing various editing commands.

Once in insert mode, typing inserts characters just like a regular text editor. You could enter this mode by pressing the i key (to insert text before the cursor), the a key (to insert text after the cursor), or some other command.

Visual mode is used to highlight selected text. You could use visual mode to select text and perform operations such as copy, delete, and paste.

Command mode has a wide variety of commands and can do things that normal mode can’t do as easily. To enter command mode type ":" from normal mode and then type your command which should appear at the bottom of the window.

有从 ThePrimeagen 的 "Vim As Your Editor" 系列中收获到了一些帮助记忆 Vim 的概念:Command Count Motion、Horizontal Movements 和 Vertical Movements。

Like many vim commands, motion can be prefixed by a number to move several steps at a time:

  • j|k means move one row down|up => 6k means move 6 rows up
  • h|l means move one character left|right => 5l means move 5 characters right
  • w|b means hop over|back by word => 4w means hop over 4 words
  • e means move to end of word => 3e means move to end of 3 words
  • 0|$ move to the beginning|end of the line => 2$ means move to the end of the next line

"Command" in "Command Count Motion" means d, c, v, y etc operation commands.

  • d3w means delete 3 words from the current cursor
  • c2$ means to modify all characters from the current cursor to the end of the next line
  • v means to enter the visual mode, appending count and motion can set the highlighted area
  • y0 yanks from cursor position to the beginning of the line, y_ yanks the entire current line

注意:当执行删除、剪切或修改命令时,Vim 默认会将删除的文本存储到 "0 寄存器。寄存器 "+ 表示系统剪贴板寄存器。通过 "+y 可以将选中的文本复制到系统剪贴板,而 "+p 则是将系统剪贴板中的文本粘贴到 Vim。命令 :reg 可以查看所有的寄存器及其内容。

In Vim, g is not a single operation, but a command prefix. The g command prefix can be combined with other commands to achieve various operations. For example:

  • gj: move down one screen, but only count one line
  • gk: move up one screen, but only count one line
  • gg: Move to the first line of the document
  • G: Move to the last line of the document
  • g_: Move to the last non-blank character of the current line
  • gm: Move to the first non-blank character of the middle line of the document

键入 forward slash / 可进入 search 模式。在输入完搜索内容后通过 return or enter 确定,向下切换可以使用 n

除上述外,再补充一些 Vim 中常用的键盘命令:

  • x means delete the character at the current cursor position
  • :%y+ could copy the entire file to the system clipboard command
  • f is used to search forward for the specified character in the current line (eg: f{)
  • zz means to center this line, usually with Ctrl + u|d to scroll up|down half a page

A Vim Session can open multiple tabs, and each tab can contain multiple windows, but each window can only display one buffer. A buffer usually refers to the contents of a file. - ChatGPT

Vim Session 是指编辑器的工作区域,其通常会包含有打开的文件、窗口的布局以及命令历史等信息:

  • 通过 :mksession 可以创建一个 Vim session,并将当前编辑器的状态保存到一个文件中
  • 使用 vim -S 可以打开一个保存了 session 信息的文件,以恢复之前的编辑器状态

A buffer is an area of Vim's memory used to hold text read from a file. In addition, an empty buffer with no associated file can be created to allow the entry of text. – vim.wikia

在 Vim 中,buffer 实际上并不是文件本身,而是文件内容的内存副本。在打开文件时,Vim 会将文件的内容加载进一个 buffer,再开始相关的编辑和修改工作。在进行编辑和修改时,buffer 中的内容是实时更新的。而保存文件后,Vim 会将 buffer 里的内容写回到文件中,以更新文件的实际内容。

Common commands for Vim session, tab, window and buffer are as follows:

  • :e 将文件加载进 buffer
  • :w 将 buffer 内容写入文件
  • :q 关闭 buffer
  • :ls 列出所有的 buffer
  • :vsplit 在当前 window 的侧边打开一个新的 window
  • :bnext | :bprev 切换到下一个或前一个 buffer
  • :tabnext | :tabprev 在不同的 tab 之间进行切换

在 Vim 中可以通过可视块模式 visual block mode 对块文本执行特定的操作:

逐行操作:键入 Ctrl + v 激活可视块模式,使用方向键选择好需要操作的行与列。如果需要逐行插入或删除文本,可以使用 I 进入可视块中的插入模式,或按 A 进入追加模式。在执行这项操作后,默认会取消高亮的块区域并将光标带到顶行。这里注意,在光标所处的顶行执行想要的批量操作,然后按退出键。最后这项操作将反映在之前可视块区所选择的的每一行中。

数字递增:以 1 到 100 为例:插入模式下在第一行输入数字 1,然后按 Enter 将光标移动到下一行后退出插入模式。在第二行键入 y1j 再键入 100p 即可在第三行及其后生成 100 行 1。在第四行(从第三行开始的下一个 1)使用 Ctrl + v 进入可视块模式后输入 99j 即可选择后续的 99 个 1。按下 gCtrl + a 可对后续的 1 进行递增。至此完成从 1-100 递增。

代码块缩进:用 Ctrl + v 与方向键选择好需要缩进的块区域,再通过 Shift + ><< 进行缩进。注意,上述的操作只会缩进一次。如果需要连续缩进,可以按下 . 键来重复上一次的操作。

Vim 在保存未命名的文件时会出现 "E32:No file name" 的提示,可以使用 :file filename 命令来设置当前文件的文件名。

在设置 Vim 主题时,可以通过 cterm 和 gui 选项来分别指定在终端和图形用户界面下的颜色方案:

  • cterm 即 Console Terminal,通常在命令行或终端窗口中使用 Vim 时生效
  • gui 即 Graphical User Interface,通常在使用 GVim 或 MacVim 等 GUI 版本的 Vim 时生效
if has("gui_running")
  " 设置图形用户界面下的颜色
  set background=dark
  colorscheme solarized
else
  " 设置终端下的颜色
  set t_Co=256
  colorscheme desert256
endif

Neovim Tips

Nerd Fonts patches developer targeted fonts with a high number of glyphs (icons). Specifically to add a high number of extra glyphs from popular ‘iconic fonts’ such as Font Awesome, Devicons, Octicons, and others.

通常终端的字符集只会包含一组基本的字符,对于一些图标和特殊符号的处理并不能够满足指定的需求,为了有效解决终端图标乱码的问题,需要使用 Nerd Fonts 这款开源字体集合(对终端设置)。

brew tap homebrew/cask-fonts && brew install --cask font-hack-nerd-font

Test whether the terminal font is set successfully => Click Here.

Mac fonts are stored in ~/Library/Fonts/ by default.

Neovim 的配置文件是用户 Home 目录中的 ~/.config/nvim/init.luainit.vim 文件。

nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) sets a global mapping for the given mode.

Neovim 中可通过 nvim_set_keymap()vim.api.nvim_set_keymap() 来设置快捷键:

  • nvim_set_keymap() 是 Neovim 自带的 API 函数,可以直接在 init.vim 或 init.lua 中使用
  • vim.api.nvim_set_keymap() 是通过 Vim 中 Lua API 调用的函数,需要在 Lua 脚本中使用
-- 左右Tab切换
vim.api.nvim_set_keymap("n", "<C-h>", ":BufferLineCyclePrev<CR>", {noremap = true, silent = true })
vim.api.nvim_set_keymap("n", "<C-l>", ":BufferLineCycleNext<CR>", {noremap = true, silent = true })

上述 Quote 中 mode 表示映射模式,即普通、可视、插入模式等;lhs 表示要映射的键位序列,常有特殊、控制、功能键位等;rhs 表示要执行的操作,如命令、Lua 函数等;opts 是一个选项表,可以指定为 nowait、silent、expr 等一个或者多个。

-- 将 <Leader>q 映射为关闭当前窗口
vim.keymap.set("", "<Leader>q", "<C-w>c", { nowait = true })
-- 将 <Leader>n 映射为跳转到下一个 QuickFix 错误
vim.keymap.set("", "<Leader>n", ":cnext<CR>", { silent = true })
-- 将 <Leader>e 映射为在侧边栏打开文件资源管理器
vim.keymap.set("", "<Leader>e", function()
  vim.cmd("NvimTreeToggle")
end)

注意,Neovim 会暴露一个全局的 vim 模块来作为 Lua 调用 Vim 中 APIs 的入口。

可通过 :echo mapleader 查看 <leader> 的映射,默认情况下 <leader> 的映射为反斜杠 \

在 Neovim 中输出某个设置属性的具体值,可以使用 :echo &option_name 命令,&option_name 为设置属性的名称。符号 & 通常用于引用某个选项的值。当使用 &option_name 语法时,Vim 和 Neovim 会将 &option_name 解释为相应选项的名称,并将其替换为该选项的当前值。如果想查看所有可用的选项及其当前值,可以执行 :options 命令。

需要注意的是,并非所有的选项都可以通过上述语法来获取相应的值。通常来说,只有那些被显式地设置为选项的变量才可以使用,其他变量的值可能需要使用一些额外的语法或函数来获取。举个例子,对于 vim.gvim.keymap,使用 echo &... 输出是无效的,因为它们都不是 Vim 的选项。

:echo g:my_setting # 输出 vim.g 中的 my_setting 变量的值

回车 Carriage Return 常缩写为 CR,即对应 Vim 中的 <CR><C-a><C-n> 分别表示 Ctrl+aCtrl+n 键。

Neovim Config From Scratch

Plugin Manager for Neovim

目前 Neovim 中常见的插件管理器有 vim-plug、packer.nvim 和 LazyVim:

  • vim-plug 使用简单,且同时支持 Vim 和 Neovim,所有功能在一个 Vim 脚本中实现
  • Packer.nvim 支持插件间的依赖,通过 lua 编写,专门针对 Neovim v0.5.0 以上版本使用
  • LazyVim 利用缓存提升速度,在轻量的同时有对 Vim 和 Neovim 不同版本提供了兼容性支持

Plugins In Neovim

LSP 是一个由 Microsoft 提出的开放式协议,用于在编辑器、IDE 和语言服务器之间进行通信。在 LSP 之前,不同的工具都需要独立实现各自的代码功能。这意味着同一门编程语言在各种工具之间可能会存在不同的效果,且不能够很好的兼容。那么对于同一种编程语言,如果让各种工具都可以共享一个语言服务器,那么就可以实现代码补全、语法检查、悬停提示等功能的一致性。

Language Client 和 Language Server 是 LSP 协议中的两个重要角色:

  • Language Server 作为服务提供者,提供与特定编程语言相关的语言分析和操作服务
  • Language Client 通过实现 LSP 协议,可以向语言服务器发送请求,接收并显示分析的结果

Nvim supports the Language Server Protocol (LSP), which means it acts as a client to LSP servers and includes a Lua framework vim.lsp for building enhanced LSP tools.

Neovim 本身并不是一个 Language Client,尽管其内置了一套 LSP 的实现。因此,若想在 Neovim 中使用 LSP,仍然需要安装一个 LSP 客户端插件来提供 Language Client 的功能。在 Neovim 中,可以通过 :h lsp:h vim.lsp 查看相关文档。本文配置 LSP 使用 nvim-lspconfig 与 mason.nvim

jdtls 全称是 Eclipse JDT Language Server,这是一个基于 Eclipse JDT(Java Development Tools)构建的 LSP(Language Server Protocol)实现,用于提供对 Java 语言的代码分析和操作支持,可以在各种编辑器和 IDE 中使用,如 Eclipse、Visual Studio Code、Sublime Text 等。

The Transparency Scheme

vim ~/.vim_runtime/sources_non_forked/vim-colors-solarized/colors/solarized.vim
" Customize and Modify color
" let s:base01      = "10"
let s:base01      = "7"
" let s:base00      = "11"
let s:base00      = "7"

使用 amix/vimrc 后,额外的配置应添加在 ~/.vim_runtime/my_configs.vim 文件。

" --- Custom config ---
" 关闭与 Vi 兼容的模式
set nocompatible
" 设置文本编码为 UTF-8
set encoding=utf-8
" 显示行号,同时也显示相对行号
set nu
set relativenumber
" 设置文件名自动补全模式
set wildmode=longest,list,full
" 开启命令行自动补全菜单
set wildmenu
" 开启鼠标支持,可以使用鼠标操作 Vim
set mouse=a

" 定义一个函数 SetTabIndent() 用于设置缩进
function SetTabIndent()
  " 根据文件类型设置缩进
  if &ft == "javascript" || &ft == "typescript" || &ft == "json" || &ft == "vue" || &ft == "jsx" || &ft == "lua" || &ft == "yaml" || &ft == "css" || &ft == "scss" || &ft == "css" || &ft == "xml" || &ft == "vim"
    setlocal tabstop=2 softtabstop=2 shiftwidth=2 expandtab
  elseif &ft == "java" || &ft == "python"
    setlocal tabstop=4 softtabstop=4 shiftwidth=4 expandtab
  else
    setlocal tabstop=8 softtabstop=0 shiftwidth=8 noexpandtab
  endif
endfunction

" 在 BufRead 和 BufNewFile 事件中调用 SetTabIndent() 函数
augroup TabIndent
  autocmd!
  autocmd BufRead,BufNewFile * call SetTabIndent()
augroup END

syntax enable " 开启语法高亮

" 当前使用 amix/vimrc,将 vim 打开的文件背景设置为与 iterm2 一致(transparency 25)
if has("gui_running")
  " GUI 相关设置
  " etc...
else
  " 终端相关设置
  set t_Co=256
  " 判断当前终端的类型是否包含字符串"256color"
  if &term =~ '256color'
    " 设置终端的背景色和前景色
    " iTerm2 等终端的透明度在终端外面设置即可
    " https://stackoverflow.com/a/34926660/10538725
    set background=dark
    " 加载颜色主题
    colorscheme solarized
    " 设置背景透明度 => 应确保在设置之前有加载颜色主题
    hi Normal ctermbg=none
    " 设置 Pmenu 的透明度
    hi Pmenu ctermbg=none
    hi PmenuSel ctermbg=none
    " 竖线用空格填充
    set fillchars+=vert:\
    set linespace=0
    " 自动补全透明度
    set pumblend=20
    " 窗口透明度
    set winblend=20
  endif
endif

在 nvim 的配置文件 $HOME/.config/nvim/init.lua 中,添加以下代码。

-- 使用 nvim-lua/kickstart,将通过 nvim 打开的文件背景设置为与 iterm2 一致
vim.o.background = 'dark' -- 设置为 dark 背景
vim.o.termguicolors = true -- 启用 24 位色彩
vim.cmd('au VimEnter * hi Normal guibg=none ctermbg=none') -- 设置 Normal 背景为透明
vim.cmd('au VimEnter * hi NonText guibg=none ctermbg=none') -- 设置 NonText 背景为透明
vim.cmd('au VimEnter * hi SignColumn guibg=none ctermbg=none') -- 设置 SignColumn 背景为透明
vim.cmd('au VimEnter * hi LineNr guibg=none ctermbg=none') -- 设置 LineNr 背景为透明
vim.cmd('au VimEnter * hi EndOfBuffer guibg=none ctermbg=none') -- 设置 EndOfBuffer 背景为透明
vim.cmd('au VimEnter * hi VertSplit guifg=NONE guibg=NONE') -- 设置分割线背景为透明
vim.cmd('au VimEnter * hi Folded guibg=none ctermbg=none') -- 设置 Folded 背景为透明

NeoTree 默认使用 netrw.vim 插件来管理文件树。NeoTree 是一个类似于 netrw 的文件浏览器插件。

nvim-neo-tree 配置文件 ~/.config/nvim/lua/custom/plugins/filetree.lua 如下。

-- Unless you are still migrating, remove the deprecated commands from v1.x
vim.cmd([[ let g:neo_tree_remove_legacy_commands = 1 ]])

-- Realize neotree transparency
-- https://www.reddit.com/r/neovim/comments/10vrw9s/making_neotree_transparent_in_lazyvim/
vim.api.nvim_create_augroup("nobg", { clear = true })
vim.api.nvim_create_autocmd({ "ColorScheme" }, {
  desc = "Make all backgrounds transparent",
  group = "nobg",
  pattern = "*",
  callback = function()
    -- 函数 vim.api.nvim_set_hl() 用于设置 Vim 的高亮组
    vim.api.nvim_set_hl(0, "Normal", { bg = "NONE", ctermbg = "NONE" })
    vim.api.nvim_set_hl(0, "NeoTreeNormal", { bg = "NONE", ctermbg = "NONE" })
    vim.api.nvim_set_hl(0, "NeoTreeNormalNC", { bg = "NONE", ctermbg = "NONE" })
    vim.api.nvim_set_hl(0, "NeoTreeEndOfBuffer", { bg = "NONE", ctermbg = "NONE" })
    -- etc...
  end,
})

return {
  "nvim-neo-tree/neo-tree.nvim",
  version = "*",
  dependencies = {
    "nvim-lua/plenary.nvim",
    "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
    "MunifTanjim/nui.nvim",
  },
  config = function ()
    -- If you want icons for diagnostic errors, you'll need to define them somewhere:
      vim.fn.sign_define("DiagnosticSignError",
        {text = " ", texthl = "DiagnosticSignError"})
      vim.fn.sign_define("DiagnosticSignWarn",
        {text = " ", texthl = "DiagnosticSignWarn"})
      vim.fn.sign_define("DiagnosticSignInfo",
        {text = " ", texthl = "DiagnosticSignInfo"})
      vim.fn.sign_define("DiagnosticSignHint",
        {text = "", texthl = "DiagnosticSignHint"})
      -- NOTE: this is changed from v1.x, which used the old style of highlight groups
      -- in the form "LspDiagnosticsSignWarning"
    require('neo-tree').setup {
      popup_border_style = "rounded",
      enable_git_status = true,
      enable_diagnostics = true,
      open_files_do_not_replace_types = { "terminal", "trouble", "qf" }, -- when opening files, do not use windows containing these filetypes or buftypes
      sort_case_insensitive = false, -- used when sorting files and directories in the tree
      sort_function = nil , -- use a custom function for sorting files and directories in the tree
      default_component_configs = {
          container = {
            enable_character_fade = true
          },
          indent = {
            indent_size = 2,
            padding = 1, -- extra padding on left hand side
            -- indent guides
            with_markers = true,
            indent_marker = "│",
            last_indent_marker = "└",
            highlight = "NeoTreeIndentMarker",
            -- expander config, needed for nesting files
            with_expanders = nil, -- if nil and file nesting is enabled, will enable expanders
            expander_collapsed = "",
            expander_expanded = "",
            expander_highlight = "NeoTreeExpander",
          },
          icon = {
            folder_closed = "",
            folder_open = "",
            folder_empty = "ﰊ",
            -- The next two settings are only a fallback, if you use nvim-web-devicons and configure default icons there
            -- then these will never be used.
            default = "*",
            highlight = "NeoTreeFileIcon"
          },
          modified = {
            symbol = "[+]",
            highlight = "NeoTreeModified",
          },
          name = {
            trailing_slash = false,
            use_git_status_colors = true,
            highlight = "NeoTreeFileName",
          },
          git_status = {
            symbols = {
              -- Change type
              added     = "", -- or "✚", but this is redundant info if you use git_status_colors on the name
              modified  = "", -- or "", but this is redundant info if you use git_status_colors on the name
              deleted   = "✖",-- this can only be used in the git_status source
              renamed   = "",-- this can only be used in the git_status source
              -- Status type
              untracked = "",
              ignored   = "",
              unstaged  = "",
              staged    = "",
              conflict  = "",
            }
          },
        },
      -- neo-tree.nvim/lua/neo-tree/defaults.lua
      -- https://github.com/nvim-neo-tree/neo-tree.nvim/issues/446
      filesystem = {
        filtered_items = {
          visible = true, -- default false 显示隐藏文件
        }
      },
    }
    vim.cmd([[nnoremap \ :Neotree toggle<cr>]])
  end,
}

NeoTree.nvim 中的高亮组 NeoTreeEndOfBuffer、NeoTreeNormalNC、NeoTreeNormal、Normal:

  • NeoTreeEndOfBuffer 表示文件树窗口底部以下的区域,该区域通常会有提示信息
  • NeoTreeNormalNC 表示文件树中没有被选中的文件或目录的名称的颜色,NC 即 No Cursor
  • NeoTreeNormal 表示文件树中被选中的文件或目录的名称的颜色
  • Normal 是 Vim 的一个默认高亮组,表示在普通模式下文本的颜色

这里选择 bufferline.nvim 给 Neovim 增加顶部标签页插件,该插件相应配置如下。

-- bufferline
-- 左右 Tab 切换
vim.api.nvim_set_keymap("n", "<C-h>", ":BufferLineCyclePrev<CR>", {noremap = true, silent = true })
vim.api.nvim_set_keymap("n", "<C-l>", ":BufferLineCycleNext<CR>", {noremap = true, silent = true })
return {
  "akinsho/bufferline.nvim",
  version = "*",
  dependencies = {
    "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended
  },
  config = function ()
    require("bufferline").setup{}
  end,
}

以上就是在配置 Neovim 时所需要注意的一些内容,文末附一张本人的配置效果。撒花 🎉。

Bug Fixes

Exception: jdtls requires at least Java 17

这个异常信息意味着 jdtls 需要至少 Java 17 的版本才能正常运行,当前系统中安装的 Java 版本低于这个要求,需要升级 Java 版本至 17 或更高。

Learn Neovim The Practical Way
All articles on how to configure and program Neovim.
https://alpha2phi.medium.com/learn-neovim-the-practical-way-8818fcf4830f
Vim cheatsheet
One-page guide to Vim: usage, examples, and more. Vim is a very efficient text editor. This reference was made for Vim 8.0. For shortcut notation, see :help key-notation.
https://devhints.io/vim
GitHub - glepnir/nvim-lua-guide-zh: https://github.com/nanotee/nvim-lua-guide chinese version
https://github.com/nanotee/nvim-lua-guide chinese version - GitHub - glepnir/nvim-lua-guide-zh: https://github.com/nanotee/nvim-lua-guide chinese version
https://github.com/glepnir/nvim-lua-guide-zh
Lua - Neovim docs
Neovim user documentation
https://neovim.io/doc/user/lua.html

结束

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!