The Missing Semester —— 学校不教的 CS 基础

最近在看一个 MIT 发布的系列课程视频:The Missing Semeter Of Your Computer Science Education,课程涵盖了很多我们在平常开发中遇到过的、但是课程体系中很少涉及到的话题,并介绍了相关的基础知识和最佳实践。这一系列视频中,其中一节是关于 Vim 的,介绍了 Vim 背后的思想和一些基本操作。

虽然之前在捣鼓云服务器的时候用过 VIm 编辑过一些文件,但是那时候对命令行环境还比较陌生,有一些基本的操作还不够熟练,也就不愿意去深入学习 Vim,导致一直没有入门。平时也主要用 VS Code 这样的现代编辑器,所以也没有太强的动力去学习 Vim。

最近在一些项目中,开始越来越多地和终端、shell 脚本打交道,愈发感觉需要学习一些进阶的操作,来提高生产力。这一系列课程视频恰好为我打开了新世界的大门。另外,在写代码的时候,确实发现在键盘和鼠标之间来回切换相当费时间。

正好现在借着这一系列视频,开始重新学习 Vim 的操作。现在断断续续已经用了两个礼拜、四五个小时了,基本的操作逐渐熟悉,编辑速度直线上升。甚至发现即使是在现代编辑器中,很多操作使用 Vim 也会更快一些。于是连 VS Code、Chrome 浏览器什么的全都配置了 Vim,尽量避免使用鼠标或者触控板。

在进入正题之前,向大家推荐一下这一系列的课程。如果你想系统地了解一下命令行界面、Vim 等各种工具,想要提升自己的生产力,或者简单地想学一些“高端”操作,都可以去看一下这一系列视频,相信你不会后悔的。

接下来,我会简单介绍一下 Vim 及其背后的思想,并通过一些简单的使用场景,介绍 Vim 入门级别的一些命令。

Vim 的基本思想

Vim 认为,开发者大部分时间花在阅读代码、对代码进行小幅编辑上,而非花在编写大量代码上。于是,Vim 就有了各种模式:在 普通(NORMAL) 模式下阅读代码、跳转位置、快速编辑,在 编辑(INSERT) 模式下编写代码,在 可视(VISUAL) 模式下选择代码,等等。

Vim 是可编程的,可以通过 Vimscript 或者 Python 编写控制 Vim 的程序。甚至连 Vim 的界面本身也是一种“编程语言”——每一个按键对应一个命令,并且多个命令可以组合在一起。

Vim 帮助你脱离对鼠标的依赖。在键盘和鼠标中频繁切换太慢了。甚至,在键盘和方向键之间切换也很慢,所以它连方向键也不需要。

基于这些思想,Vim 能够跟上我们思考的速度,使用熟练以后,会大大提高我们的效率。

Vim 的基本操作

模式切换

  • 当使用 Vim 命令打开一个文件时,会默认进入普通模式。
  • 使用 i (insert) 进入编辑模式,在当前光标之前插入文本。(还有另外的命令可以进入编辑模式,在之后会详细列出)
  • 使用 v (visual) 进入可视模式
  • 在非普通模式下,使用 ESC 回到普通模式
    • 在使用 Vim 时,会频繁使用到 ESC。因此,建议将大写锁定键绑定为 ESC,来减少手的移动幅度。

普通模式

正如在基本思想中所说,开发者很大一部分时间是在阅读代码。而且阅读并非总是线性的,需要在同一文件的不同部分来回跳转切换,或者需要查看多个文件。因此,在阅读模式下快速移动光标到自己想要查看的位置尤为重要。

Vim 不需要方向键,而是由 hjkl 代替,分别对应左、下、上、右四个方向键。虽然其实用方向键也可以移动,但是为了能够尽量避免手的位移,还是习惯一下 hjkl 为好。在 Vim 的配置文件中,也可以设置禁用方向键,这个之后会给出相应的配置。

但是这仅仅只是实现了方向键。我们还是只能一点点的上下左右移动。于是,又有了很多别的操作,让我们更快地移动

行间移动:

  • w 向后移动一个词。
  • b 向前移动一个词
  • e 向后移动到当前词的末尾字符
  • {number}w/b/e 移动规定词数,比如 3w 会向后移动三个词
  • $ / ^ 光标移动至行尾/行首

