What is Vim?
下面的内容将尝试使用 TypeScript 的类型系统来描述 Vim阅读学要掌握 TypeScript 的基本类型系统。从 TypeScript 的角度来看看 Vim 的内容。
内容在探索 vim 教程好方式,可能随时会修改(本次以 TypeScript 的类型的形式讲解学习,看看自己学习效果)。这样一来复习了 TS 的类型系统的约束,二来使用 JS/TS 对象来描述。看上去就很简单了。同时尽可能的配图,更加直观
// what is vim?
type Vim = {
author: string
from: string;
brothday: number | Date;
description: string;
[index: string]: any;
}
const vim: Vim = {
author: 'Bram Moolenaar';
from: 'vi',
brothday: 1991,
description: `Vim is a highly configurable text editor built to make creating
and changing any kind of text very efficient. It is included as "vi" with most
UNIX systems and with Apple OS X.`,
}
开始效率低,后期效率高
效率其实就是熟练程度,vim 在不同的熟悉人的手中,表现出不同的效率。
type effective = 'proficiency' & 'think' & 'patience';
开始学习时 vim 插件不是必须的
其实不是 vim 插件 vim 所提供的功能就已经很强大了。当熟悉了 vim 的按键布局操作之后,我们就可以慢慢探索 vim 更加强大的插件能力。
Vim 基础知识
基础从问题开始: ***为什么 vim 有如此多的模式?***
TypeScript 类型描述6中不同的模式:
// 三种可视模式
type VisualMode =
|"CharacterwiseVisualMode" // 字符级别的可视模式
|"LinewiseVisualMode" // 行级别的可视模式
|"BlockwiseVisualMode" // 块级别的可视模式
type VimMode =
|"normalMode" // 普通模式
| VisualMode // 三种可视模式
|"InsertMode" // 插入模式
|"CommandLinedMode" // 命令行模式
|"SelectMode" // 选择模式
|"ExMode" // ex 模式
|"ReplaceMode" // 替换模式
// 假如有一个 Vim 构造器,构建一个模式
const mode: VimMode = 'InsertMode';
const viminstance = new Vim({...options,mode})
如果你已经熟悉了,不同的 ide 和编辑器,打开了项目,就是编辑模式。但是 vim 为什么不是这样呢?
vim 来源于 vi,vim (vi IMproved) 是 vi 的 增强版。vi 是 Bill Joy 在其大学时候创建的。如果还不了解 Bill Joy 的铜须建议去了解真大神,可能获得更多的学习 vim 的乐趣。
vim 的从 1991 年发布以来,到今年已有了 30 个年头。想想 91 年,都还没有出生。在漫长的时间里,开发人员一辈辈的诞生,编辑器也在不断的展现出新的形式。但似乎只有 vim 一直以各种各样的形式存在。似乎也成了编辑文件的一种规范方式。
常用模式
一般的编辑器/IDE 都只有一个模式: 输入模式。但是在 vim 中,首先要接受的就是 vim 不止一种模式,vim 是使用多种模式,来完成不同的编辑任务。
normal模式(普通模式:mormal-modal)insert模式(输入模式 insert-modal)bottom-line模式 (底线命令模式 bottom-line-modal)visual模式(可视模式:char/line/block-modal)replace模式 (替换模式:replace-modal)
模式切换
vim 可以不需要鼠标,快速的进行编辑操作
普通模式 -> 插入模式:
type NormalToInsertLevel =
| "char"
| "word"
| "line"
| "block"
type NormalToInsertPositoin =
| "before"
| "after"
type NormalToInser = NormalToInsertLevel & NormalToInsertPositoin
普通模式与插入模式之间的互相切换,是vim 操作中最为常见的操作。 vim 的每一个操作都是如此的细腻,开始可能不习惯这种细腻的按键操作,但是随着越使用次数增多,越觉得流畅。
跳转到指定位置后插入光标删除指定字符、词、行内容后插入光标
以光标为参照点:line/char/before/after
在字符/行之前插入
type InsertBefore = "char" | "line"
i: insert before char(当前字符:前 插入光标)I: insert before line(当前行:前 插入光标)
在字符/行之后插入
type InsertAfter =
| "char"
| "line"
a: insert after char(当前字符:后 插入光标)A: insert after line(当前行:后 插入光标)
插入行(当前行的上下行)
type InsertLinePosition = "up" | "down"
const up: InsertLinePosition = 'O';
const down: InsertLinePostion = 'o';
o: insert after line (当前行:下 插入空行)O: insert before line (当前行:上 插入空行)
在删除字符/行之后插入
s:del char and insert (删除当前字符,然后插入)S:del line and insert (删除当前行,然后进入插入模式)dddel a line and not insert (删除当前行,但是不进入插入模式)
ccdel line and insert (删除当前行内容,并在第一字符位置插入光标)
普通模式 -> 可视模式:
v: 以字符为单位V: 以行为单位ctrl + v: 以块为单位
普通模式 -> 替换模式
r: 字符级别替换R: 文档级别的替换gr: 字符级别虚拟替换替换gR: 文档级别虚拟替换替换
虚拟与非虚拟的区别:
<Tab>按键的使用区别<NL>换行的处理方式
插入模式 -> 普通模式
- Esc
可视模式 -> 普通模式
- Esc
移动光标(normal 模式)
type TDirection = "up" | "down" |"left" |"right"
const dircectionOp: TDirection = "up";
h(键盘⬅️)普通模式向左,字符级别移动j(键盘⬇️)普通模式向下,行级别移动k(键盘⬆️)普通模式向上,行级别移动j(键盘⬆️)普通模式向上,字符级别移动
基本操作
- 复制
y===yank - 粘贴
p===paste - 修改
c===change - 删除
d===delete - 插入
i===insert - 追加
a===append - 结尾
e===end
这些命令的语义单词也很好的解释了上面模式切换命令。注意这些命令不仅适用于普通模式,也适用于 visual 模式。
普通模式中常用操作
vim 除了之前的命令模式,以及一些命名,最重要的就是 vim 其实一直操作就是文本对象(object-text),通过操作文本对象,vim 的操作更加的丰富。
number: 表示数量 command: 执行的具体命令,normal 模式下的命令 i/a/r 等命令 text object or motion: 指的是需要操作的文本对象,比如单词、句子、段落等
[number]<command>[text object or motion]
// typescript 对操作的描述类型
type opLevel = "char" | "word" | "sentence" | "line" | "block";
const scope_char: opLevel = 'char'; // 字符级别
const scope_word: opLevel = 'word'; // 词级别
const scope_sentence: opLevel = 'sentence'; // 句子级别
const scope_line: opLevel = 'line'; // 行级别
const scope_block: opLevel = 'block'; // 块界别
移动(move or jump)
移动:字符左右
h(键盘⬅️)普通模式向左,字符级别移动j(键盘⬆️)普通模式向上,字符级别移动
移动:词左右
l跳转到下一个词的第一个字符
const jump_a_char = {
name: 'l_jump',
c_name: "跳转到下一个字符",
description: "vim normal 模式跳转命令,跳转到下一个字符",
scope: "char",
size: 1,
command: 'l',
before_vim_mode: "normal_mode",
after_vim_mode: "normal_mode“,
}
W跳转到下一个词的第一个字符**W跳转到下一个词的第一个字符e跳转到下一个词的第一个字符E跳转到下一个词的第一个字符b跳转到上一个词的第一个字符B跳转到上一个词的第一个字符
移动:行首/尾
- 行的最前端
const jump_line_first_position = {
name: 'line_first_jump',
c_name: "跳转到行最前端",
description: "vim normal 跳转到行的最前端,
scope: "line",
size: unknown,
command: 0 | 'shift |',
before_vim_mode: "normal_mode",
after_vim_mode: "normal_mode",
support_in_visual_model: false;
}
有时候我们需要跳转到行第一的位置(注意,不是缩进后的第一个字符,就是行的最前端)
- 缩进后的最前端
const jump_line_indent_first_position = {
name: 'line_indent_first_jump',
c_name: "跳转到行缩进最前端",
description: "vim normal 跳转到行的最前端",
scope: "line",
size: unknown,
command: I,
before_vim_mode: "normal_mode",
after_vim_mode: "normal_mode",
support_in_visual_model: false;
}
注意:有时候以上两种情况是重叠的,两个命令效果是一样的
- 移动到行尾
const jump_line_last_position = {
name: 'line_last_jump',
c_name: "跳转到行尾",
description: "vim normal 跳转到行尾",
scope: "line",
size: unknown,
command: $,
before_vim_mode: "normal_mode",
after_vim_mode: "normal_mode",
support_in_visual_model: trye;
}
与之对应跳转到行尾部,并进入插入模式(在其他的地方进行讲述)命令就是 A。
移动:行与行之间
^跳转到当前行的开始(与正则匹配相关)$跳转到当前行的结尾(与正则匹配相关)gg跳转到第一行的开始ngg跳转到第n行的开始G跳转到最后一行的开始
移动:成对匹配
使用 % 进行成对匹配跳转,支持字符:
[](){}其他
⚠️注意: <> 不支持成对匹配跳转。
移动:查找相同
#/n跳转到上一个相同N跳转到下一个相同
移动:屏幕底部
L跳转到屏幕底部H跳转到屏幕顶部M跳转到屏幕中间
跳转
在词(word)级别跳转
b/B前一个词w/W下一个词
在句子(sentence)级别跳转
(下一个🍊)上一个🍊
在段落(paragraph)之间进行跳转
}下一个段落{上一个段落
在小节 (section)之间跳转
函数跳转到定义的位置
文本内容搜索
vim 使用 /和? 执行正向和反向的查找任务,查找到的内容会高亮显示.
- 正向:
/your_find_content - 反向:
/your_find_content
其次可以使用插件来完成更加复杂的查找和跳转任务。
vim 文本-文件操作
打开文件
- vim -r <file_name> 恢复之前编辑的文件(vim 会创建交换文件)
文本的相关操作
文件编码
- utf8
文件格式
- unix, dos, mac
文件保存
ZZ保存文件并退出w保存文件不退出
vim 文本全选
ctrl + v使用 visualgg跳到第一行的开始v进入 visual 模式G跳转到末尾行- 获取全选功能
- 删除(d)/复制(指定的寄存器 "*y)
撤销与恢复
ctrl + r撤销上一次操作u恢复上一次操作
文本注释
- inline-visual 下行首根据不同的语言插入不同注释字符
"//"or"#"。
注释单个行
shift + v进入行内 visual 模式^跳转到行首esc进入普通模式i进入插入模式,输入注释内容 "//" or "#"
使用 vim 插件完成注释功能
vim-commentary 所提供的注释操作符命令为 gc,那么就可以有如下的操作
gcc注释行<number>gccgcap注释段落。如果函数之间是用空行分隔的,那么gcap会注释光标所在整个的函数。
字符大小写变化
gU/U将所选字符转换成大写gu/u将所选字符转换成小写~将字符大小写进行颠倒
vim 寄存器
寄存器与 buffer 是有很大的共同性的, 比如它们都有一个列表,保存了所有的内容:
查看寄存器列表
- 使用
:reg命令查看寄存器的列表,类似于 buffer 的:ls/:files。
使用寄存器的名字进行访问
" 单个双引号表示开始引用寄存器。下面要开始介绍寄存起的类型了。" 引用不同的类型的寄存器,可以完成不同的任务(如此的细腻,一点都不粗糙!)。
系统级别的寄存器
当我们需要和vim外内容时,系统寄存器就派上了用场,并且我们使用系统寄存的次数应该时特别的多的:
type SystemClipboard = {
name: "systemClipboardReg",
c_name: "系统剪贴板寄存器",
description: "与系统剪贴板进行交互访问,配合vim y/p 等命令,在 vim 中进行操作。",
fromSystemToVim: {
"command"
}
}
type SystemRegs = "SystemClipboardReg" | "currentBufferReg";
const sReg: SystemRegs = "SystemClipboardReg";
"*当前缓冲区"+系统剪贴板
vim 寄存器的类型(或者种类)
使用 TypeScript 的类型系统进行描述:
type VIMRegisters = {
NumberReg: number;
InlineDeleteReg: any;
NameReg: string;
readony ReadOnlyReg: string;
// todo
ExpressionReg: string;
SelectionAndDropReg: string;
BlackHole: string
SearchPatternReg: string;
}
在计算机的世界里,最好还是要称呼类型。因为强类型语言是常用语言,类型重要性不言而喻。
未命名寄存器(The Unnamed Register)数字寄存器(The Numbered Registers)行内删除寄存器(The Small Delete Register)命名寄存器(The Named Registers)只读寄存器(The Read-Only Registers)乱换缓冲寄存器(The Read-Only Registers)表达式寄存器(The Expression Register)选择和拖拽寄存器(The Selection and Drop Registers)黑洞寄存器(The Black Hole Register)搜索模式寄存器(Search Pattern Register)
未命名寄存器 the Unnamed Register
也叫匿名寄存器,使用d,c,x等删除的字符会存放进匿名寄存器,换句话说,最近一次删除,修改,复制的内容都存放在这里,会覆盖。
type theUnnamedReg = {
name:string;
operate: 'del' | 'change' | 'x';
descriptions: string;
}
vim 中的删除操作
删除字符
- s 删除当前光标所在的字符,然后再此位置插入光标(进入插入模式)
const vim_s_del = {
name: 's_del',
c_name: "删除字符",
description: "当前光标删除,插入光标,进入插入模式",
scope: "character",
size: 1,
command: 's',
before_vim_mode: "normal_mode",
after_vim_mode: "insert_mode",
}
删除词
const vim_s_del = {
name: 's_del',
c_name: "删除字符",
description: "当前光标删除,插入光标,进入插入模式",
scope: "character",
size: 1,
command: 's',
before_vim_mode: "normal_mode",
after_vim_mode: "insert_mode",
}
删除行
dd删除当前行且不保留S清空行删除当前行且保留行(会进入插入模式)
注意:我们在使用 vim 对文本进行增删改查的时候,要时刻的注意 vim 的寄存器的变化,寄存器中保留的数据可以方便的配合我们返回之前删除的内容。
删除之后的恢复:
- 有进入 insert mode,使用 esc 键退出 insert 模式,然后使用 u 命令进行撤回。
- 没有进入 insert mode,直接使用 u 命令撤回
- 使用寄存器中保存的数据,重新填充
删除指定范围的行
{a,b}d能够删除指定范围的行
删除行级块
删除块多配合 vim 的 visual 模式,和 visual 模式下的命令进行配合使用
shift + v进入行 visual-line 模式- 使用 hjkl 方向进行导航,选择需要的行
dd执行删除多行的命令
因为是多行,看起来就是行级别的块的操作。
删除块
使用 vim 的 visual 模式,操作文本编辑器块是非常方便。
删除成对符号内部的内容
d(a/i)({, <, [,)删除{}, <>, [], 内部或者{}及所有代码块
清空文件中的所有内容
ggdG: gg 是跳转到顶部, d 删除,G 跳转到结尾,总结起来就是:先跳转开始,然后执行删除,删除到 G 的位置也就是结尾。
寄存器与命令行为
-
yy复制一行之后,寄存器有哪些变化? -
"0 数字寄存器的 0 保存了yy命令的内容
-
"* 系统寄存器 * B 保存了 yy 命令的内容,也就是说可以直接在系统的其他地方使用 yy 民名复制的内容。
vim buffers
理解buffer
一个 buffer 就是一个已经载入内存的文件。所有打开的文件都对应着一个buffer,同时也存在着不对应任何文件的 buffer。可以理解为 ide 中,打开的一个 tab。
下面是 buffers 的 "增删改查",为了理解和熟练 buffers 的内容,我们可以创建一个文件夹和一些文件来测试 buffer 以及 buffer 相关的操作。
cd your_target_dir
mkdir vim_buffers_test && cd vim_buffers_test
touch index.js about.js article.js
vim vim_buffers_test
- 原生 vim 的原声文件目录结构管理
- 使用 NerdTree 管理目录
增加一个 buffer
badd意思是 buffer add 增加 buffer。
示例
# 进入目录
cd your_project_dir
vim ./
# 在 vim 中编辑
:ls
:badd test.index.js
# vim 输入测试内容
const a = this;
# 保存文件
:wq!
# 打开 nerdtree 查看我们创建的文件是否在当前目录下
# 或者推出 vim 看看 ls 中是否包含 test.index.js 文件
创建分屏存放不同的 buffers
:vsp创建垂直的分屏
打开打开文件选择器 NERDTree 选择不同的文件
因为 NEDTREE 绑定 ctrl+d 快捷键切换 NERDTree 的打开和关闭
ctrl + d # 切换 nerdtree 的显示状态
在不同的 buffers 之间进行切换
ctrl + w
按照 buffer 的的序号切换 buffer
buffer 提供了 b + num, 切换到指定 num 所在的 buffer(num 是 :ls/:files/:buffers 中内容)。
按找 buffer 前后位置切换
bn相当于当前 buffer 的 nextbp相当于当前 buffer 的 preview
收尾位置切换 buffers
bfirst打开第一个 bufferblast打开最后一个 buffer
按照文件名打开 buffer
buffer your_file_name.ext打开指定文件扩展名的文件
查看 buffers
:buffers # 得到所有 buffer 编号,和缓冲区相关信息
:ls # 类似于 linux 的 ls 命令
:files # 是 buffers 的另外一个别名
也就是说查看有的 buffer 信息,有以上三个命令可以帮助我们。
缓冲区信息
不同种类的 buffer, 在 ls 列表中,除了表示 buffer 的序号,还有buffer的类型,buffer 用以下的字符表示不同类型 buffer,
aactive buffer 表示当前激活的缓冲区#alternate buffer 交换缓冲区%current buffer 当前缓冲区-Readonly buffer 只读缓冲区(禁用了 modifiable 选项)=Readonly buffer 只读缓冲区Hhidden buffer 已经存在了,但是被隐藏起来了+modify buffer 已经修改的缓冲区xerror buffer 读取时报错的缓冲区uunlisted buffer 使用!才展示的缓冲区
这里要多讲一下 nerdtree 的内容,nerdtree 其实产生的就是一个隐藏的示例。
其中 3u a- 所在的 buffer 的就是一个隐藏的buffer。不使用 ! 命令在 buggers 列表中是看不见的。
buffer 使用示例
我们对比 2 号和4号缓冲区信息的变化。图片1 是没有正在激活的buffer, 原因是正在处于 NerdTree 的选择阶段。图片2 中有了 %a 表示当前的缓冲区和激活的缓冲区是在一个位置
buffers 之间的切换信息
- 快捷键操作 buffer
ctrl+^在最近的两个 buffer 之间进行切换
删除 buffer
- :bdelete filename/num 后置删除
- :num,num bdelete 前置删除
卸载 buffer
- bunload
- bunload!
buffer 与 窗口之间的相互配合
vim tab 操作
最常用的 tab 操作居然是,我们打开很多的 tab, 然后只保留当前正在使用的: :tabo(只保留自己,其他的全部删除)。在 vscode 中,我们配合 ctrl+p 来切换不同的文件,每一个文件都会产生一个 tab.
vim 技巧
vim 上下复制一行,使用 . 重复执行,有多余的使用 u 进行撤回
- 行复制
yyp - 重复的行复制
. - 撤销多余的行复制
u
注释代码
原生注释方法
插件注释
注释是代码中很常见的内容需求,我们使用插件来完成 vim 的注释功能:
- vim-commentary vim 的注视
- 注释当前行: gcc
- 取消注释:gcc(使用重复 . 来进行切换)
特殊的注释语法
- jsx/tsx 中包含了特殊的注释语法。 vscode 的 vim 插件使用插件注入,已经能识别是 jsx 语法进行注释,但是在 纯 vim 中式不能识别代码是不是包含在 jsx 的结构中,这一点是特别注意的。
文件处理
在 vim 处理文件与我们在 ide 和编辑器中有很大的不同,之前我们学习了vim 的一些基础的概念,知道了 vim 熟悉之后的操作是比其他的ide在边编辑文字方面是要高很多的。
查找文件
NerdTree插件工程化文件管理器fzf插件快速的文件查找ctrlp插件
type TVimPlugs = "nerdtree" | "fzf" | "ctrlp";
type TFindVimFiles = {
name: string;
vimPlugs: TVimPlugs[].
}
const vimFindFiles: TFindVimFiles = {
name: 'vim find files',
vimPlugs: ["nerdtree", "fzf", "ctrlp"],
}
文件分屏编辑
- vim 文件分屏编辑(多屏的 vim 编写文件是常见需求)
- tmux + vim 文件分屏工具
type VimSplitScreenSolution = "vim" | "tmuxWithVim";
type VimSplitScreenOpportunity = "open" | "edit";
type VimSplitScreenDiretion = "vs" | "sv";
type VimSplitScreenWithNewFile = "newFileSv" | "newFileVs"
Tmux 与 Vim 天作之合
七夕节,我们写一点与之有关的内容。我们知道 Vim 在编辑上效率是非常高的,但是在管理分屏和项目管理上并不是他的强项。 Tmux 就是解决了终端的复用问题, Tmux 将终端会话化,会话不停 Tmux 不止。
vim 与终端
vim 8 开始支持终端。但是使用起来并不是很顺手,vim 插件中 vim-floaterm 使用浮动弹窗的方式创建终端。其实这个时候 tmux 中就比较大的。
配置 `vimrc` 快捷键,`新建`-`上一个`-`下一个`-`显示/隐藏`
```vimrc
# 添加插件
Plug 'voldikss/vim-floaterm'
# 配置快捷键
let g:floaterm_keymap_new = '<F7>'
let g:floaterm_keymap_prev = '<F8>'
let g:floaterm_keymap_next = '<F9>'
let g:floaterm_keymap_toggle = '<F12>'
vim 与 git
使用 lazygit 插件
Plug 'kdheepak/lazygit.vim'
vim 撤销 回退操作
- undo 是撤销操作
- ctrl + r 回退
常用操作:撤销之后回退
vim 特殊符号
删除指定的位置:
di<: 删除<content_str>中的 content_str, html 等标记语言中常常用到di{: 删除{content_str}中的 content_str, css/js/rust/... 等等语言中表示块di[: 删除[content_str]中的 content_str,js 中表示数组,
vim 在行后、词后(normal) 模式进入 insert 模式
常用操作:在 normal 下找到代码位置,然后快速的进入 insert 模式:
- 一个指定行后面需要加入新的内容 append:
$a - 一个指定词后面需要加入新的内容 append:
ea
与之相反:
- 一个指定行前面需要加入新的内容 append:
^a - 一个指定词后面需要加入新的内容 append:
ba
医生是 normal 的跳转和进入insert模式的组合命令。
参考
😸持续更新中...
- Vim 快捷键大全 www.cnblogs.com/codehome/p/…