Vim 快捷键可以看:Vim 常用命令总结
什么是 Vim?
- Vim(Vi Improved)是 Bram Moolenaar 于 1991 年发布的文本编辑器,是经典 Unix 编辑器 Vi 的改进版。简单的来说, Vi 是老式的文字处理器,不过功能已经很齐全了,但是还是有可以进步的地方。 Vim 则可以说是程序开发者的一项很好用的工具。
- 核心特征:模式编辑(普通模式、插入模式、可视模式、命令模式),这是它和现代编辑器最大的区别。
- 跨平台:Linux/macOS 默认预装,Windows 也可用,任何有终端的场合都能用。
- 不需要鼠标:所有操作完全基于键盘。
为什么选择 Vim?
- SSH 场景不可替代:你不可能在生产服务器上装 VSCode 和其他有图形界面的编辑器,但 Vim 一定在。
- 零依赖:不需要图形界面、不需要 X11、不需要浏览器引擎,一个终端即可。
- 肌肉记忆:一旦熟练,这些Vim的操作会变成手指的本能反应,不挑编辑器——VS Code、IntelliJ、JetBrains 全系都有 Vim 插件。
- 轻量是种哲学:从按下回车到开始打字,Vim 已经干完活了,Electron 编辑器可能还在初始化。
为什么不装插件?
不装插件是为了保持配置的可移植性和零心智负担。
在服务器上、Docker 容器里、甚至别人的机器上,vim 一定在,vim-plug 和 lazy.nvim 不一定在。
我用Vim就是想要一个任何环境里打开就能用的编辑器。
Neovim
对于本地开发项目,Neovim 毫无疑问是更好的选择。
Neovim 的 Lua 配置比 Vim 优雅得多,内置 LSP + Tree-sitter 能提供接近 VSCode 的补全和诊断体验,异步支持也让插件不再卡界面。
其实初学更推荐用 Neovim+LazyVim,但本文意不在此。我只是想尝试不借助插件,完全通过 set 选项和 map 绑键调教出一个顺手的编辑器,这本身就是理解 Vim 设计哲学的最好方式。
本文配置效果
根据本文对Vim进行配置后,你会得到
以下均为对 ~/.vimrc 的操作。
基本配置
关闭兼容模式
set nocompatible这告诉Vim不要再假装自己的前身Vi了。
Vim 启动时默认是 compatible(兼容模式),此时 Vim 的行为会尽量模仿原始的 Vi 编辑器,以保证老脚本或习惯 Vi 的用户能够正常使用。但这样会牺牲很多 Vim 的特色功能,比如语法高亮、可视模式、自动缩进、命令行历史等。
设置 nocompatible 后的效果:
-
开启文件类型检测、语法高亮等现代编辑器特性
-
允许使用 Vim 独有的选项和映射(如 set backspace=indent,eol,start 等)
-
使
等命令更友好 -
通常这也是很多其他高级配置(如插件系统)生效的前提
实际上,只要你有一个 ~/.vimrc 文件(即使为空),Vim 就会自动设置 nocompatible。但为了明确意图和避免依赖隐式行为,绝大多数配置都会显式地写上这一行,放在 .vimrc 的最开头。
开启语法高亮和插件
syntax onset termguicolorsfiletype plugin indent onsyntax on可以开启语法高亮set termguicolors启用 Vim 在终端中的真彩色(24 位颜色)支持filetype plugin indent on开启插件,不过这是Vim自带的
显示行号和相对行号
set number relativenumber开启鼠标支持
set mouse=a调整 Tab
set expandtabset autoindent smartindent绑键
Vim 允许绑定按键,比如在插入模式中(insert),使用 jk 退出插入模式
imap jk <Esc>外观
set background=dark " 使用暗色背景,适配颜色方案的高亮设计colorscheme slate " slate 颜色方案:白/青字+深灰底,高对比度暗色主题
set cursorline " 高亮光标所在的整行,便于快速定位highlight CursorLine cterm=NONE ctermbg=DarkGrey guibg=#444444 guifg=NONEVim 自带了多种好看的主题,我自己用的是 slate主题+灰色高亮当前行。
底部栏
set laststatus=2 " 始终显示状态栏(2=总是, 1=仅多窗口, 0=从不)set statusline= " 清空状态栏,从头重新构建set statusline+=%#PmenuSel# " 模式指示区域使用弹出菜单的高亮色(更醒目)set statusline+=\ %{mode()} " 显示当前模式名:NORMAL / INSERT / VISUAL / REPLACEset statusline+=%#StatusLine# " 切回正常状态栏颜色set statusline+=\ %<%f\ " 显示文件路径(相对路径),过长时左截断set statusline+=%h%m%r%w " 标志位:[Help] 帮助文件 [Modified]+已修改 [ReadOnly]只读 [Preview]预览set statusline+=%= " 分隔符:之后的内容右对齐set statusline+=%y\ " 文件类型(如 python, rust, vim, sh)set statusline+=%{&fileencoding?&fileencoding:&encoding}\ " 当前编码(如 utf-8),如果未设则用 Vim 内部编码set statusline+=\| " 视觉分隔符 |set statusline+=%-10.(%l,%c%)\ " 行号,列号(左对齐,最小占 10 字符)set statusline+=%4P\ " 当前行在文件中的百分比位置(如 Top / Bot / 50%)文件查找
set path+=**set wildmenu-
set path+=**双星号会告诉Vim,查找文件时,递归搜索子目录 此时,你可以使用:find来模糊查找文件。 -
set wildmenu增强 Vim 命令行中的补全体验此时,使用
:find *.xx时,你可以使用 Tab 挑选匹配到的文件。你也可以使用
set wildmode来设置补全模式:set wildmode=longest:full,full-
第一次按 Tab:
- 最长公共前缀(longest)。
- 如果只有一个匹配项,直接完整补全。
- 如果有多个匹配,则把输入扩展到它们的共同前缀部分,但不弹出完整菜单。
-
第二次按 Tab:
切换到 full 模式,循环显示完整的匹配项(逐个替换命令行内容,而不是仅补全公共前缀)。
-
第三次及以后按 Tab:
继续 full 模式(因为逗号后的第二个 full 指定了后续所有 Tab 的行为),在完整匹配项中继续循环切换。
但仅仅是默认激活
wildmenu,也可以获得非常好的体验。 -
还有一点需要考虑,如果你在Vim中打开过一个文件,此时输入 :ls,他会给你一个Vim称之为缓冲区的列表。这些缓冲区基本上就是它保存在内存中的文件。
如果你打开过多个文件,当你输入 :ls,Vim会列出这些文件,然后你可以通过buffer命令,输入 :b [之前使用ls命令列出的编号] 来跳转到对应文件,或者直接在 :b 后面输入部分文件名,通过 Tab 来选择匹配到子字符串的文件。
使用 :bd 1 2 3 可以关闭编号为1、2、3的buffer。
所以,熟练使用find和buffer命令,你可以在不需要任何其他依赖项的情况下完成大量的导航操作。
当然,你也可以绑键来快速切换buffer。
" 模糊查找文件名(配合 path+=** 遍历子目录)nnoremap <leader>f :find<Space>" 浏览最近打开过的文件列表(:oldfiles)nnoremap <leader>r :browse oldfiles<CR>" ========================================" Buffer / 窗口管理(多文件编辑)" ========================================set hidden " 允许修改未保存时切换 buffer,内容保留在内存中不会丢失nnoremap <leader>q :bp\|bd #<CR>nnoremap <leader>bn :bnext<CR>nnoremap <leader>bp :bprevious<CR>nnoremap <leader>bl :ls<CR>:b<Space>分屏
Vim自带非常方便的分屏功能
-
水平分屏
:split或:sp,快捷键Ctrl-w s -
垂直分屏
:vsplit或:vsp,快捷键Ctrl-w v -
切换窗口
Ctrl-w w或Ctrl-w h/j/k/l -
调整大小
Ctrl-w +/–用于增减高度,Ctrl-w >/<用于增减宽度Ctrl-w =可以等分窗口 -
仅保留当前页面:
Ctrl-w o
标签页
如果你有同时编辑多个文档的需求,设置标签页(顶部标签行能够让你更快速地进行切换。
在netrw中对文件按t即可创建新标签页。
" ========================================" 标签页栏(顶部标签行)" ========================================set showtabline=2 " 始终显示标签页栏(2=总是, 1=仅多标签页, 0=从不)set tabline=%!MyTabLine() " 使用自定义函数 MyTabLine() 渲染标签页栏function! MyTabLine() let s = '' for i in range(tabpagenr('$')) " 遍历所有标签页 let buflist = tabpagebuflist(i + 1) " 获取该标签页的 buffer 列表 let winnr = tabpagewinnr(i + 1) " 该标签页当前激活的窗口编号 let bufname = bufname(buflist[winnr - 1]) " 当前窗口对应的文件名(含路径) let bufname = fnamemodify(bufname, ':t') " 仅保留文件名,去掉目录路径 if bufname == '' " 若为未命名 buffer(如新建文件尚未保存) let bufname = '[No Name]' endif let s .= '%' . (i + 1) . 'T' " 标签页跳转标记(点击时切换到对应标签页) let s .= (i + 1 == tabpagenr() ? '%#TabLineSel#' : '%#TabLine#') " 当前标签页使用高亮色 let s .= ' ' . (i + 1) . ': ' . bufname . ' ' " 格式:编号: 文件名 endfor let s .= '%#TabLineFill#%T' " 填充右侧空白区域 return sendfunction绑键方便切换
" Tab 切换到下一个标签页nnoremap <Tab> gt" Shift+Tab 切换到上一个标签页nnoremap <S-Tab> gT使用数字+gt即可切换到对应标签页。
标签跳转
这需要一个额外的插件,叫作ctags
sudo pacman -S ctags你只需要安装他,并在Vim中配置。
set tags=./tags;,tags在你的项目中使用
ctags -R .它会自动分析你的项目,然后你就可以随意跳转标签了。
- 使用
^]跳转到定义 - 如果有多个定义,可以使用
g^] - 使用
^t返回
自动补全
内置补全
Vim自带,可以稍微配置一下:
set completeopt=menuone,noinsert,noselect " menuone=单个结果也弹菜单, noinsert=不自动插入, noselect=不高亮首项set pumheight=12 " 补全弹出菜单最多显示 12 行set shortmess+=c " 抑制补全中的 "match X of Y" 消息,减少闪烁set dictionary+=/usr/share/dict/words
set complete=.,w,b,u,t,k " 明确指定补全源(去掉无效的 spell)iset infercase " 补全时智能匹配大小写插入模式下:
- ^x^n 根据当前文件补全
- ^x^f 补全文件名
- ^x^] 只补全标签
- ^n ^p 选择补全选项
括号、引号补全
inoremap ( ()<left>inoremap { {}<left>inoremap [ []<left>可以设置有右括号的情况下跳过
" ========================================" 括号、引号自动配对" ========================================" 输入左括号/引号时自动补全右侧符号,光标留在中间inoremap ( ()<left>inoremap { {}<left>inoremap [ []<left>inoremap <expr> ) strpart(getline('.'), col('.')-1, 1) == ')' ? '<Right>' : ')'inoremap <expr> } strpart(getline('.'), col('.')-1, 1) == '}' ? '<Right>' : '}'inoremap <expr> ] strpart(getline('.'), col('.')-1, 1) == ']' ? '<Right>' : ']'
inoremap <expr> " strpart(getline('.'), col('.')-1, 1) == '"' ? '<Right>' : '""<left>'inoremap <expr> ' strpart(getline('.'), col('.')-1, 1) == "'" ? '<Right>' : "''<left>"文件浏览
文件浏览器
Vim 自带的,勉强够用
" ========================================" netrw 文件浏览器" ========================================let g:netrw_liststyle = 3 " 树形显示(1=普通列表, 2=详细信息, 3=树形, 0=普通变体)let g:netrw_banner = 0 " 去掉顶部的帮助/路径信息横幅(清爽)let g:netrw_winsize = 25 " 当打开文件浏览器时,占用窗口宽度的 25%(其余留给编辑区)let g:netrw_browse_split = 4 " 打开文件时的默认行为:4 表示在先前使用的窗口中打开(避免多分屏)" let g:netrw_chgwin = 0 " 指定 netrw 窗口的编号(较少用到)let g:netrw_altv = 1 " 垂直分屏时新文件打开在 netrw 的右侧" 隐藏文件,gh切换let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'如果觉得不习惯,可以修改快捷键
" netrw 窗口内的自定义快捷键(仅在该 buffer 中生效)augroup custom_netrw_mappings autocmd! " l 键打开文件/文件夹(类似普通文件管理器) autocmd FileType netrw nmap <buffer> l <CR> autocmd FileType netrw nmap <buffer> a % autocmd FileType netrw nmap <buffer> A d autocmd FileType netrw nmap <buffer> r Raugroup END清除没用的缓存
augroup netrw_cleanup autocmd! autocmd WinClosed * if &filetype == 'netrw' | bwipeout! | endifaugroup END我的完整Vim配置
" ========================================" 基础兼容性" ========================================set nocompatible " 关闭 Vi 兼容模式,启用 Vim 全部功能(必须放第一行)
" ========================================" 语法与外观" ========================================syntax on " 启用语法高亮,根据文件类型着色关键字、字符串等set termguicolors " 启用终端真彩色(24-bit),需终端支持(如 kitty)filetype plugin indent on " 启用文件类型检测,加载对应 ftplugin 脚本set number relativenumber " 混合行号:当前行显示绝对行号,其余行显示到光标的相对距离set mouse=a " 在所有模式下启用鼠标:点击定位、滚轮滚动、可视化选择
" ========================================" 缩进与制表符" ========================================set expandtab tabstop=4 " 按 Tab 时插入空格而非制表符(便于团队统一缩进风格)set autoindent smartindent " autoindent=保持上一行缩进, smartindent=根据代码语法自动增减缩进set shiftwidth=4
" ========================================" 文件搜索与命令补全" ========================================set path+=** " 递归子目录加入搜索路径,:find 可在整个项目中找文件set wildmenu " 命令行 Tab 补全时弹出横向菜单,列出所有候选项set wildmode=longest:full,full " 第一次 Tab:补全到最长公共前缀并显示菜单;后续 Tab:轮流选择
" ========================================" 补全(插入模式下 Ctrl-x 子模式)" ========================================set completeopt=menuone,noinsert,noselect " menuone=单个结果也弹菜单, noinsert=不自动插入, noselect=不高亮首项set pumheight=12 " 补全弹出菜单最多显示 12 行set shortmess+=c " 抑制补全中的 "match X of Y" 消息,减少闪烁set dictionary+=/usr/share/dict/words,/home/laplacan/.vim/python-words" set wildignore+=*/venv/*
set complete=.,w,b,u,t,k " 明确指定补全源(去掉无效的 spell)去掉了i" Ctrl-x Ctrl-k 触发字典补全(Python 关键字/异常/内置函数等)set infercase " 补全时智能匹配大小写
" ========================================" 括号、引号自动配对" ========================================" 输入左括号/引号时自动补全右侧符号,光标留在中间inoremap ( ()<left>inoremap { {}<left>inoremap [ []<left>inoremap <expr> ) strpart(getline('.'), col('.')-1, 1) == ')' ? '<Right>' : ')'inoremap <expr> } strpart(getline('.'), col('.')-1, 1) == '}' ? '<Right>' : '}'inoremap <expr> ] strpart(getline('.'), col('.')-1, 1) == ']' ? '<Right>' : ']'
inoremap <expr> " strpart(getline('.'), col('.')-1, 1) == '"' ? '<Right>' : '""<left>'inoremap <expr> ' strpart(getline('.'), col('.')-1, 1) == "'" ? '<Right>' : "''<left>"
" ========================================" 映射 <Leader>cd 复制当前文件所在目录路径" ========================================" nnoremap <Leader>cd :let @+ = expand("%:p:h")<CR>:echo "Copied: " . expand("%:p:h")<CR>" 下面一行使用 wl-copy 命令直接写入 Wayland 剪贴板nnoremap <Leader>cd :let @a = expand("%:p:h") \| call system('wl-copy', @a)<CR>:echo "Copied: " . expand("%:p:h")<CR>
" ========================================" 搜索时忽略大小写并高亮" ========================================set ignorecase smartcase " ignorecase=默认忽略大小写, smartcase=搜索词含大写时自动区分set hlsearch incsearch " hlsearch=高亮所有匹配项, incsearch=逐字符实时跳转到首个匹配" F3 搜索光标下单词但停在原地(* 默认会跳到下一个匹配)nnoremap <F3> *N
" ========================================" 实时显示命令" ========================================set showcmd " 右下角显示正在输入的命令前缀
" ========================================" 显示模式(INSERT / VISUAL)" ========================================set showmode " 左下角显示当前模式(-- INSERT -- / -- VISUAL -- / -- REPLACE --)
" ========================================" 全局快捷映射" ========================================" 打开/切换左侧文件浏览器侧边栏nnoremap <leader>e :Lexplore<CR>" Tab 切换到下一个标签页nnoremap <Tab> gt" Shift+Tab 切换到上一个标签页nnoremap <S-Tab> gT" 模糊查找文件名(配合 path+=** 遍历子目录)nnoremap <leader>f :find<Space>" 浏览最近打开过的文件列表(:oldfiles)nnoremap <leader>r :browse oldfiles<CR>imap jk <Esc>
" ========================================" Buffer / 窗口管理(多文件编辑)" ========================================set hidden " 允许修改未保存时切换 buffer,内容保留在内存中不会丢失nnoremap <leader>q :bp\|bd #<CR>nnoremap <leader>bn :bnext<CR>nnoremap <leader>bp :bprevious<CR>nnoremap <leader>bl :ls<CR>:b<Space>
" ========================================" netrw 文件浏览器" ========================================let g:netrw_liststyle = 3 " 树形显示(1=普通列表, 2=详细信息, 3=树形, 0=普通变体)let g:netrw_banner = 0 " 去掉顶部的帮助/路径信息横幅(清爽)let g:netrw_winsize = 25 " 当打开文件浏览器时,占用窗口宽度的 25%(其余留给编辑区)let g:netrw_browse_split = 4 " 打开文件时的默认行为:4 表示在先前使用的窗口中打开(避免多分屏)" let g:netrw_chgwin = 0 " 指定 netrw 窗口的编号(较少用到)let g:netrw_altv = 1 " 垂直分屏时新文件打开在 netrw 的右侧" 隐藏文件,gh切换let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'
" netrw 窗口内的自定义快捷键(仅在该 buffer 中生效)augroup custom_netrw_mappings autocmd! " l 键打开文件/文件夹(类似普通文件管理器) autocmd FileType netrw nmap <buffer> l <CR> autocmd FileType netrw nmap <buffer> a % autocmd FileType netrw nmap <buffer> A d autocmd FileType netrw nmap <buffer> r Raugroup END
augroup netrw_cleanup autocmd! autocmd WinClosed * if &filetype == 'netrw' | bwipeout! | endifaugroup END
" ========================================" 外观主题" ========================================set background=dark " 使用暗色背景,适配颜色方案的高亮设计" colorscheme slate " slate 颜色方案:白/青字+深灰底,高对比度暗色主题colorscheme catppuccin " slate 颜色方案:白/青字+深灰底,高对比度暗色主题
" ========================================" 高亮当前行" ========================================set cursorline " 高亮光标所在的整行,便于快速定位highlight CursorLine cterm=NONE ctermbg=DarkGrey guibg=#444444 guifg=NONE
" ========================================" 状态栏(底部信息条)" ========================================set laststatus=2 " 始终显示状态栏(2=总是, 1=仅多窗口, 0=从不)set statusline= " 清空状态栏,从头重新构建set statusline+=%#PmenuSel# " 模式指示区域使用弹出菜单的高亮色(更醒目)set statusline+=\ %{mode()} " 显示当前模式名:NORMAL / INSERT / VISUAL / REPLACEset statusline+=%#StatusLine# " 切回正常状态栏颜色set statusline+=\ %<%f\ " 显示文件路径(相对路径),过长时左截断set statusline+=%h%m%r%w " 标志位:[Help] 帮助文件 [Modified]+已修改 [ReadOnly]只读 [Preview]预览set statusline+=%= " 分隔符:之后的内容右对齐set statusline+=%y\ " 文件类型(如 python, rust, vim, sh)set statusline+=%{&fileencoding?&fileencoding:&encoding}\ " 当前编码(如 utf-8),如果未设则用 Vim 内部编码set statusline+=\| " 视觉分隔符 |set statusline+=%-10.(%l,%c%)\ " 行号,列号(左对齐,最小占 10 字符)set statusline+=%4P\ " 当前行在文件中的百分比位置(如 Top / Bot / 50%)
" ========================================" 标签页栏(顶部标签行)" ========================================set showtabline=2 " 始终显示标签页栏(2=总是, 1=仅多标签页, 0=从不)set tabline=%!MyTabLine() " 使用自定义函数 MyTabLine() 渲染标签页栏function! MyTabLine() let s = '' for i in range(tabpagenr('$')) " 遍历所有标签页 let buflist = tabpagebuflist(i + 1) " 获取该标签页的 buffer 列表 let winnr = tabpagewinnr(i + 1) " 该标签页当前激活的窗口编号 let bufname = bufname(buflist[winnr - 1]) " 当前窗口对应的文件名(含路径) let bufname = fnamemodify(bufname, ':t') " 仅保留文件名,去掉目录路径 if bufname == '' " 若为未命名 buffer(如新建文件尚未保存) let bufname = '[No Name]' endif let s .= '%' . (i + 1) . 'T' " 标签页跳转标记(点击时切换到对应标签页) let s .= (i + 1 == tabpagenr() ? '%#TabLineSel#' : '%#TabLine#') " 当前标签页使用高亮色 let s .= ' ' . (i + 1) . ': ' . bufname . ' ' " 格式:编号: 文件名 endfor let s .= '%#TabLineFill#%T' " 填充右侧空白区域 return sendfunction
" ========================================" 滚动与响应" ========================================set scrolloff=8 " 光标距离屏幕顶部/底部至少保留 8 行再开始滚动(避免光标总在边缘)set sidescrolloff=8 " 横向滚动时,光标距离屏幕左/右至少保留 8 列set timeoutlen=300 " 映射键序列的等待超时(毫秒),值越小按键响应越快(默认 1000)
" ========================================" 内置扩展包" ========================================packadd! matchit " 加载 Vim 内置 matchit 包:% 键可匹配 if/else/endif、HTML 标签等
" ========================================" 注释切换(适用于 # 注释符的语言:Python/Shell/Ruby/YAML 等)" ========================================xnoremap gc :s/^/# /<CR>:nohlsearch<CR> " 可视模式 gc 注释选中的行(行首加 #)xnoremap gv :s/^# //<CR>:nohlsearch<CR> " 可视模式 gC 取消注释选中的行(去掉 #)nnoremap gcc :s/^/# /<CR>:nohlsearch<CR> " 普通模式 gcc 注释当前行nnoremap gvv :s/^# //<CR>:nohlsearch<CR> " 普通模式 gCC 取消注释当前行
" ========================================" 系统剪贴板复制(Ctrl+Shift+O,不污染寄存器)" ========================================if executable('wl-copy') function! s:Clip(text) abort call system('wl-copy', a:text) endfunctionelseif executable('xclip') function! s:Clip(text) abort call system('xclip -selection clipboard', a:text) endfunctionelseif executable('pbcopy') function! s:Clip(text) abort call system('pbcopy', a:text) endfunctionelse function! s:Clip(text) abort endfunctionendif
execute "set <F28>=\<Esc>[67;6u"
nnoremap <F28> :call <SID>Clip(getline('.') . "\n")<CR>:echo 'Line copied'<CR>xnoremap <F28> <Esc>:call <SID>Clip(join(getline("'<", "'>"), "\n") . "\n")<CR>gv:echo 'Selection copied'<CR>
set splitbelow splitrightset backspace=indent,eol,start
" ========================================" 标签跳转" ========================================set tags=./tags;,tags
nnoremap <leader>t :term<CR>tnoremap <leader>t <C-\><C-n>:q!<CR>
nnoremap <F5> :w <bar> !python3 %<CR>