Vim + TypeScript 类型系统以及对象描述♨️

1,229 阅读17分钟

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 的每一个操作都是如此的细腻,开始可能不习惯这种细腻的按键操作,但是随着越使用次数增多,越觉得流畅。

  1. 跳转到指定位置后插入光标
  2. 删除指定字符、词、行内容后插入光标

以光标为参照点: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 (删除当前行,然后进入插入模式)
    • dd del a line and not insert (删除当前行,但是不进入插入模式)
  • cc del 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 使用 visual
  • gg 跳到第一行的开始
  • v 进入 visual 模式
  • G 跳转到末尾行
  • 获取全选功能
  • 删除(d)/复制(指定的寄存器 "*y)

撤销与恢复

  • ctrl + r 撤销上一次操作
  • u 恢复上一次操作

文本注释

  • inline-visual 下行首根据不同的语言插入不同注释字符 "//" or "#"

注释单个行

  • shift + v 进入行内 visual 模式
  • ^ 跳转到行首
  • esc 进入普通模式
  • i 进入插入模式,输入注释内容 "//" or "#"

使用 vim 插件完成注释功能

vim-commentary 所提供的注释操作符命令为 gc,那么就可以有如下的操作

  • gcc 注释行
  • <number>gcc
  • gcap 注释段落。如果函数之间是用空行分隔的,那么gcap会注释光标所在整个的函数。

字符大小写变化

  • gU/U 将所选字符转换成大写
  • gu/u 将所选字符转换成小写
  • ~ 将字符大小写进行颠倒

vim 寄存器

寄存器与 buffer 是有很大的共同性的, 比如它们都有一个列表,保存了所有的内容:

查看寄存器列表

  • 使用 :reg 命令查看寄存器的列表,类似于 buffer 的 :ls/:files

image.png

使用寄存器的名字进行访问

" 单个双引号表示开始引用寄存器。下面要开始介绍寄存起的类型了。" 引用不同的类型的寄存器,可以完成不同的任务(如此的细腻,一点都不粗糙!)。

系统级别的寄存器

当我们需要和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 的 next
  • bp 相当于当前 buffer 的 preview

收尾位置切换 buffers

  • bfirst 打开第一个 buffer
  • blast 打开最后一个 buffer

按照文件名打开 buffer

  • buffer your_file_name.ext 打开指定文件扩展名的文件

查看 buffers

:buffers # 得到所有 buffer 编号,和缓冲区相关信息
:ls # 类似于 linux 的 ls 命令
:files # 是 buffers 的另外一个别名

也就是说查看有的 buffer 信息,有以上三个命令可以帮助我们。

缓冲区信息

不同种类的 buffer, 在 ls 列表中,除了表示 buffer 的序号,还有buffer的类型,buffer 用以下的字符表示不同类型 buffer,

  • a active buffer 表示当前激活的缓冲区
  • # alternate buffer 交换缓冲区
  • % current buffer 当前缓冲区
  • - Readonly buffer 只读缓冲区(禁用了 modifiable 选项)
  • = Readonly buffer 只读缓冲区
  • H hidden buffer 已经存在了,但是被隐藏起来了
  • + modify buffer 已经修改的缓冲区
  • x error buffer 读取时报错的缓冲区
  • u unlisted buffer 使用 ! 才展示的缓冲区

这里要多讲一下 nerdtree 的内容,nerdtree 其实产生的就是一个隐藏的示例。

image.png

其中 3u a- 所在的 buffer 的就是一个隐藏的buffer。不使用 ! 命令在 buggers 列表中是看不见的。

buffer 使用示例

image.png

image.png

我们对比 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在边编辑文字方面是要高很多的。

查找文件

  1. NerdTree插件 工程化文件管理器
  2. fzf插件 快速的文件查找
  3. ctrlp 插件
type TVimPlugs = "nerdtree" | "fzf" | "ctrlp";

type TFindVimFiles = {
    name: string;
    vimPlugs: TVimPlugs[].
}

const vimFindFiles: TFindVimFiles  = {
    name: 'vim find files',
    vimPlugs: ["nerdtree", "fzf", "ctrlp"],
}

文件分屏编辑

  1. vim 文件分屏编辑(多屏的 vim 编写文件是常见需求)
  2. 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 模式:

  1. 一个指定行后面需要加入新的内容 append: $a
  2. 一个指定词后面需要加入新的内容 append: ea

与之相反:

  1. 一个指定行前面需要加入新的内容 append: ^a
  2. 一个指定词后面需要加入新的内容 append: ba

医生是 normal 的跳转和进入insert模式的组合命令。

参考

😸持续更新中...

  1. Vim 快捷键大全 www.cnblogs.com/codehome/p/…