跨行移动:

  • {number}j/k 向下/向上移动相应行数,比如 5k 向上移动 5 行
    • 这样移动时,行号很重要。Vim 不仅支持显示当前行数,还可以在配置文件中设置显示相对行号,这样很方便就能看出需要向上或者向下移动几行。详细配置会在后面一并给出。
  • {number}G 移动到该行
  • H 移动到窗口顶端,M 移动至窗口中间,L 移动到窗口底部
  • Ctrl-u 向上滚动半个窗口,Ctrl-d 向下滚动半个窗口
  • gg 移至文件顶端,G 文件移至底端

搜索:

  • f{character} F{character},在当前行,向后/向前查找某一字符,光标移动到该字符
    • 使用 ,; 跳至下一处或者上一处
  • t{character}, T{character},在当前行,向后/向前查找某一字符,光标移动到该字符之前
    • 同样,使用 ,; 跳至下一处或者上一处
  • /{regex} 搜索文件,n / N 跳转到下一处/上一处

编辑模式

在普通模式下,除了上面提到的用 i 进入编辑模式,还有其他的命令:

  • I (shift + i) 在当前行的第一个非空字符之前进入编辑模式
  • a (append) 在光标之后进入编辑模式
  • A 在行尾进入编辑模式
  • o 在当前行下新插入一行,光标移动至行首,进入编辑模式
  • O 在当前行上面新插入一行,光标移动至行首,进入编辑模式

这几个操作对应了几个不同的场景。在实际的开发过程中很常见。比如行尾少了冒号或者分号,比如需要新开一行,不需要先移动光标到行尾,而可以直接使用对应的指令在相应位置开始编写代码。

还有一些常见的命令,可以对应其他的编辑场景:

  • d{motion} 删除,比如 dw 是删除当前词,d$ 删除光标至行尾的所有字符,d^ 删除光标至行首的所有字符
  • c{motion} 修改,cw 是修改当前词,效果是删除当前词,并进入编辑模式,等同于 dw + i
  • x 删除当前字符,相当于 dl
  • s 替换当前字符,相当于 xi

进入可视模式后,可以选中文本,再进行操作:

  • 可视模式下通过前面介绍过的移动命令可以选中特定文本
  • 选中后,d 删除,c 修改
  • y 复制(英文是 yank),d 也会复制,p 粘贴

Vim 中也有对应的撤销、重做:

  • 在普通模式下,u 撤销 ctrl + r 重做

计数与修饰

在前面我们见过了 {number}k 这样的操作,可以通过计数来执行特定次数相同的操作。更多的例子有:3w 向后移动 3 个词,7dw 删除 7 个词

