# 计算机教育缺失的一课：编辑器（Vim）


## 编辑模式

Vim 的符号说明，对于 `Ctlr+v` 的组合键，可能有 `^V`、`Ctrl-v`、`<C-v>` 三种表达方式。

Vim 的设计以大多数时间都花在阅读、浏览和进行少量编辑改动为基础，因此它具有多种操作模式：

- **正常模式**：在文件中四处移动光标进行修改
- **插入模式**：插入文本
- **替换模式**：替换文本
- **可视化模式**（一般，行，块）：选中文本块
- **命令模式**：用于执行命令

> 正常模式即 normal 模式，我更习惯叫 normal 模式。

Vim 的模式：

```txt
normal <-> insert
           replace
           visual
           command-line
```

在 normal 模式下，按下 `v` 可以进入可视（一般）模式，按下 `V` 则可以进入可视（行）模式，而 `<C-v>` 则会进入可视（方块）模式。

## buffer，window，tab

vim 具有多个 tab（标签），每个 tab 可以包含多个 window，每个 window 对应一个 buffer，而同一个 buffer 可能由多个 window 打开。

`:q` 实际上只是关闭当前窗口，假设 vim 已经没有打开的窗口了，那么才会退出 vim。

## 命令模式

在 normal 模式下按下 `:` 可以进入命令模式，这个模式下，可以打开、保存、关闭文件，以及退出 Vim。

- `:q` 退出（关闭窗口）
- `:w` 保存（写）
- `:wq` 保存然后退出
- `:e {文件名}` 打开要编辑的文件
- `:ls` 显示打开的缓存
- `:help {标题}` 打开帮助文档
    - `:help :w` 打开 `:w` 命令的帮助文档
    - `:help w` 打开 `w` 移动的帮助文档

## 移动光标

在 normal 模式下，可以利用移动命令在 buffer 中移动光标，在 Vim 中，移动也被称为“名词”，与编辑命令（`i`、`o`）等相对应。

- 基本移动：`hjkl` （左，下，上，右）
- 词： `w` （下一个词）， `b` （词初）， `e` （词尾）
- 行： `0` （行初）， `^` （第一个非空格字符）， `$` （行尾）
- 屏幕： `H` （屏幕首行）， `M` （屏幕中间）， `L` （屏幕底部）
- 翻页： `Ctrl-u` （上翻）， `Ctrl-d` （下翻）
- 文件： `gg` （文件头）， `G` （文件尾）
- 行数： `:{行数}<CR>` 或者 `{行数}G` ({行数}为行数)
- 杂项： `%` （找到配对，比如括号或者 /* */ 之类的注释对）
- 查找： `f{字符}`， `t{字符}`， `F{字符}`， `T{字符}`
    - 查找/到 向前/向后 在本行的{字符}
    - `,` / `;` 用于导航匹配
- 搜索：`/{正则表达式}`, 按下 <CR> 之后，`n` / `N` 用于导航匹配

## 编辑


所有你需要用鼠标做的事，你现在都可以用键盘：采用编辑命令和移动命令的组合来完成。
这就是 Vim 的界面开始看起来像一个程序语言的时候。Vim 的编辑命令也被称为“动词”，
因为动词可以施动于名词。

- `i` 进入插入模式 
    - 但是对于操纵/编辑文本，不单想用退格键完成
    - `I` 表示在当前行首插入，`a` 表示在当前光标后插入，`A` 表示在当前行末插入
- `O` / `o` 在之上/之下插入行
- `d{移动命令}` 删除 {移动命令}
    - 例如，`dw` 删除词，`d$` 删除到行尾，`d0` 删除到行头。
- `c{移动命令}` 改变 {移动命令}
    - 例如，`cw` 改变词，相当于 `dw` 再执行 `i`
- `x` 删除字符（等同于 `dl`）
- `s` 替换字符（等同于 `xi`）
- 可视化模式 + 操作
    - 选中文字，`d` 删除 或者 `c` 改变
- `u` 撤销，`<C-r>` 重做
- `y` 复制 / "yank" （其他一些命令比如 `d` 也会复制）
- `p` 粘贴
- 更多值得学习的：比如 `~` 改变字符的大小写

## 计数

你可以用一个计数来结合“名词”和“动词”，这会执行指定操作若干次。

- `3w` 向后移动三个词
- `5j` 向下移动 5 行
- `7dw` 删除 7 个词

## 修饰语

你可以用修饰语改变“名词”的意义。修饰语有 `i`，表示“内部”或者“在内”，和 `a`，
表示“周围”。

- `ci(` 改变当前括号内的内容
- `ci[` 改变当前方括号内的内容
- `da'` 删除一个单引号字符串，包括周围的单引号

## 宏

- `q{字符}` 来开始在寄存器`{字符}`中录制宏
- `q`停止录制
- `@{字符}` 重放宏
- 宏的执行遇错误会停止
- `{计数}@{字符}`执行一个宏{计数}次
- 宏可以递归
    - 首先用`q{字符}q`清除宏
    - 录制该宏，用 `@{字符}` 来递归调用该宏
    （在录制完成之前不会有任何操作）
- 例子：将 xml 转成 json ([file](/2020/files/example-data.xml))
    - 一个有 "name" / "email" 键对象的数组
    - 用一个 Python 程序？
    - 用 sed / 正则表达式
        - `g/people/d`
        - `%s/<person>/{/g`
        - `%s/<name>\(.*\)<\/name>/"name": "\1",/g`
        - ...
    - Vim 命令 / 宏
        - `ggdd`, `Gdd` 删除第一行和最后一行
        - 格式化最后一个元素的宏（寄存器 `e`）
            - 跳转到有 `<name>` 的行
            - `qe^r"f>s": "<ESC>f<C"<ESC>q`
        - 格式化一个<person>的宏
            - 跳转到有 `<person>` 的行
            - `qpS{<ESC>j@eA,<ESC>j@ejS},<ESC>q`
        - 格式化一个<person>标签然后转到另外一个<person>的宏
            - 跳转到有 `<person>` 的行
            - `qq@pjq`
        - 执行宏到文件尾
            - `999@q`
        - 手动移除最后的 `,` 然后加上 `[` 和 `]` 分隔符
