首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
59 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
emacs
Java
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
309
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
59
篇与
的结果
2022-07-15
从零开始配置 vim(4)——键盘映射的一些技巧
通过前面的学习,我们已经知道了如何进行键盘映射,并且也知道了在任何场合应该使用非递归版本的映射。这篇再介绍一些使用关于快捷键映射的内容作为收尾快速编辑 vimrc 文件通过前面掌握的知识,相信各位小伙伴已经在配置文件中加入了不少配置了。不知道小伙伴们加配置是怎么加的呢?是不是先进入配置文件,编辑完了之后退出,再重新进入看看配置是否生效呢了?又或者在使用vim编程的过程中觉得需要加入某个属性或者特性。这个时候是不是要先退出当前编辑的文件,或者聪明一点的小伙伴知道使用 :vs 命令新建一个窗口打开配置文件。但是在编辑完了配置文件,想让它生效还是得先退出vim,然后再进入。如果配置出错,就得不断的退出进入。这个过程是很耽误编程的。有没有想过怎么改进呢?我们学了快捷键的映射,完全可以定义两个快捷键用于快速打开和应用配置文件。在完成这个事情的过程中需要了解绍一个变量和一个命令。要介绍的变量是 $MYVIMRC 它代表当前vim使用的配置文件的路径,针对neovim来说,如果你使用init.vim作为配置文件,那么它的值就是 ~/.config/nvim/init.vim。如果你用 init.lua 作为配置文件,那么它的值就是 ~/.config/nvim/init.lua 。只要你的配置写的位置是正确的,使用它总能对应上要介绍的命令就是 :source 命令。熟悉 shell 的应该对这个命令不陌生,shell 中,使用 source 来使对应的配置文件生效。vim中它也是使vim的配置文件生效。两个命令的用法也一样,都是跟对应配置文件的路径。使用它配合 $MYVIMRC 能做到在不退出vim的过程中重新加载配置有了这两个东东,我们就可以定义出快捷键了vim.api.nvim_set_keymap("n", "<leader>ee", ":vs $MYVIMRC<CR>", {silent = true, noremap = true}) vim.api.nvim_set_keymap("n", "<leader>ss", ":source $MYVIMRC<CR>:q<CR>", {silent = true, noremap = true})它的效果如下这里我使用 ee 来打开主要考虑到 e 代表的是 edit ,s 代表 s 或者 source 比较好记,各位小伙伴可以先使用其他方便记忆的按键。如果在写完配置之后,不想按 :w 来保存,完全可以将 <leader>ss 映射为 :wq<CR>:source $MYVIMRC<CR>。将保存和启用一并使用。我自己是属于那种稍微改一点就要按 :w 来保存的人。我这里就只需要启用就行。在保存并启用之后,我这里又用了 :q 来关闭之前打开的新窗口。现在你就又可以专注于当前的编程任务了。学会使用新映射的快捷键从插入模式回到普通模式有这么几种方式 <esc>、<Ctrl+[>和 <Ctrl +c>。但是不管哪种方式都存在比较难按的情况,手指需要移开较大距离我们可以使用快捷键映射的方式,将 jk 映射为退出键(这里就可以使用上面定义的快捷键快速的打开配置文件了)vim.api.nvim_set_keymap("i", "jk", "<esc>", {silent = true, noremap = true})这个时候我们似乎用 <esc>习惯了,已经形成肌肉记忆了,只要一想到退回到普通模式,就下意识的使用 <esc>,我们辛辛苦苦分析建立的更高效的快捷键完全没有用处。又或者有 vim 高手指出,要学会更快速的移动光标应该摒弃 j、k、l、h 这些按键,但是明明知道用w或者 f 能更快速的移动光标,但是有时候就是控制不住手,手不自觉的就按到这些键上面去了,就像我在使用 notepad 时总是无意识的输入 <esc>:wq 来保存退出一样。该如何处理这种情况呢?一种有效的办法是将它们都设置为无效键,当你下意识的使用到这些被无效的键时发现它没任何作用,此时你会意识到自己将它们定义为了无效键,同时你也会想起来该使用之前定义的那些更高效的替代品。虽然开始你可能不太习惯,但是一旦形成记忆,你会跟以前一样得心应手。我们可以使用 <nop> 来定义一个键失效, 例如我可以使用 :inoremap <esc> <nop>。定义之后在插入模式下就无法使用 <esc> 来退回到普通模式了。在 lua 中就可以使用如下语句来取消 escvim.api.nvim_set_keymap("i", "<esc>", "<nop>", {silent = true, noremap = true})我们可以看到,在写下那段配置之后,<esc> 已经无法从插入模式退回到普通模式了,只有使用 jk 才能退回。
2022年07月15日
6 阅读
0 评论
0 点赞
2022-07-13
从零开始配置 vim(3)—— 键盘映射进阶
严格意义上来说,快捷键的绑定应该是键盘映射,将某些键映射为另一些键。在上篇我们介绍了基本的键盘映射操作,知道了如何 :map、:imap、:vmap、:nmap这些命令来映射键盘快捷键。它们很方便,也很简单,但是有一个致命的缺点。他们是递归的,我们先来讨论什么是递归映射的递归问题让我们先来执行下面的命令:nmap jj J :nmap J j这里我们原本是想 jj来实现 J的功能,更加快速的实现合并行的功能,但是我们按下之后发现,它只是将光标移动到下一行了。这并不是我们想要的。到底发生什么了呢?因为这些命令是递归的。如何理解递归呢?我们以函数的思想来考虑,每定义一个快捷键,就相当于定义了一个函数。并且在新定义的函数中调用老函数。依照这个思路我们来分析一下上述两个命令产生的结果:首先定义了一个名为 jj的函数,它的函数代码为 J()然后我们定义了一个名为 J的函数,它的代码定义为 j()我们执行 jj函数的时候,它在函数内部调用 J(),J函数内部调用 j()。因此它表现出来的最终效果就是 jj等效与 j。这些映射产生的伪代码如下:void J() { j(); } void jj() { J(); }我们在调用 jj 这个函数的时候就相当于在调用 j有点绕是不是呢。为了讲述这个问题,我们再来看这么一个例子:nmap dd o<esc>kddj我们来分析一下它的本意:首先使用 o在光标所在行之下插入一行退回到普通模式,并且让光标向上移动一行删除光标所在行移动到下一行,也就是刚刚的插入行看起来这个命令的作用是清除本行,但是vim并没有这么做,只有按下 <C-c>才能停下来,而且vim中多出了许多空白行。依照上述分析思路,我们可以对这个命令的执行结果写出如下的伪代码void dd() { o(); esc(); k(); dd(); j(); }从上述的代码看出这个映射会陷入无限循环,或者叫死递归。我们只能使用 来终止。在正式进入下一步之前让我们先删除这个映射。我们可以使用 :nunmpa 来删除一条快捷键映射,输入 :nunmap dd 来终止上述出错的映射。之前介绍的那些映射命令都有 un 系列的命令,例如 map 对应 unmap,imap 对应 iunmap 。我们从上面的几个例子应该看出来了, 之前介绍的函数好用是好用,但是会形成递归。在上述代码中还算是比较好找,如果我们配置文件大了,不同插件有自己的映射,而我们也会定义一堆自己的映射,这个时候出问题就难查了。为了解决这个问题,vim提供了一系列的 nore 开头的函数。它相比于之前介绍的函数来说,是非递归的。之前每个命令对应的非递归版本如下:nmap 对应 nnoremapimap 对应 inorempapvmap 对应 vnoremapcmap 对应 cnoremap相信各位应该看出来了,我们在原来命令的基础之上添加了 nore 作为非递归版本。我们来做一个试验:nmap x dd :nnoremap \ x我们输入 \ 发现它只删除了一个单词,即使用 :nnoremap 只保留了 \ 作为 x 操作符的作用,而斩断了之前 x 被映射为 dd 的操作。那么我们何时该使用递归版本,何时使用非递归版本呢?答案是在任何时候,永远使用非递归版本,现在就请各位小伙伴忘掉非递归版本把。现在多敲几个单词将来会省去大量排错时间。lua 配置到此位置我们学会了怎么使用 :map 系列的命令定义快捷键,同时也知道什么是快捷键之间的递归和非递归。可以说掌握了关于 vimscript 定义快捷键的基本方法。那么如何跟 lua 对应呢?neovim 定义了一系列的函数帮助我们定义、获取和删除快捷键vim.api.nvim_set_keymap: 设置快捷键vim.api.nvim_get_keymap: 获取快捷键vim.api.nvim_del_keymap: 删除快捷键我们可以通过帮助文档查到 vim.api.nvim_set_keymap 的定义如下:nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts})mode 是一个字符串,对应着一个模式,即我们之前说的可视模式、插入模式或者普通模式,下面是各个模式对应的字符串名称 。字符串模式对应的vim 命令""所有模式:map"n"普通模式:nmap"v"可视模式:vmap"i"插入模式:imap"s"选择模式:smap"c"命令模式:cmaplhs 对应着一个键位,即我们想映射的键位,如果传入空字符串,相当于通过 :map传入 <NOP>。表示将要禁用这个键rhs 对应着将要执行的命令,是 :map 的第二个参数opts 代表映射的其他属性,主要是一个表,你可以暂时理解为一个字典。比如可以使用 noremap 表示禁止递归,使用 silent 表示执行命令时不回显内容例如我们在配置文件中定义vim.api.nvim_set_keymap("n", "<space>", "/nvim<CR>", {noremap = true, silent = false})通过这段代码,我们将 空格键映射为在文件中查找 nvim 字符。因为 silent 设置的是 false 因此我们在按下空格键之后在vim 的左下角会看到 /nvim 的字样我们可以通过设置 silent = true来取消这个回显。vim.api.nvim_set_keymap("n", "<space>", "/nvim<CR>", {norema=true, silent = true});映射leader 键常见的映射主要出现在普通模式下,普通模式下的很多按键都有其特殊用途,而且还大多挺常用的,想来想去不怎么使用,而且位置好按的也就 <space>、H、L、D 这些了,将他们进行映射,映射到常用功能,减轻我们的按键负担是再好不过了。但是我们常用功能又那么多,特别是装了插件之后的。这些键完全不够使用的。这个时候我们要延续 emacs 或者其他软件的思路了,一个键不够就两个键,比如使用下面的映射:noremap -d dd :noremap -c ddO这意味着我们可以使用一个键作为前缀,后面接其他字符,将他们作为一个整体来映射一个功能。多按一个键而已,比你输入整个命令要轻松多了。这就引入一个新的思路了,我们可以统一定义一个键作为前缀键,后面添加一些字符来整体进行映射。当然我们可以手工这么做,每次需要多个按键的时候的就手动写上 - 或者其他的。但是后期我发现 - 并不容易按到,我觉得 <space> 或者 ,更容易按到,要进行修改,那么修改的量就太大了。vim 中有一个被称之为 leader 键的东西来解决这个问题,我们可以提前定义一个 leader ,在映射的时候使用 <leader> 来代表对应的前缀键。例如,先设置 leader 为 <space> 后面再定义 <leader>d 作为 dd,后面可以很方便的修改 <space>d 这个映射为 ,d,只需要修改 leader 键的定义。可以使用 :let mapleader = <space> 来定义 leader 键为空格。这里你可以定义成你喜欢的键,我平时喜欢用空格,因为它比较大,平时也在大拇指的位置,方便按。然后我们可以使用 :noremap <leader>d dd 来定义映射如何在 lua 中定义 leader 键呢?从上面的 vimscript 代码中可以看到 mapleader 是用 let 关键字来设置的,一般 let 是用来设置变量的。lua 中自定义变量可以直接定义,但是 mapleader 明显是vim 自带的变量。跟设置选项类似,neovim 提供了两种访问 vim 内部变量的方式,一种是使用函数,一种是使用 元访问器。跟变量有关的函数主要有:vim.api.nvim_set_var():设置全局变量的值vim.api.nvim_get_var():获取全局变量vim.api.nvim_del_var():删除全局变量当然使用元访问器会更加简便,对应的元访问器为 vim.g。所以这里我们可以使用 vim.g.mapleader = " " 来设置到此我们已经学会了关于映射的所有初级的内容,现在已经可以完成大部分的配置工作了。至于在定义快捷键的时候是使用 leader键还是使用多个普通键,看具体场景和各位小伙伴的使用习惯了。这里我就不给建议了,一切以方便好按为主。
2022年07月13日
8 阅读
0 评论
0 点赞
2022-07-11
从零开始匹配vim(2)——快捷键绑定
如果说 vim有什么最吸引人,我想vim允许你自由的定义各种快捷键算是一个原因吧。你可以通过绑定各种快捷键来使经常使用的功能更加便利。通俗的讲,快捷键映射就是我按下某个键,我想让vim将它当成另一个键,例如我按下 k,我想让vim把它当做 c 来使用(当然这么映射会把人逼疯)快捷键映射我们使用 :map 命令来进行快捷键的映射,例如我们输入如下内容:map - dd通过这个命令,我们将 - 映射为 dd ,也就是说现在我们按下 - 就可以删除光标所在行了。针对功能键,例如 Ctrl 、空格、Tab键 等,在 vim 中使用 <keyname> 的形式,其中 keyname 是按键名称,下面列举出一些常用的控制键的表示方式:Ctrl 键对应 <c>空格 键对应 <space>alt 键对应 <a> esc 键对应 <esc>退格键对应 <bs>回车键对应 <cr>shift 键对应 <shift>f1 到 f12 对应 <f1> 到 <f12>这些功能键与普通字母做配合时,将字母键放入到 <> 中,并以 - 和 功能键做分割,比如 :map <c-d> dd 来将 <Ctrl +d> 映射为 dd当然有时候为了可读性,我们可以将这些功能键以大写字母来表示,例如 <C-d> 就表示 <Ctrl +d>快捷键映射就是这么简单,接下来要做的就是思考如何进行映射能提升我们的效率,即提取常用的操作将他们映射为快捷键。我们来通过几个小例子来给小伙伴们提供一些思路。定义 -为将当前行往下移动移动我们知道,在普通模式下要将当前行往下移动,可以首先执行 dd 删除一行,然后执行 p 来拷贝到下一行,因此我们可以这样定义快捷键:map - ddp定义 _ 为将当前行往上移动移动在这个例子中,第一步仍然是执行删除,但是与上一个例子不同的是,我们要将删除部分往上一行粘贴。如果往上一行粘贴呢,在之前介绍的时候我们并没有提到如何执行这么一个操作。如果你的第一反应是要去翻用户手册或者去查其他的资料,那么我建议你仔细去阅读一下我之前写的内容。体会一下vim的一些思想和技巧。仔细回想一下 o(小写)和 O (大写) 的区别,而且我们曾经提到过,普通模式下,大写字母和小写字母功能类似。说到这我想你应该知道答案了。我们使用 P来粘贴到上一行。因此这里的映射可以写成:map _ ddP如果你觉得这两个映射对你有用,你可以将它放入你的 vimrc 中,或者先记下来,等我们介绍完使用 lua 配置之后,再写到 init.lua 中。其他模式下的映射map 可以定义映射,但是各位小伙伴可以做一下试验,map 命令定义的快捷键在 visual 模式下仍然有效。这样就会带来一个问题,如果我想在多个模式下都对 - 这个键进行了定义,而且定义成了不同的快捷键,这该怎么处理呢?其实 vim 针对不同的模式有不同的快捷键定义命令,例如 :nmap 负责在普通模式下定义快捷键, :vmap 负责在 visual 模式下定义快捷键。这个时候你肯定已经猜到了,:imap 可以在插入模式下定义快捷键,或者使用 :cmap 定义命令行模式下的快捷键。这些命令的格式与 :map 一样。下面还是以例子为主吧例1: 重新在普通模式下定义 -例如我们使用 :nmap 重新定义 - 为 ddp ,这个时候我们发现它只在普通模式下有用,而在选择模式下无效。例2: 定义 为复制粘贴不知道有没有小伙伴在初学vim的时候希望也能像在Windows中那样,使用 <Ctrl+c> 和 <Ctrl+v> 来进行复制粘贴。学到现在我们终于有能力自己动手实现这个需求了。虽然不推荐这么干,但是还是满足各位折腾的欲望吧。仔细回想一下,<Ctrl+c> 实际上是拷贝选中的文本,而<Ctrl+v> 是在输入的时候直接粘贴的。因此我们知道映射 <Ctrl+c> 应该是在 可视模式下。而<Ctrl+v> 应该是在插入模式下。另外在插入模式下是不支持使用 p 来进行粘贴的。还记得我们在插入模式下介绍的那些技巧吗?其中有一个快速从寄存器中粘贴的技巧。正好可以在这里用到。而复制操作会将复制的文本拷贝到寄存器中(也就是 0寄存器)。结合这两个知识点,我们可以很方便的映射出这两个快捷键:vmap <C-c> y :imap <C-v> <C-r>0虽然它仍有一些不完美,但是已经够用了。最后还是不推荐这么干,毕竟使用 y 等操作就能搞定的事情没必要弄的这么麻烦。而且在linux平台,特别是在终端上,这些键都另有用途。例如 <C-c> 在vim中可以退回到普通模式。例3: 定义 <c-d> 为在插入模式下删除一行我们再举一个例子,我想在插入模式下使用 <C-d> 来删除光标所在行。vim 在插入模式下并没有提供快捷键来删除一行,需要使用退格键一个个的删除,想要快速删除可以回到普通模式使用dd。那么我们可以使用 :imap <c-d> <esc>dd 。我们会发现按下 <c-d> 之后它成功删除了一行,但是它自己退回到普通模式去了,我们想要的是它能在删除一行后仍然处于插入模式,这样我们就可以直接输入了,因此我们可以在后面新加一个再次进入插入模式的步骤,即 :imap <c-d> <esc>ddi 。再次尝试,我们发现它已经满足我们的需求了。例4: 在插入模式下定义快捷键快速转换单词为全大写我们定义一个在插入模式下能快速将光标所在单词改为全大写字母。例如 C/C++ 语言的编程规范中都会讲到宏或者常量最好以大写字母命名。例如 #define MAX_CONNECTIONS_QUEUE 100 这样长的宏定义,我们在输入的时候要一直按着 shift 键是很痛苦的事情。我们需要有这么一个快捷键我们可以先输入小写字母然后使用快捷键将它改为全大写。首先我们思考一下,不使用快捷键该是如何操作呢?首先我们将光标移动到单词所在位置并退回到普通模式,接着使用 viw选中,然后使用 U来改为大写。根据这一串操作命令,我们可以就知道了该如何定义快捷键了,:imap <c-u> <esc>viwU 。根据上面的一个例子我们知道,这样它最终会停留在普通模式下面,我们还是希望能够回到插入模式,考虑到我的使用场景是,我在写完整个单词之后会立马将它转化为大写,然后再编辑后面的其他部分,因此我将光标移动到被大写的单词的尾部,并且进入插入模式。现在命令就变成了 :imap <c-u> <esc>viwUwa。这里你可以根据需要灵活的进行调整,例如可以改为 :imap <c-u> <esc>viwUA,在行尾进入插入模式。最后做一个总结。本篇提到的比较重要的定义快捷键的命令有:map: 定义所有模式下的通用快捷键imap: 定义插入模式下的快捷键 (insert)nmpa: 定义普通模式下的快捷键 (normal)vmap: 定义选择模式下的快捷键 (visual)
2022年07月11日
7 阅读
0 评论
0 点赞
2022-07-11
从零开始匹配vim(1)——选项设置
前面我们算是对 vimscript 做了一个入门,并且实现了一个 输出 hello world 的语句。现在我们继续进行 vimscript 的学习。set语句之前在介绍 vim 基础的时候,我们说要开启或者关闭某个属性,并且给出了相关的配置代码。那个时候已经接触了 set 语句了。例如我们使用 set number 来显示行号。这里我们可以知道 set的第一个用法:set attribute其中这个 attribute 是一个属性名称,用于开启/关闭属性。设置布尔值一般在程序设计中将开启或者关闭某个特性的变量设置为 bool 值。在 vim 中也是如此,但是它的特殊性在于它并不是将这个变量设置为 true 或者 false 而是设置为 name 或者 noname 的样子。举个例子,设置显示行号使用 set number 设置不显示行号使用 set nonumber 。另外对于 bool 类型的属性值还可以使用 ! 来对现有值进行取反,使用 ? 来查看当前使用的值。例如,如果当前不显示行号 set number! 会显示行号,再次执行则 :set number! 则不会执行。我们可以使用 set number? 来查看当前是否显示行号下面我们来执行这么一个例子set number set number? set number! set number?第一个 set number? 应该会返回 number 而第二个应该会返回 nonumber设置键值对有些值是属于 bool 类型,我们只需要 set 某一选项即可。但是有些属性并不适合设计成 bool 类型,例如窗口宽度、高度等等。他们更适合做成一个键值对的形式。针对这种形式的属性,我们直接使用 set key=value 的形式。例如 set columns=80 。对于这种类型的属性仍然可以使用 ? 来显示当前的值。例如 set columns? 来显示当前每行的最大字符长度。当然,与其他编程语言类似,我们也可以使用 set 语句,一次性给多个属性进行赋值,例如set number columns=80只是一般编程语言多个变量的赋值使用 , 作为分割,但是 vimscript 中使用 空格。使用 lua 设置vim属性作为一门更加规范的编程语言,lua 提供了多种方式来对这些变量进行赋值。目前有如下办法能对这些属性进行赋值。使用neovim提供的全局函数进行设置neovim 中提供了一组函数来设置这些属性。常见的分为三类:设置全局属性 a. vim.api.nvim_set_option() :设置值 b. vim.api.nvim_get_option() : 获取值设置窗口相关属性 a. vim.api.nvim_win_set_option() : 设置值 b. vim.api.nvim_win_get_option(): 获取值设置缓冲区相关属性 a. vim.api.nvim_buf_set_option() :设置值 b. vim.api.nvim_buf_set_option() : 获取值例如我们可以使用 vim.api.nvim_set_option('number', true) 来设置 显示行号。使用这些函数时需要严格区分对应属性是 bool、数字或者字符串类型。在 vimscript 中则没有这么严格的区分。使用元访问器进行设置neovim 的接口针对上述这些函数进行了一定的封装,提供了一组元访问器,以便我们能够像使用普通变量一样使用这些属性值。至于什么是元访问器暂时不用关心,只需要知道我们可以像使用变量一样来设置和获得这些属性,但是本质上还是在调用上述那些函数。针对上述函数,我们可以使用如下几种访问对象:vim.o: 全局属性,我们可以利用 options来记忆vim.bo: 缓冲区属性,我们可以利用 buffer-option来记忆vim.wo: 窗口属性, 我们可以利用 window-option来记忆例如我们可以使用 vim.o.number = true 或者 vim.o.columns = 80 来设置对应选项。等效于 vim.api.nvim_set_option("number", true) 和 vim.api.nvim_set_option("columns", 80)总结通过这篇文章,我们讨论了使用 set 来设置一些选项,并且给出了对应的使用 lua 来设置的方法。下面小伙伴们要做的就是读一下 vim 的用户手册,根据自己的喜好尝试着设置一些其他选项。例如我喜欢的基本配置如下:vim.o.syntax = "enable" vim.o.relativenumber = true vim.o.number = true vim.o.wrap = true vim.o.ruler = true vim.o.incsearch = true vim.o.softtabstop = 4 vim.o.shiftwidth = 4 vim.o.expandtab = true如果是vim可能需要的配置更多,但是 neovim 没有历史包袱,默认的已经很好用了最后,目前教程采取这种 vimscript 穿插着 lua 的方式进行讲解,后续配置虽然主要以 lua 为主,但是有时候又离不开 vimscript,neovim并没有完全开放 vim 的内置功能,有些功能只能使用 vimscript 来实现。目前我不太确定是继续采用这种 vimscript 穿插着 lua 进行讲解还是采用将 vimscript 和 lua 分开进行讲述。各位小伙伴觉得哪种方式更能接受呢?
2022年07月11日
7 阅读
0 评论
0 点赞
2022-07-06
从零开始匹配vim(0)——vimscript 简介
通过之前一系列的文章,相信各位小伙伴应该已经对vim产生了浓厚的兴趣,可能不少小伙伴通过慢慢的使用变的跟我一样,离开vim就不会写代码了。如果你希望继续长时间使用vim,甚至将vim作为主要的代码编辑器,那么花一定的时间来学习vim的配置是很有用的。如果你只是因为 linux 平台默认安装了 vi/vim ,平时又主要用它改改配置文件,临时用用,那么还是赶紧退出这个文章,免得耽误时间吧。标题上说从零基础打造vim多少有点标题党,要理解后面一系列文章的内容至少要了解vim里面的一些基本概念,我认为在阅读本系列专栏之前,你应该了解以下内容知道vim,并且知道 vim 的基本操作,例如进入插入模式、移动光标,并且知道如何保存退出知道vim的相关术语,缓冲区列表、缓冲区、窗口、命令模式、插入模式等等知道 vim的配置文件在哪3. 知道 vim的配置文件在哪如果你还不知道,请移步我的专栏,跟着专栏中的内容做做实验。理解一下里面提到的这些概念。关于配置部分,我准备介绍以下几个方面的内容:vimscript 的基础内容,并补充一些之前没介绍过的vim的特性neovim lua对应的接口推荐一些插件和相关配置,做到抛砖引玉可能有小伙伴要问了,vimscript 不管写还是读都比较晦涩,而且 neovim 已经采用更容易理解也更加现代化的 lua了,有必要学习 vim script吗?我觉得是有必要的,首先 neovim 是 vim 的另一个实现版本,要想学好 neovim 自然绕不开 vim,而学好 vim 自然也绕不开 vimscript 。其次,lua 脚本中的接口继承自 vimscript 。想要使用 lua 配置的前提是知道使用 vimscript 配置。它们就好像学习 MFC 绕不开 Win32 API 一样。所以我觉得想要学习 neovim 的配置自然绕不开 vimscript 。而且 neovim 完全兼容 vim 基本不用担心 vimscript 在 neovim 中执行不了的问题。 倒是 neovim 并没有实现 vimscript 百分之百的功能,有些时候还是得靠 vimscript 来实现。演示环境之前一系列的文章本来打算在 mac 上做演示的,但是我发现 mac上录屏比较麻烦,而且 mac上的 neovim 我已经配置好了,行为上可能跟裸 vim 有些差距(虽然可以不加载配置文件启动),所以我采用 WSL2 Ubuntu上的 neovim做演示。刚好这个演示环境没有对vim进行过配置。也方便演示从0开始配置。本教程的环境是 linux中 neovim 的 0.6.1 版本,虽然官方已经放出来 0.8 版本,但是Ubuntu官方源中好像还是 0.6。这里就继续用它演示吧第一个 vimscript 脚本我学习的任何一门语言好像都是从打印 hello world 开始的,我也不能免俗。我们这个教程也从打印 hello world 开始吧vimscript 中可以使用 echo 和 echom 来打印语句,例如我们可以在命令模式中输入 echo "hello world"。我们发现在屏幕的下方出现了 hello world 字样。接着我们再使用 echom "hello world" 发现,它仍然在下方打印了 hello world 字样。这两者有什么区别呢?要理解它们的区别我们需要介绍一个新的命令 :message 。这条命令可以查看 message-history 中的内容,我们可以简单的将这条命令理解为查看 vim 的运行日志(虽然并不是在查看日志)。执行这条命令我们发现,message-history 中只有一条 hello world。这个时候他们的区别就出来了,一个在打印的同时会将打印信息写入 message-history,另一个就仅仅只是打印而已。各位小伙伴只需要简单打印不同的语句就能知道 echom 会将内容写入到日志中,而 echo 不会。这里留个各位自己去实验吧,我就不做演示了。我们现在来完成一个小练习,使用vim脚本来写一段欢迎信息例如 hello, jack, 我们让用户每次打开都能看到这个欢迎信息 。这里的用户我们暂时让它固定,后续我们可以做到动态的修改 欢迎的用户。我们知道每次打开vim,它都会去加载配置文件,我们只要将要执行的命令写到配置文件中就好了。还记得配置文件在哪吗?针对 neovim 来说它支持 vimscript 和 lua 配置(当然它也支持其他语言)。我们将目录切换到 ~/.config/nvim 。如果各位小伙伴之前有过配置,那么请先将配置做一个备份。同时确保 init.lua (lua配置)和 init.vim (vimscript 配置) 只能有一个我们先尝试着用 vimscript 的方式来完成这个功能。我们建立一个新文件 ~/.config/nvim/init.vim。然后在里面写上一句 echo 'hello, jack',然后退出。我们发现每次进入 neovim 之后,总会在下方显示这么一句话了。我们再来看看如何使用 lua 来打印这么一句话呢?lua 中对应的函数是 print 。我们可以直接在 之前备份的 init.lua 中写上 print("hello, jack") 来完成这一个工作(记得现将之前的 init.vim 备份)。需要注意的是 lua 中的 print 写将信息一并写入到 message 中,也就是说它与 echom 效果相同。如果想执行 echo 操作可以使用 api.vim.nvim_echo() 。它的用法比较复杂,这就就不介绍它了,有兴趣的小伙伴可以去官网查一下,试试用它来实现这个打印欢迎信息的例子。这篇文章的内容到此就结束了,最后给大家做一个总结:neovim如果想要加载 vimscript配置,可以将配置文件放到 ~/.config/nvim/init.vim 中,如果想要加载 lua 配置,可以将文件放到 ~/.config/nvim/init.lua 中。echo、echom 都会打印字符串,不同的是 echom 将打印的内容往一个叫做 message-history 的地方写。lua 中的 print 具备 echom的功能可以使用 message 命令查看 message-history 的内容从 vimscript 的使用上看,它里面写的是vim的一些命令,我们将命令写在文件中让vim去执行。vim命令与 vimscript 的关系有点像 shell 命令和 shell 脚本的关系
2022年07月06日
6 阅读
0 评论
0 点赞
2022-07-04
vim 从嫌弃到依赖(23)——最后的闲扯
截止到上一篇文章,关于vim的基础操作都已经讨论完了,这篇我主要就是闲扯,瞎聊。就想毕业论文都有一个致谢一样,这篇我们就作为整个系列的致谢吧学习vim到底能给我们带来什么学习vim到底能给我们带来什么呢?工作中很少有用会用vim来做主力编辑器,现在有各种现代化的编程工具,像JB 全家桶、visual studio 系列。它们从上手难度和集成化程度来说,都做的比较好,离开vim也能编程。而且vim本身也不能给你的简历带来什么亮点,没有公司招人的时候会要求熟练掌握vim、也没有人在简历上写自己熟练使用vim。面试时也没有面试官会问你vim相关的内容,反倒是你用学习vim的时间去学一门新的编程语言,像 go、rust 之类的能给你带来一份新的工作,能带来涨薪。从这个上面看,学习 vim 似乎变得有那么些鸡肋甚至有一种耽误时间的感觉。我最开始学习vim的初衷是,我需要在linux下进行一些环境的搭建,例如Apache、nginx、或者其他的服务。在linux的终端中,vim算是标配,几乎每个linux服务器都会安装vim。那个时候我只会按 i 进入插入模式、按方向键移动光标。它对我来说就是一个linux上比记事本还麻烦的一个文本编辑器。后来我看了 《程序员修炼之道》、《程序员的呐喊》这两本书,书中提到:我们程序员平时会花大量的时间与代码、文本编辑器打交道,如果每天抽出一些时间花在优化编辑器上将会大有好处。而且《程序员的呐喊》这本书十分推崇emacs。我遵照书中的意思学了一段时间的emacs ,发现emacs 中最高效的编辑方式是一个叫做 evil 的插件,它是一个 vim 的模拟器。而且后续我了解到很多编辑器都有相关的vim插件。这个时候我开始意识到vim并没有我之前想象的那么简单。随着emacs 的学习和使用进入瓶颈,我意识到要想用好 emacs 首先还是得学会如何使用 vim。这个时候我立马入了 vim 的坑。而且通过学习 vim,我越来越觉得 vim 给我带来的好处大于学习任何一种编辑器。首先,vim 最被推崇的是它的一套文本操作方式,例如为了减少快捷键的按键次数以及为了更好的抽象现实中编辑文本的场景,它使用了分模式的办法,不同模式下不同按键有不同的功能。在这它将文本进行分级,分为字符、单词、字串、句子、段落并且提出了文本对象的概念,极大方便了我们处理文本的速度。同时它也有 . 命令和 宏的操作,进一步简化了重复操作。再者它极大的继承了 unix 的哲学,专门的软件做专门的事并且将这个事做到极致。它很方便的和外部程序做集成,扩大了功能范围。最后就是它的高可定制性,使用者可以方便的根据自身的工作场景做定制,做出符合自己的编辑器。如今 vim 已经不单单指一个软件了,而是一整套完善的文本编辑技术,学好了这个,后续在任何编辑器上都可以用到,例如各种编辑器IDE都支持vim 快捷键或者vim 插件。可以做到无缝切换编辑环境。另外学习 vim 给我培养了一种解决问题并从中学习的思路:根据实际场景提出问题-->找到解决办法--> 寻找更好的办法-->学到新知识-->将新知识-->使用新的知识更好的解决之前的问题。我想我通过前面的一些文章已经传递了这一思想,例如根据所学知识不断的完善 在每行最后添加分号 这一操作。使用 . 或者 宏来操作重复内容等等。甚至还有小伙伴在评论区给出更好的解决方案,这些都是这一思路的体现。由于不断有新知识,而且知识可以很快的运用到工作中。学习vim的过程有很好的正反馈,以前需要不停用鼠标点或者需要自己手工完成的操作,现在只需要几秒钟或者几分钟就由vim自动完成的这一喜悦使我在学习vim的过程中一直乐在其中。我想这就是我学习vim和使用vim的意义和快乐所在吧写这一系列文章的心路历程最开始学习vim的时候我很困惑,读vim的用户手册显的干巴巴的,读的头昏脑涨,记得的不多,基本合书就忘。网上的教程很多都是直接罗列命令,跟用户手册差不多。或者直接写一堆配置告诉你vim可以配置的很好用。这些充斥着网络,但是又不是我这种初学者需要的。我希望的是有一个教程在实际使用中循序渐进的帮我掌握vim这些知识点,需要一个教程跟着它进行操作我就能理解并在实际中使用vim。我需要一个vim手册和实际使用的一个桥梁。很遗憾的是我没有找到我想要的教程。那么我就自己写吧,我相信肯定有vim的初学者跟我当初遇到的情况一样,我想把我心目中认为最适合我的教程写出来,没准它也适合其他人。在写这一系列文章的过程中,我深刻体会到,在如今互联网环境中,一旦写出什么东西,这个东西马上就不属于你。当然这个并不是再说有人抄袭之类的。我只是在感叹互联网中的内容传播速度,从我更新第一篇注水的内容开始,马上就有人关注并且给我评论说支持我。这无疑给我了很大的勇气,让我慢慢更新完这一系列文章。中间也有白天上班、晚上整理资料、写博客,白天在抽时间发表的时候,有时候也挺累,偶尔也想休息一下或者断更,中间有想法更新我觉得更有意思的内容。但是想想那些因为我的这些文章而关注我的人,不知道他们看到我在断更之后会是怎样的失落,这种好不容易找到适合自己的本想好好跟着学习,结果却被迫中断的这种心情,我很能体会。既然如此那就硬着头皮更新吧。这一系列的文章一旦开始立项、更新,那么它就不属于我自己,而属于各位希望通过这些文章学到点东西的小伙伴。我有义务将它们一一发布出来。中间也有不少小伙伴私行我,告诉我他们通过我的文章学到很多东西,也重拾了对 vim 的兴趣。这些鼓励的声音给了很大的帮助。特别是 知乎的用户 @ugvibib。最开始是他一直在给我评论分享自己的学习心得,也是他不停给我私信提醒我某些地方有错别字或者排版有错误。这些文章有他一部分的功劳,感谢 @ugvibib 负责给我校对。在写这些文章的过程中,我真的体会到“您的点赞关注评论是对我最大的支持”这句话并不完全是骗赞,骗流量的空话。我在更新这一系列文章的过程中也真心的希望得到反馈,得到关注,让我知道我写的这些破玩意还是有人看的,有人能从中获益,这些并不是我自己在这自说自话,自嗨,这些也并不是网络垃圾。有人关注、有人评论和点赞给我了继续更新下去的动力。后面该干什么关于vim的基础操作到此为止就全部更新完了,但这并不是vim的全部。后面该如何学习vim呢?通读vim用户手册,相信通过这些文章的学习各位小伙伴再重读vim手册也不会感到无所适从。我们可以从vim手册中找到比我介绍的更适合自己的操作方式。形成一套只适合自己的操作流程开始学习 vimscript 慢慢定制自己的vim并不是所有的场合都允许使用 vim 的。下面可以考虑将vim 的操作方式转移到其他编辑器上,例如 visual studio code、emacs 等等。最后感谢各位关注和鼓励我的小伙伴,下一个专栏我想继续写vim相关的。暂时定为写 vimscript 和vim配置相关的内容吧。
2022年07月04日
9 阅读
0 评论
0 点赞
2022-07-01
vim 从嫌弃到依赖(22)——自动补全
这篇文章我们将讨论 vim 自带的自动补全功能。当然,针对自动补全功能有许多好用的插件,但是了解vim自带的功能有助于我们更好的用来插件的补全功能。因为我见过有的配置文件将插件的功能配置的比原有的更难用,而且只用基本的功能不一定有原版的好用。所以这里也介绍一下原始版本用法,算是帮助各位在以后的配置中提供一个标杆。make 命令在了解自动补全之前,让我们先简单聊聊 :make 这个命令,它与上一篇文章中介绍的 :grep 命令类似,也是对 shell 命令的一个封装。它默认封装的是 make 命令。我们对 c/c++ 语言执行 :make 也就是在调用 shell 中的 make 命令。它会将编译产生的错误信息存储在 quickfix 列表中。我们上一节中介绍了如何操作 quickfix 列表。也介绍了如何对 :grep 命令进行改造。同样的 :make 也支持使用相同的方法进行改造。:make 命令中,使用 makeprg 来执行外部命令,使用 errorformat 来格式化输出到 quickfix 中。它们默认的值如下:makeprg="make" errorformag="errorformat=%*[^"]"%f"%*\D%l: %m,"%f"%*\D%l: %m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\,,%-GIn file incl uded from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,"%f"\, line %l%*\D%c%*[^ ] %m,%D%*\a[%*\d]: Entering directory %*[`']%f',%X%* \a[%*\d]: Leaving directory %*[`']%f',%D%*\a: Entering directory %*[`']%f',%X%*\a: Leaving directory %*[`']%f',%DMaking %*\a in %f,%f|%l| %m"可以调整它们的值来适配不同的外部命令。这里就不再详细展开了,相信阅读过上一篇文章的小伙伴对这个应该不陌生。本来 :make 命令是vim中十分有用的一个命令,应该单独写一篇文章的。但是它于 :grep 重复度太高了,所以我决定在介绍其他内容的时候一笔带过。想了解详细信息的可以参考vim的用户手册。自动补全自动补全可以在插入模式下触发,当我们触发补全功能的时候,vim会根据当前编辑会话中所有缓冲区的内容建立一张补全列表,然后根据当前光标左侧的字符进行检测,看在表中能否找到单词的一部分,能找到则会用这个未完成的单词对补全列表进行过滤,所以不是以它为开头的单词都被过滤掉,剩余的组成一个弹出式菜单供用户选择。效果如下:上述例子中,因为以 re 开头的原本只有 require 一项,为了展示补全效果这里我们新增一个以 re 开头的 return我们使用 <Ctrl +p> 和 <Ctrl + n> 来切换补全菜单中的上一条和下一条。除了这个,我们还有其他的用于操作补全菜单的快捷键。<Ctrl - n> : 使用来自补全列表中的下一项内容(next)<Ctrl - p> : 使用来自补全列表中的上一项内容(prev)<Down> : 与 <Ctrl -n> 相同<Up>: 与 <Ctrl - p> 相同<Ctrl -y> : 确认使用当前选中的匹配项<Ctrl - e> : 还原最初的输入项<Ctrl -h> : 从当前匹配项中删除一个字符<Ctrl - l> : 从当前匹配项中增加一个字符一般在输入字符的时候,如果有匹配项可以匹配vim会自动弹出,或者也可以手动使用 <Ctrl - n> 弹出匹配项菜单。在确定要使用的匹配后可以使用 <Ctrl-y> 来确认有时候虽然弹出了匹配项菜单,但是匹配项太多了,而你需要的单词又在列表的太后面,这个时候可以使用 <Ctrl - e> 来退出菜单,手动输入几个字符使匹配项更加精确。或者也可以输入 <Ctrl -p> 到达最开始的项,即我们目前的输入,然后再次输入字符来精简菜单项,接着使用 <Ctrl - n> 弹出菜单。使用这种方式来一步一步的逼近我们想要的结果自定义补全项来源默认情况下,vim 补全项主要来源于以下几个地方:缓冲区列表:vim补全项最基本的来源就是当前的缓冲区列表。它可以通过 <Ctrl - x><Ctrl - n> 来触发该项。包含文件,所有的编程语言都有包含文件的概念,例如 c/c++中的 #include , python 中的 import 。使用 <Ctrl-x><Ctrl-i> 可以触发这个选项,让vim从被包含文件中提取补全项。vim本身使用 c 语言编写的,它能够识别 c/c++ 语言中的关键字,我们可以指定 include 项来使 vim认识其他不同的关键字。一般常用的编程语言 vim 都能够识别,因此不需要修改 include 项。标签文件,我们使用 ctags 或者类似的插件的时候会生成一个标签文件,该文件会将扫描到代码中的关键字、函数、变量等的索引放入到一个文件中以供后续进行跳转。同时他们也会产生一系列的补全项到补全列表中。可以使用 <Ctrl+x><Ctrl+]> 来触发一般直接使用 <Ctrl + n> 触发的是当前缓冲区列表中的补全项,使用 <Ctrl+x> 作为前缀,可以触发其他类型的补全项。这么做有一个好处是尽量精简补全列表,减少了我们手动遍历的过程。但是有时候我们并不知道我想要的内容该从哪里来,有没有什么办法能做到,用 <Ctrl + n> 这个按钮就可以调用其他所有来源的补全项呢?要做到这点,可以使用 complete 这个配置项。该项包含一组由逗号分隔的单个字符表示的参数,当参数出现时表示需要扫描该参数代表的位置。使用 set complete? 可以看到,缺省项为 complete=.,w,b,u,t 。我们可以使用 set complete-=i 或者 set complete+=k 来删除或者添加某个扫描位置。常见的位置参数如下所示:. : 表示当前以打开的缓冲区w : 当前打开的窗口b : 当前缓冲区列表u : 当前处于缓冲区列表中,但是未打开的缓冲区t : 当前标签文件U : 当前打开的,不属于缓冲区列表中的缓冲区k : 从字典文件中加载的补全项i : 从当前文件和包含文件中读取d : 从当前文件和包含文件中读取使用 define定义的宏完整的内容可以使用 :h 'complete' 来查看。使用字典文件在上面的论述中,我们可以知道 vim 是可以自定义补全的字典文件,然后从字典中产生匹配的。我们可以使用 <Ctrl-x><Ctrl-k> 来加载字典中的匹配项。我们可以使用 set spell来启动拼写检查,拼写检查也会产生新的字典文件。如果不想使用该项,也可以使用 set dictionary来指定含有一个或者多个单词的字典文件。在这个例子中我们在 nvim-config 目录中新建一个 spell.txt 文件,我们在里面写入如下内容require return request然后使用 set dictionary=./spell.txt ,接着删除 init.lua 中的 return ,输入 re 然后使用 <Ctrl+x><Ctrl+k> 这个时候我们发现它已经加载了补全整行除了补全单词,vim还可以补全整行的内容,使用 <Ctrl+x><Ctrl+l> 可以触发补全整行的操作。补全行的补全项来源与补全单词相同,需要注意的是补全行的操作会自动忽略行首的缩进。补全行的操作与之前介绍的 yy 或者 :t 产生的效果相同,我们应该要根据实际情况分别使用。补全文件名在 shell中输入命令可以使用 <Tab> 键来自动补全文件路径,vim中使用 <Ctrl+x><Ctrl+f> 来对文件路径和文件名进行补全。需要注意的是当我们使用相对路径来补全文件名时,使用的是工作目录,也就是你从哪个目录中进入的vim。我们可以在 vim中使用:cd来切换工作目录。例如我在 nvim-config这个工程的根目录执行 nvim init.lua,我们在这个文件中希望快速补全 basic/settings.lua这项,我们发现它在补全的时候报错这个时候我们可以使用 :cd lua 来切换工作目录到 nvim-config/lua 。这个时候再执行补全命令就可以了。根据具体编程语言生成补全上述补全在编辑普通文本的时候显的有点用处,但是作为程序员平时在写代码如果只能使用上述方式进行补全肯定会抓狂的。好在vim 提供了像其他IDE那样的基于编程语言的补全方式。使用该补全方式的快捷键为 <Ctrl+x><Ctrl+o> 要启用该方式,需要启动文件类型识别。nvim 中已经启用了这一特性,因此不必特意进行设置,但是这里我还是给出它的配置。vim.o.filetype="plugin"或者vim中可以使用如下代码set filetype=plugin set nocompatiable # 设置与vi 不兼容例如我们可以尝试着在 css 文件中使用补全vim 本身也确实支持很多语言的自动补全,但是为了获得完整的体验还是推荐使用各种专门的补全插件获得更好的体验最后的总结在这边文章中,介绍了vim中补全项主要的几个来源分别是:当前缓冲区和缓冲区列表、包含文件、外部程序生成的标签等等。同时也介绍了如何使用快捷键来进行不同项的补全,现在对这些快捷键总结如下:<Ctrl + n> : 普通关键字补全,主要来源自缓冲区列表和当前缓冲区<Ctrl+x><Ctrl+n> : 与 <Ctrl+n>作用相同<Ctrl+x><Ctrl+i> : 从包含文件中获取补全项<Ctrl+x><Ctrl+]> : 从外部标签中获取补全项<Ctrl+x><Ctrl+k> : 从字典文件中获取补全项<Ctrl+x><Ctrl+l> : 补全整行<Ctrl+x><Ctrl+f> : 补全文件名<Ctrl+x><Ctrl+o> : 根据编程语言来进行补全
2022年07月01日
11 阅读
0 评论
0 点赞
2022-06-28
vim 从嫌弃到依赖(21)——跨文件搜索
之前介绍了vim中的搜索模式,使用正则表达式可以很方便的在一个文件中进行搜索。后续也介绍了如何使用 argsdo 命令在参数列表中进行替换操作。但是到目前为止还没有介绍如何在工程目录中进行搜索,而这个功能是其他编辑器的基本功能。vim 主要运行在 unix 平台,而 unix 平台信奉的哲学是专门的软件做好专门的事,在多个文件中搜索关键字是 grep 这个程序的工作,vim本身并没有单独提供类似 grep 的功能,而是提供了方法直接调用 grep。本篇我们将要讨论在vim中是如何调用 grep 进行搜索的。并且介绍其他搜索整个工程中代码的方式。提前声明一下,因为vim中 grep 命令与 shell中的 grep 重名了,容易造成误解,因此这里采用 :grep 来表示 vim 中的 grep 命令,grep 来表示 shell中的 grep,也就是 vim中的命令都以 : 开头grep 命令vim 中也提供了 :grep 命令,它是对 shell 中 grep 的封装。它可以让我们直接在vim中使用grep并且可以在vim中显示结果(当然我们也可以在命令模式中使用 :!grep 来调用shell的 grep 命令)。我们仍然以前面介绍的搜索 TODO 标签为例。我们先在 shell 中使用 grep 命令。grep -n "\-\- TODO" **/*.lua因为 --TODO 中的 - 在shell中是传参的标志,所以这里需要进行转义。-n 表示在输出的结果中显示行号。**/*.lua 表示在所有lua文件中进行搜索。我们可以看到,它输出了我们想要的结果。我们该如何根据这个结果快速跳转到对应位置呢?例如要跳转到 lua/basic/settings.lua 的第5行, 我们可以在 shell 中可以使用 nvim lua/basic/settings.lua +5 表示打开到该文件并跳转到第5行。当我们要频繁不同文件间进行跳转的时候,要频繁的退回到 shell 并执行 vim 来打开,操作上比较繁琐。vim 为这种需求提供了自己的工具—— :grep 命令和 :vimgrep在vim中输入 :grep "\-\- TODO" **/*.lua 会发现 vim 在下方显示了当前所有搜索到的内容。这里我们没有加上 -n 选项,但是它仍然显示了行号,vim默认自动为 grep 添加了 -n 选项。这些内容被存储在一个被称之为 quickfix 的列表中。可以通过这个列表快速跳转到对应的位置。遍历 quickfix 列表quickfix 列表是由我们执行 :make 命令或者 :grep 命令所产生的,它会保存一个或者多个文件位置信息。我们可以使用以 c 开头的一组命令来遍历,下面列举出相关的命令:cnext:跳转到下一项cprev:跳转到上一项cfirst:跳转到第一项clast:跳转到最后一项cnfile:跳转到下一个文件的第一项cpfile:跳转到上一个文件的第一项cc n:跳转到第你项copen:打开 quickfix列表cclose: 关闭 quickfix列表后续使用 vim 时会大量使用到 quickfix 列表,为了减轻输入的负担,可以考虑将其定义为快捷键。:cnext 和 :cprev 命令前面可以加数字表示向后或者向前跳转多少次。例如我这里使用 :2cnext 表示向后跳转2次。我们可以使用 :copen 来使用新的窗口来显示 quickfix 列表中的内容。在这个窗口中可以使用 motion 命令来移动光标。quickfix 列表无法进行修改,因此这里只能移动光标。它比较特别的一点在于,如果我们在某一行按下回车键,那么vim会自动跳转到光标所在行对应的位置。quickfix 所在窗口总有一项处于高亮状态,这个状态表示当前我们在访问哪个位置的内容,我们可以通过窗口跳转来改变高亮的行,执行 :cnext 和 :cprev 以及 cc 之类的命令也可以修改当前高亮的行。例如我在这里执行 :cc 2 来跳转到第二条记录另外 vim 会自动保存之前产生的 quickfix 列表,并不会随着执行新的 :grep 而发生覆盖。我们可以使用 :colder 来查看上一个列表,使用 :cnewer 来查看下一个。定制 grep命令vim 中的 :grep 是对 shell 中的 grep 的一个封装。前面说道,vim 中的 :grep 命令会默认加上 -n 这个选项,而 grep 还可以使用 -i 来忽略大小写,我想把这项也加入到 :grep 命令中该如何做呢?另外 :grep 是对 shell 中的 grep 的封装,现在我有更好的文本搜索工具,我想用它来替换 grep 该如何做呢?还有一个很奇怪的点,在使用 :grep 进行搜索的时候,我们明明输入的是 :grep "\-\- TODO **/*.lua" 但是它给我们显示结果的时候显示的却是 :!grep -n "\-\- TODO" **/*.lua /dev/null 2>&1| tee /tmp/nvimPRHF8B/6 这是为什么呢?在这一小节我们将来探讨这些问题。当我们通过 vim 来执行 :grep 命令的时候,grepprg 负责制定将要调用的 shell 命令。grepformat 决定如何来 :grep 命令的输出结果。通过使用 :h grepprg 和 :h grepformat 看到,它们自身在 vim 中的默认值如下:grepprg = "grep -n $* /dev/null" grepformat = "%f:%l:%m,%f:%l%m,%f %l%m"在 grepprg 中 $* 表示占位符,它将被 :grep 命令中输入的内容替换,这也就解释了为什么最后在显示的时候,会在我们输入的基础之上加上了后面那些内容。 我们只需要对其做一些修改就可以使我们的 :grep 自动忽略大小写set grepprg=grep\ -n\ -i\ $*我们看到,同样的命令现在多出来了一条小写的结果,另外从它的显示上看也已经加上了 -i 选项了。接下来我们来看看 vim是如何解析 :grep命令输出的。grepformat中各种匹配格式是按照 ,来进行分割。也就是它定义了多组可能的输出格式,每组以 ,分割。%f表示文件名称、%l表示行号,:m表示匹配的行。了解这些之后,我们来试试使用别的命令来替换默认的 grep。这里我们以 ack作为演示,当然你也可以使用其他的命令。插一句题外话,我觉得 ack相较于 grep来说,最大的优势在于它可以识别不同的文件类型,这样就可以做到只搜索某一类型文件中的内容,而且默认支持递归搜索当前目录下所有文件。在 shell 中,可以直接使用 ack "\-\- TODO" 来搜索所有的 todo项,也可以使用 -i 来忽略大小写。在默认情况下 ack 会用两行来显示搜索到的结果,第一行是 文件名,第二行是行号和匹配行的内容。ack 默认会搜索当前目录中所有文件中的内容,所以这里可以不需要像 grep 那样给出具体的目录。我们可以使用 --nogroup来达到与 grep相同的输出格式。我们可以使用 --nogroup 来使 ack 达到与 grep 相同的输出,因此这里也可以不修改 grepformat 的内容。我们只需要修改 grepprg 即可:set grepprg=ack\ --nogroup\ $*另外 ack 还支持添加 --column 来输出对应的列,配合 grepformat 我们可以做到精确定位到对应的行和列。这里我们设置 set grepprg=ack\ --nogroup\ --column\ $*。同时设置 set grepformat=%f:%l:%c:%m从上图中可以看到,此时已经可以显示列号了,并且 grep 已经被替换成了 ack 了vim 提供了很方便的方式让我们修改 :grep 命令的行为。但是我们在执行 :grep 的时候发现它在调用 ack 命令有时候会造成一定的疑惑或者误解。而且并不是每次我都想使用某一个 shell 程序的。例如这次我想用 grep 进行搜索,下一次我想用 ack 搜索,这样每次修改外部命令,我都得修改 grepprg 和 grepformat 想想也挺麻烦的。为什么不创建一个 :ack 命令专门用于使用外部的 ack,或者其他命令专门用于调用其他外部程序呢?目前很多插件都是这么干的。在后续介绍 vim配置的时候我们将会给出这样的例子。vimgrep 简介除了使用 :grep 来调用外部的搜索命令外,vim 自身也提供了 :vimgrep 命令。它最大的特色是支持 vim 自己的正则表达式。它的使用格式如下::vimgrep[!] /{pattern}/[j][g] {file}它的使用方式与之前介绍的 搜索模式类似。只是它只支持2个标志,j 表示不进行跳转只是将匹配结果保存到 quickfix 列表中,默认情况下,它会跳转到第一个匹配的位置,并且将搜索结果保存到 quickfix 中。g 表示将所有匹配都记录下来,默认只记录每一行第一个匹配处。因为它与搜索模式下使用的模式相同,因此这里我们可以先用查找模式来在一个文件中进行试验,试验成功后再使用 vimgrep,否则错误的结果将会污染历史的 quickfix 列表,影响后续使用 colder 和 cnewer 。例如这里我还是搜索 --TODO 可以现在单个文件中使用 :\v--\s+TODO进行搜索。然后使用模式域留空的方式查找,即 :vimgrep //gj **/*.lua关于 vimgrep的内容就介绍到这里了,一般我很少使用原装的 :grep和 :vimgrep。而是采用功能更加强大的其他搜索插件。各位小伙伴也不需要纠结究竟掌握它们中的哪个好,有更好的,直接用更好的就行。
2022年06月28日
6 阅读
0 评论
0 点赞
2022-06-24
vim 从嫌弃到依赖(20)——global 命令
在前面的文章中,我们介绍了如何进行查找和替换,而替换是建立在查找基础之上的一个简单的应用,它只是将匹配文本修改为另一个。那么vim中还能针对匹配上的文本做哪些操作呢?在本篇文章中我们来对这个问题进行探讨。初识global 命令我们能够对存在匹配项的行进行其他操作的关键在于 global 命令。global 命令的作用是存在匹配项的行上执行指定的ex命令。命令的格式如下::[range] g[lobal][!]/{pattern}/[cmd]与大多数ex命令一样,它接收一个作用范围。如果不给范围,则默认作用于整个文件,即它默认范围是 %! 代表取反,是在不存在匹配项的行上执行ex命令pattern 表示匹配模式cmd表示将在对应文本上执行哪些ex命令。如果不指定则默认执行 print命令这里需要强调的是,执行ex 命令操作的是有匹配项的行。操作的不是高亮的文本,而是有高亮文本的行。我们还是以一个简单的例子来演示如何使用#define VERSION "v1.0.1" char pszVersion[] = VERSION; #define TITLE "vim" char* pszTitle = TITLE; #define AUTHOR "Bram Moolenaar" char* pszAuthor = AUTHOR;假设有这么一段代码,我们先用 ".*" 来匹配一个字符串,然后执行 :g//d 来执行删除操作。我们发现它并不是删除了后面的字符串而是将所有有字符串的行都删除了,只保留了赋值语句。相信通过这个例子各位小伙伴应该已经理解global 命令是如何作用的。如果我们要删除上述代码中所有的赋值语句,可以利用 !来进行取反,:g!//d在 《vim 实用技巧》这本书中提到一个很有意思的东西。通过上面的描述,可以总结出 global命令的一个简写形式 :g/re/p 其中 g是 global命令的缩写,re代表正则表达式 regular expression, 而 p 则是 print的缩写。我们将 / 从中去掉就发现这个简写变成了 grep 这个单词。这也就是 grep 这个命令的由来。上面的内容已经初步介绍了 global 命令的使用,下面再来看看其他的使用场景配合缓冲区参数列表使用我们还是用 neovim 的配置文件。我们随机在部分 lua 文件中加一些 TODO 的注释。表示暂时未做将来会实现的功能。我们先在某个文件中查找 TODO字样,有的文件显示没有找到也不要紧,只是为了保存这个模式然后将所有的 lua 文件加入到参数列表中,:args **/.*lua然后选择清空一个寄存器 qaq ,其中 qa 代表我们将要使用 a 寄存器来录制一个宏,不输入任何内容直接使用 q 结束录制。因为宏就是将操作内容写入寄存器,所以不进行任何操作的宏就可以清除寄存器的内容。然后执行 :argdo g//yank A 这里使用 yank 这个命令来复制内容到寄存器。另外使用了 A 而不是 a 因为这里是对每个文件依次执行命令的,需要一个个的添加到寄存器里面。所以这里使用大写字母。此时可以查看 a 寄存器的内容,发现已经有对应内容了。提取出来的内容有一个缺点就是无法显示具体是哪个文件中的 todo 项。将未来要实现但是现在没实现的功能用 TODO 描述出来在编程中是一个很常见的习惯,针对这个功能有许多做的不错的插件,后续将会介绍相关插件。指定 ex 命令的执行范围不光 global可以指定范围,后面接的 cmd也可以指定范围,下面将通过一个演示该如何使用假设有一段 css 代码html{ margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } body{ line-high: 1.5; color: black; background: white; }我们想对每组css内部属性按字母顺序进行排序。第一个办法:可以录制宏来自动化。首先通过模式来匹配 { 即使用 /{ 然后开始录制宏: nvi{:sort ,首先通过 n 来跳转到下一个匹配,然后使用 vi{ 通过文本对象来选中 {} 中的内容,最后针对选中来执行 sort 命令但是我们可以使用 global 命令完成同样的操作。首先还是来构造对应的模式。我们可以通过 :g/{/ .+1,/}/-1 sort 这么一条命令来完成这一操作。我们来分析一下这条命令的意思。首先将这条命令按照 / 分为3个部分,第一部分是 { 表示匹配所有 { 之后的内容。第二部分是 .+1,/}/-1 。它表示一个范围,范围分为两个部分,以 , 分割,前面一部分代表的是当前行的下一行,也就是{ 所在行的下一行,/} 表示匹配结束的 } 符号,后面跟一个 -1 表示 } 所在的上一行,这个范围代表的就是 {} 之间的所有行。最后一个部分是命令也就是 sort,在对应的这个范围中执行 sort 命令。针对上面解释的内容,我们可以对 global命令再做一个详细的使用格式::[range]/g[lobal][!]/{start pattern}/ .{offsize},/{end pattern}/{offsize} [cmd]这里我们也可以跟其他命令,例如将C函数中的代码进行缩进,那么就可以使用 :g/{/ .+1,/}/-1 > 。
2022年06月24日
7 阅读
0 评论
0 点赞
2022-06-22
vim 从嫌弃到依赖(19)——替换
之前讨论了关于在vim中使用正则表达式的相关知识能方便的进行搜索,现在在之前的基础之上继续来讨论如何进行替换操作。substitute 简介substitute 允许我们先查找一段文本并用新的文本将匹配上的文本进行替换。它的使用比较复杂,需要提供一个匹配模式和一个替换的字符串。命令格式如下::[range]s[ubstitute]/{pattern}/{string}/{flag}range 表示范围,与之前介绍的其他 ex 命令中范围的作用一样。pattern 表示一个匹配模式,回忆一下之前说过的,这里的模式跟之前介绍的普通模式、插入模式的含义不同,它代表的是一串用来进行匹配并高亮显示的字符串。string是一串用来进行替换的字符串,将匹配项都替换成某项。flag是一些替换的标志,我们将在后面的内容中进行介绍。例如 :%s/python/Python/g 表示在整个文件中将 python都替换为 Python(这么长时间没怎么提到 ex 命令了,不知道各位小伙伴是否还记得 %代表当前打开的文件)。g 是一个标志位,表示修改整行中的所有匹配项,而不仅仅是修改第一个匹配项。标志位上面的例子中我们使用了一个 g 作为标志位,其实还有其他的标志位。我们可以通过标志位灵活的定义 substitute 的行为。下面是一些常用的标志位:\r:插入一个换行符\t:插入一个制表符\\:插入一个反斜杠\1:插入第一个子匹配项\2:插入第二个子匹配项\0:插入匹配模式的所有内容&:与 \0用法相同~:使用上一次调用 substitute时提供的 string内容\={vim script}:执行 vim script并将返回内容作为 string有这么多标志位,该怎么记,平时怎么用到呢?别急,下面将通过相应的示例来演示如何使用它们,我们完全可以在日常使用中学会它们。示例使用 g 替换所有内容how can I learn python very good, just use it more and more. python is very powerful, you can just learn python within your work.我们将上面文本中的所有 python 都改为 vim 。我们先来看看不使用标志是什么样子的。即这里输入 :%s/python/vim我们发现它只替换了每一行的第一个出现 python 的地方,同一行后面的 python 不受影响。这里我们使用 /g 替换每一处出现 python 的地方。g 这个标志很容易联想到 global 这个单词,应该表示的是整个选中的文本范围,而我们前面已经选定了当前文件中的所有文本,似乎看起来很合理。但是 g 作用范围应该是整行,而我们选中的是文本中的所有行。看起来效果是一样,但是理解起来确实有差距。为什么它会作用于行,我想应该是 vim 发源于 ed 这个编辑器,vim的 ex 命令起源于 ed 编辑器,而后者是一个行编辑器,所以大部分的命令都作用于行。这样应该就能说得通了。手动选择是否需要替换有的时候我们并不希望盲目的对所有内容进行替换,而只替换其中的部分内容。例如上述的文本中,我们只想替换第二行的最后一个 python 为vim。那么可以使用 c 标志。你可以理解为 copy ?。vim会询问我们是否需要进行替换。即我们在这里输入 :%s/python/vim/gc。后面可以按下 y 来确定替换,n 表示不进行替换并切换到下一处匹配。因此这里我们可以输入 nny其实不光yn这两个选项,从vim的提示看总共有 ynaql和 以及 。它们的含义如下:y:替换本处匹配n:不替换本处匹配a:替换此处之后的所有匹配项,随后退出本次替换q:退出本次匹配l:替换此处之后退出本次替换:向上翻滚屏幕:向下翻滚屏幕重用上次匹配模式如果我们将 {pattern} 部分留空,那么 vim会重用上次的 {pattern} 。下面我们使用一个例子来看如何使用这一特性。#define VERSION "v1.01" #define TITLE "vim" #define PATH "~/.config/nvim"我们想将里面的字符串改为宽字符,也就是在双引号前加L我们首先要匹配所有的引号内容。很多时候正则表达式比较复杂,无法一次就写对的,所以这里我们分步骤来,首先正确写出正则表达式匹配出所有带双引号的字符串。 \v\"(.*)\"可以获取所有的字符串。接着我们使用上面的这个模式来进行替换,即输入 :%s//L\0/gc。当然这里只有这么三行一眼就知道我们要替换所有,但是代码一长了,就需要我们来确认是否需要替换。复杂的正则表达式我们无法一次就输对,如果进行替换操作的时候因为正则表达式输入不对导致每次都得重新输入一堆内容就显得比较麻烦了。而且如果使用 substitute 命令之后才发现错了,又得撤销重新输入那么大一串。与 substitute 相比,查找模式不会修改文本,我们可以在查找模式中使用 <Up> 慢慢修改直到满意为止。当模式对了,下面就可以利用 {pattern} 留空这种方式来重用上次模式。需要注意的是将模式留空,将会在历史命令中留下一个不完整的记录,模式与命令是独立存储的。在上面的例子中,如果我又执行了新的匹配,例如我想查找所有 define,后面使用 重新执行命令的时候,发现匹配的内容变了。有一个办法就是将上次的模式存储到寄存器中,在匹配的时候从寄存器中取数据填充 {pattern} 部分。上次匹配成功之后如何将对应的模式放到寄存器呢,这里我们介绍一个新的内容——命令窗口。命令窗口是一个显示历史命令的缓冲区,它跟普通的缓冲区区别仅仅在于它显示的是历史命令而已。使用 q: 可以调出,这里我们可以使用 q/ 调出模式的命令窗口。在对应模式行使用 "iy$ 来粘贴一行,然后在最后替换时使用 <C-r>i 来填充 {pattern}。使用留空这一招有时候很方便有时候又很麻烦,上述两种方式具体如何使用请各位结合具体情况自行判断吧。使用寄存器的内容进行替换{pattern} 域留空了,vim会自动以上一次的模式来进行匹配,那么如果我把替换域留空,是不是会以上次替换的字符串作为这次的进行替换呢?试验过后发现 vim 并不会这样做,它会使用空字符串进行替换(单纯的使用上次的替换字符串使用的是 ~ 这个符号)。如果想要快速填充替换域,可以先进行复制,然后在 substitute 中使用0 寄存器。即,我们可以输入 :%s/{pattern}/<C-r>0/gc 来完成替换。就想上面的例子那样。但是这种方式有一种缺陷,那就是如果复制的内容中有 / & 之类的特殊符号的话,它会出现错误。这个时候我们可以手动编辑寄存器中的内容,对特殊符号进行转义。这个时候我们会想有没有什么办法能让vim知道我只想将寄存器中的特殊符号作为普通字符串呢?当然是有办法的,我们可以借助vim script 来实现这一需求。我们可以输入 :%s/{pattern}/\=@0/gc,其中 \=是我们之前列举的使用 vim script,而后面的 @0 则是 vim script 的内容,表示取 0 寄存器的内容。这里出现了 vim script的内容,不过不用担心,这里涉及到的都是最简单的vim script内容,而且更新完了 vim 的基础内容之后会开一个新专栏介绍vim script和vim的配置,那个时候再回过来看这个方法也可以。这段脚本也很容易理解是不是?使用上一次的 substitute 命令假设我们在执行 substitute命令的时候忘记了在前面添加 %,我们当然可以使用 <Up> 键来在上一次的基础之上进行修改。这里介绍一个更简单的方式,可以在普通模式中输入 g& 它会在整个文件中重新执行上一条 substitute命令。它等效于 :%s//~/&。当你其他部分都正确,唯独忘了添加 %可以考虑使用这条命令。我们再来看另外一种情形:假设我有这么一段代码mini = { applyName: function(config){ return obj.factory(config, this.getName()); }, }我要在此基础之上增加一个新函数,使其变为这样mini = { applyName: function(config){ return obj.factory(config, this.getName()); }, applyNumber: function(config){ return obj.factory(config, this.getNumber()); }, }我们发现,只需要简单的将上一个函数进行复制,然后将 Name 改为 Number 就可以了。我们可以使用 :%s/Name/Number/g 来执行替换,但是有一个问题就是它将所有的内容都进行了一个替换。好在可以使用一次 u来撤销所有修改。在介绍命令模式的时候介绍过,大部分的 ex 命令都可以使用选择模式中选中部分作为命令执行的范围,substitute 同样可以。我们先选中后面要更改的部分,然后使用 :&&来在选中部分重复执行上一次的 substitute 命令。这两个 &具有不同的含义,第一个 & 表示重复上次执行的 substitute 命令,但是它不包含上次指定的标志位,在后面再加一个 & 表示重复上一次的标志位。后面的 &与上面介绍的 g&中的 &含义相同。使用子匹配进行替换假设我有这么一份联系人记录Jack Ma, 12398988011 Pony Ma, 16528649018 Donald Trump, 15092838173 Joe Biden, 18571820986现在我想改变一下顺序,让电话号码在前,人名在后。首先构造一个可以准确匹配到人名和电话号码的正则表达式: (.*),\s+(\d{11}) 。第一个括号匹配的是人名,第二个括号匹配的是电话号码的11位整数。然后我们可以利用之前介绍的 {pattern} 留空的方式,重新组织新的排列格式 :%s//\2, \1使用 vimscript 脚本在上面介绍从寄存器中读取内容进行替换的时候初步介绍了,使用 vimscript 的例子。这里我们再举出一个使用 vimscript 的例子,不过不用慌,使用的脚本都极为简单,不存在理解障碍。假设有这么一段HTML代码<h2> this is a h2 tag</h2> <h3> this is a h3 tag</h3> <h4> this is a h4 tag</h4> <h5> this is a h5 tag</h5>我们希望对标题标签进行升级,换句话说就是将 <h2>,提升为 <h1>,<h3> 提升为 <h2>。首先我们构造模式来匹配对应的数字,可以用 \d 来匹配数字,但是它会匹配到所有数字,因此我们加一个限定,只匹配以 <h 或者 </h开头的数字,这个时候正则表达式可以改为 \<\/?h\d,我们只想要后面的数字,因此可以对这个匹配进行裁剪,\<\/?h\zs\d。这样就匹配到了所有标签后的数字,但是内容里面的数字没有被匹配。接着我们介绍一个新的 vimscript 命令——submatch,它接收一个表示第几个匹配的参数,返回对应的匹配项。我们可以使用这个函数将获取的每个匹配项都 -1,即输入 :%s//\=submatch(0)-1/g 就可以完成这个操作了。最后的总结在这篇文章我着重讨论了 substitute 这个命令的使用,介绍了该命令对应的标志位,并通过一些例子演示了如何使用这些标志位。相信各位对替换命令有了一定的认识。各位小伙伴可能还会有疑惑,目前介绍的查找替换似乎只针对的是某个文件,如果我想在项目中进行全局替换该怎么办呢?请各位想想之前我们是如何在多个文件中执行宏的。这部分就不做介绍,算是留的一个练习吧。至于多个文件进行查找,我们将在后面的部分继续介绍。
2022年06月22日
9 阅读
0 评论
0 点赞
1
...
3
4
5
6