通过修饰,可以修改名词的意思,实现不同的编辑效果。比如,ci( 可以修改当前括号内的内容,它会删除当前括号内的内容,并进入编辑模式。da' 则会删除引号以及引号内部的内容。这里 i 的意思是 inside,a 则是 around

Vim 命令行

在普通模式下,使用 : 进入命令行,进行文件的读取、写入等操作:

  • :w 保存
  • :q 退出。 :wq 保存 + 退出
  • :q! 放弃修改的内容强行退出
  • :e {file name} 打开某一文件
  • :ls 显示打开的文件(缓冲)
  • :help {topic} 打开帮助界面
    • :help :w 打开 :w 命令的帮助
    • :help w 打开 w 移动操作的帮助

Vim 实例演示

介绍了一些基本操作,可以通过一个例子来看看如何使用这些命令来编辑一个文件:

The Missing Semester 提供了一个例子,里面是一段有问题的 Python 程序:

def fizz_buzz(limit):
    for i in range(limit):
        if i % 3 == 0:
            print('fizz')
        if i % 5 == 0:
            print('fizz')
        if i % 3 and i % 5:
            print(i)

def main():
    fizz_buzz(10)

该程序存在一些问题:

  • main 函数不会执行,需要在文件末尾添加一些语句
  • 循环会从 0 开始,而不是从 1 开始,需要修改 range 中的内容
  • 当数字是 3 的倍数 或者是 5 的倍数的时候,打印的是同一个词, 需要修改其中一个引号内的词为别的什么以区分
  • main 函数中使用的是写死的参数,而非命令行参数或者是用户的输入

下面的几个动图,显示了如何通过 Vim 来修改这个程序。按键操作显示在了动图中

添加 if __name__ == 'main': 语句,涉及到行数跳转:

add main

修改其中一个 'fizz''buzz'

modify range

修改 range 范围:

modify word

添加命令行参数:

Vim 基本配置

前面提到了一些需要配置的功能。在上面的动图中你也看到了相对行号显示、自动缩进等,这些在最开始打开 Vim 的时候并不是默认开启的,需要在 Vim 的配置文件中进行配置。

在 macOS 和 Linux 系统中,Vim 的配置文件位于 ‘~/’ 目录下,文件名为 .vimrc。Vim 在每次启动时都会默认读取该文件的配置。

这里是一个比较简单的配置文件,建议你阅读注释的描述,判断自己是否需要这一设置,再手动配置你的 ‘~/.vimrc` 文件,仅仅加入你需要的配置项。这样能够避免你在不清楚设置的具体内容是什么的情况下,盲目拷贝别人的配置,导致引入一些你不习惯的操作。

配置文件来自于 The Missing Semester,翻译了注释

" 双引号为注释标记.

" 如果在 Vim 中打开这个文件,则会看到语法高亮

" Vim 是基于 Vi 开发的。设置 <code>nocompatible</code> 会取消默认的 Vi 兼容模式,并启用一些有用的 Vim 功能
" 这项配置在 ~/.vimrc 中其实没必要,因为如果用户有 <code>~/.vimrc</code> 文件,Vim 会自动使用非兼容模式。
" 写在这以防配置文件是通过其他方式引入的(比如文件另存为了 foo,并通过 <code>vim -u foo</code> 命令启动了 Vim
set nocompatible

" 打开语法高亮
syntax on

" 禁用默认的开启提示文字
set shortmess+=I

" 显示行号
set number

" 启用相对行号。当前行所显示的数字是实际行号,上面和下面的行都会显示相对的行号,便于跳转
set relativenumber

" 在窗口底部显示状态栏
set laststatus=2

" 如果搜索的关键词全部为小写,则搜索忽略大小写。如果关键词中含有大写字母,则搜索变为大小写敏感
set ignorecase
set smartcase

" 即时显示搜索结果,而非等待按回车之后再显示
set incsearch

" 启用鼠标。虽然你应该尽量避免使用鼠标,但是某些情况下还是鼠标比较方便 
set mouse+=a

" 当使用方向键跳转行数时发出提示
" Do this in normal mode...
nnoremap <Left>  :echoe "Use h"<CR>
nnoremap <Right> :echoe "Use l"<CR>
nnoremap <Up>    :echoe "Use k"<CR>
nnoremap <Down>  :echoe "Use j"<CR>
" ...and in insert mode
inoremap <Left>  <ESC>:echoe "Use h"<CR>
inoremap <Right> <ESC>:echoe "Use l"<CR>
inoremap <Up>    <ESC>:echoe "Use k"<CR>
inoremap <Down>  <ESC>:echoe "Use j"<CR>

" 将缩进符显示为 4 个空格 
set tabstop=4
" 当使用 '>' 缩进时, 使用 4 个空格
set shiftwidth=4
" 按 Tab 键时,插入 4 个空格
set expandtab
" 设置自动缩进,根据文件类型缩进 
set autoindent
filetype plugin indent on

小结

本篇文章介绍了 Vim 的基本思想,入门 Vim 需要了解的基本操作,以及基本的配置项。这篇文章也是在 VS Code 中 启用了 Vim 插件之后编写的。

我自己也是刚用了没几个小时,虽然大部分操作已经比较熟悉,但是还是有些地方会卡住一小会。不过,用得越多,就能发现卡壳儿的地方越来越少,写代码的速度越来越快了。

Vim 是一个非常强大的编辑器,还有很多的功能、插件和配置没有探索到。后续当我用得再多一些,总结出一些高级用法之后,再来分享。

网上也有不少资源可以学习 Vim,比如 Vim Tutor 在命令行学习 Vim 操作。还有人建了一个网页,大家互相比拼谁能用最少的命令修改完一段文本。另外,当你觉得在编辑什么文件时,有些操作过于繁琐,那很有可能确实存在一个更好的方式,这时不妨上网查一下,看看前人有没有相应的解决方法。

入门 Vim 确实是一个循序渐进的过程,一开始很不习惯。但是当你逼着自己用一段时间以后,就会越用越顺手。当然,还是在业余时间偷偷练习吧,熟悉 Vim 操作之前,在搬砖修福报的时候还是保持效率更重要。。

参考链接:

https://missing.csail.mit.edu/2020/editors/

文章同步发布于博客:点击查看