Neovim 的 quickfix 窗口还是比较常用的功能,在这里,我实现了一些 quickfix 窗口内常用的快捷键。
在 quickfix 窗口内,默认情况下 Normal 模式下的 dd
和 Visual 模式下的 d
快捷键是不可用的,会提示:
E21: Cannot make changes, 'modifiable' is off
但是有时候,会感觉 quickfix 展示的结果太多了,有些内容不是我所需要的。使用如下代码实现了 Normal 模式下 dd
删除光标下的结果,Visual 模式下 d
删除选中行的结果。
local group = vim.api.nvim_create_augroup('quickfix_mapping', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'FileType' }, {
pattern = 'qf',
group = group,
callback = function(ev)
vim.keymap.set('n', 'dd', function()
local qflist = vim.fn.getqflist()
local line = vim.fn.line('.')
table.remove(qflist, line)
vim.fn.setqflist(qflist)
vim.cmd(tostring(line))
end, { buffer = ev.buf })
end,
})
local function table_remove(t, from, to)
local rst = {}
for i = 1, #t, 1 do
if i < from or i > to then
rst[#rst + 1] = t[i]
-- else
-- i = to + 1
end
end
return rst
end
vim.api.nvim_create_autocmd({ 'FileType' }, {
pattern = 'qf',
group = group,
callback = function(ev)
vim.keymap.set('v', 'd', function()
local esc = vim.api.nvim_replace_termcodes('<esc>', true, false, true)
vim.api.nvim_feedkeys(esc, 'x', false)
local qflist = vim.fn.getqflist()
local from = vim.fn.getpos("'<")[2]
local to = vim.fn.getpos("'>")[2]
if from > to then
from, to = to, from
end
qflist = table_remove(qflist, from, to)
vim.fn.setqflist(qflist)
vim.cmd(tostring(from))
end, { buffer = ev.buf })
end,
})
这里主要是为了实现一个功能,移除文件路径匹配输入表达式的结果。
local group = vim.api.nvim_create_augroup('quickfix_mapping', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'FileType' }, {
pattern = 'qf',
group = group,
callback = function(ev)
vim.keymap.set('n', 'c', function()
local input_pattern = vim.fn.input('filter pattern:')
-- vim.cmd('noautocmd normal! :')
local re = vim.regex(input_pattern)
local qf = {}
for _, item in ipairs(vim.fn.getqflist()) do
if not re:match_str(vim.fn.bufname(item.bufnr)) then
table.insert(qf, item)
end
end
vim.fn.setqflist(qf)
end, { buffer = ev.buf })
end,
})
上述代码,设定的快捷键为 c
, 按下快捷键后会弹出一个出入提示,输入一段字符串,可以是 Vim 正则,回车后会移除 quickfix 窗口中文件名称匹配字符串的结果。
如果需要保留文件名匹配字符串的结果,可以增加一个新的快捷键,比如 C
,代码如下:
local group = vim.api.nvim_create_augroup('quickfix_mapping', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'FileType' }, {
pattern = 'qf',
group = group,
callback = function(ev)
vim.keymap.set('n', 'C', function()
local input_pattern = vim.fn.input('filter pattern:')
-- vim.cmd('noautocmd normal! :')
local re = vim.regex(input_pattern)
local qf = {}
for _, item in ipairs(vim.fn.getqflist()) do
if re:match_str(vim.fn.bufname(item.bufnr)) then
table.insert(qf, item)
end
end
vim.fn.setqflist(qf)
end, { buffer = ev.buf })
end,
})
上述功能汇总为插件 quickfix.nvim.
2018 年 8 月 28 日,我在 CSDN 博客上面发表过一篇文章:《Vim 中使用正则表达式》。 随着 CSDN 账号的注销,这篇文章已经无法在更新,于是将文章迁移至个人博客,并加以更新,增加 Neovim 相关内容。
经常在网上看到有人抱怨 Vim 的正则表达式太奇怪,无法接受。我倒是觉得 Vim 的正则表达式比较容易理解。 可能是因为我最早接触的正则表达式就是 Vim 的正则表达式吧,正好借此机会整理下 Vim 的正则表达式相关的内容。
首先,在哪些情况下会用到正则表达式?
使用正则表达式的命令最常见的就是 /
和 ?
命令。其格式如下:
/正则表达式
?正则表达式
另一个很有用的命令就是 :s[ubstitute]
(替换)命令,将第一个//
之间的正则表达式替换成第二个//
之间的字符串。
:s/正则表达式/替换字符串/选项
在学习正则表达式时可以利用 /
命令来练习。
元字符 | 说明 |
---|---|
. |
匹配任意字符 |
[abc] |
匹配方括号中的任意一个字符。可以使用 - 表示字符范围,如[a-z0-9] 匹 配小写字母和阿拉伯数字。 |
\d |
匹配阿拉伯数字,等同于[0-9] 。 |
[^abc] |
在方括号内开头使用^符号,表示匹配除方括号中字符之外的任意字符。 |
\d |
匹配阿拉伯数字,等同于[0-9]。 |
\D |
匹配阿拉伯数字之外的任意字符,等同于[^0-9]。 |
\x |
匹配十六进制数字,等同于[0-9A-Fa-f]。 |
\X |
匹配十六进制数字之外的任意字符,等同于[^0-9A-Fa-f]。 |
\w |
匹配单词字母,等同于[0-9A-Za-z_]。 |
\W |
匹配单词字母之外的任意字符,等同于[^0-9A-Za-z_]。 |
\t |
匹配字符。 |
\s |
匹配空白字符,等同于[ \t]。 |
\S |
匹配非空白字符,等同于[^ \t]。 |
如果需要查找一些特殊字符,如 *
、.
、/
等,可以在这些字符前面添加 \
,表示这些不是元字符,而是普通字符。比如:\/d
匹配的是 /d
这两个字符,而不是匹配任意数字。
表示数量的元字符
元字符 | 说明 |
---|---|
* |
匹配0-任意个 |
\+ |
匹配1-任意个 |
\? |
匹配0-1个 |
\{n,m} |
匹配n-m个 |
\{n} |
匹配n个 |
\{n,} |
匹配n-任意个 |
\{,m} |
匹配0-m个 |
表示位置的符号
元字符 | 说明 |
---|---|
$ |
匹配行尾 |
^ |
匹配行首 |
\< |
匹配单词词首 |
\> |
匹配单词词尾 |
使用示例
命令 | 描述 |
---|---|
/char\s\+[A-Za-z_]\w*; |
查找所有以char开头,之后是一个以上的空白,最后是一个标识符和分号 |
/\d\d:\d\d:\d\d |
查找如 17:37:01 格式的时间字符 |
:g/^\s*$/d |
删除只有空白的行 |
:s/\<four\>/4/g |
将所有的 four 替换成 4,但是 fourteen 中的 four 不替换 |
在正规表达式中使用 \(
和 \)
符号括起正规表达式,即可在后面使用\1
、\2
等变量来访问 \(
和 \)
中的内容。
使用示例
命令 | 描述 |
---|---|
/\(a\+\)[^a]\+\1 |
查找开头和结尾处a的个数相同的字符串,如 aabbbaa,aaacccaaa,但是不匹配 abbbaa |
:s/\(http:\/\/[-a-z\._~\+%\/]\+\)/<a href="\1">\1<\/a>/ |
将 url 替换为http://url的格式 |
:s/\(\w\+\)\s\+\(\w\+\)/\2\t\1 |
将 data1 data2 修改为 data2 data1 |
在替换命令 :s/{pattern}/{string}/[flags]
中可以使用函数表达式来书写替换内容,格式为
:s/替换字符串/\=函数式
在函数式中可以使用 submatch(1)
、submatch(2)
等来引用 \1
、\2
等的内容,而submatch(0)
可以引用匹配的整个内容。
使用例
:%s/\<id\>/\=line(".")
将各行的 id 字符串替换为行号
:%s/^\<\w\+\>/\=(line(".")-10) .".". submatch(1)
将每行开头的单词替换为 (行号-10).单词 的格式,如第11行的 word 替换成 1. word
Vim语法 | Perl语法 | 含义 |
---|---|---|
\+ |
+ |
1-任意个 |
\? |
? |
0-1个 |
\{n,m} |
{n,m} |
n-m个 |
\( 和 \) |
( 和 ) |
分组 |
在 Vim 里,默认是贪婪模式,即 a.*b
会尽可能多滴匹配字符,在 ahdbjkbkls
中匹配 ahdbjkb
而不是 ahdb
。
如果是非贪婪的,可以使用 \{-}
代替 *
,即 a.\{-}b
匹配 ahdb
而不是 ahdbjkb
。
Neovim 目前主要的配置语言是 Lua,并且 Lua 并没有 VimL 的 =~#
和 =~
比较符。但是 Neovim 提供了一个 vim.regex
Lua 模块。
vim.regex
模块常见的使用方式:
if vim.regex('http[s]*://'):match_str(str) then
-- do something
end
熟悉 Vim 或者 Neovim 的用户对于其状态栏(statusline)和标签栏(tabline)应该也不陌生,对于这两个 UI 组件上面的点击事件, 早期是只有 tabline 上面支持点击操控 Vim 的标签页。
很早以前,就有用户想在 vim-airline 插件的标签栏实现 Buffer 切换的功能, 但是似乎直到现在,Vim 都未增加该功能。
2016 年,Neovim 增加了 tablineat 特性,使得用户可以在自定义状态栏和标签栏时,
指定某个区域设定对应的点击事件回调函数,以此实现监控状态栏和标签栏的点击事件。可以使用 has('tablineat')
检测当前 Neovim 是否支持该特性。
以下内容摘自 :h statusline
@ N Start of execute function label. Use %X or %T to end the label,
e.g.: %10@[email protected]%X. Clicking this label runs the
specified function: in the example when clicking once using left
mouse button on "foo.c", a `SwitchBuffer(10, 1, 'l', ' ')`
expression will be run. The specified function receives the
following arguments in order:
1. minwid field value or zero if no N was specified
2. number of mouse clicks to detect multiple clicks
3. mouse button used: "l", "r" or "m" for left, right or middle
button respectively; one should not rely on third argument
being only "l", "r" or "m": any other non-empty string value
that contains only ASCII lower case letters may be expected
for other mouse buttons
4. modifiers pressed: string which contains "s" if shift
modifier was pressed, "c" for control, "a" for alt and "m"
for meta; currently if modifier is not pressed string
contains space instead, but one should not rely on presence
of spaces or specific order of modifiers: use |stridx()| to
test whether some modifier is present; string is guaranteed
to contain only ASCII letters and spaces, one letter per
modifier; "?" modifier may also be present, but its presence
is a bug that denotes that new mouse button recognition was
added without modifying code that reacts on mouse clicks on
this label.
Use |getmousepos()|.winid in the specified function to get the
corresponding window id of the clicked item.
按照前面的帮助文档描述的内容,可以使用格式 %N@function_name@text%X
来指定区域监控点击事件,
回调函数的名称为两个 @
标记之间的字符串,%
与 @
之间的数字作为第一个参数。
写一个测试函数,看一看点击事件的具体执行效果:
function! OnClick(a, b, c, d) abort
echom a:a
echom a:b
echom a:c
echom '>' . a:d . '<'
endfunction
set tabline=xxxxxxx%1@OnClick@11111%X%2@OnClick@22222%X
将以上文件保存为 vim 文件并执行 :so %
后,状态栏变成了
xxxxxxx1111122222
, 鼠标左键单击 11111
区域,看到如下输出。
1
1
l
> <
上述四行分别对应函数的四个参数:
%
与 @
之间的数字l
)、右键(r
)或者中键(m
)c
)、Shift 键(s
)、Alt 键(a
)、Meta 键(m
)shift/alt/ctrl
。在使用前面的测试脚本测试的过程中,发现第二个参数返回的是点击的次数,其结果不是完全可预期的。为了更加方便测试,将测试的脚本修改如下:
function! OnClick(a, b, c, d) abort
echom '点击次数:' .. a:b
endfunction
set tabline=xxxxxxx%1@OnClick@11111%X%2@OnClick@22222%X
多次点击后,其结果如下:
点击次数:1
点击次数:2
点击次数:3
点击次数:4
点击次数:1
点击次数:2
点击次数:3
点击次数:4
点击次数:1
点击次数:2
点击次数:1
点击次数:2
点击次数:3
点击次数:4
点击次数:1
点击次数:2
上面的输出可以看到,当连续点击时,回调函数会在每次点击时被调用。并且点击次数会增加,
当间隔时间超过 mousetime
时,点击次数计数归零,
间隔时间可以查阅 :h mousetime
, 默认为 500 毫秒。
最近,我在 Reddit 上面分享了一个新的代码格式化的插件 format.nvim, 回复大多是是在问为什么要做这个插件以及跟现有插件的区别。
在 SpaceVim v2.3.0 之前,一直使用的代码格式化插件是 neoformat, 这个插件是使用 Vim 脚本写的,同时支持 Neovim 和 Vim。
但是美中不足的地方是这个插件执行格式化命令是调用 Vim 的 system()
函数,当命令执行消耗时间很长时,就会卡住界面无法进行下一步操作。
我也尝试过给 neoformat 增加异步支持,但是考虑到其他几个原因,还是决定重新写一个插件。
其实 neoformat 的插件代码实现逻辑还是非常好的,因此我也是参考了 neoformat 的实现逻辑, 使用 Lua 来实现了这个异步代码格式化的插件 format.nvim。 这个插件使用了 SpaceVim 的 job api 可以异步执行格式化命令,使得 Neovim 的操作更加顺畅。
对于 SpaceVim 用户来说,只需要在启用 format 模块的时候,指定格式化方法为 format.nvim 即可,配置如下:
[[layers]]
name = 'format'
format_method = 'format.nvim'
虽然 format.nvim 是基于 SpaceVim 的 Job API 实现的,但是该插件已经包含的改 API 的必须文件, 因此可以任意插件管理器独立安装,例如使用 vim-plug:
Plug 'wsdjeg/format.nvim'
以 Lua 语言为例,设置使用 stylua 命令进行格式化:
require('format').setup({
custom_formatters = {
lua = {
exe = 'stylua',
args = { '-' },
stdin = true,
},
},
})
1、格式化整个 Buffer
format.nvim 只提供了一个命令 :Fromat
, 执行该命令时就可以根据当前文件的文件类型选择对应的 formatter 对整个 Buffer 进行格式化。
2、选中区域进行格式化
:Format
命令支持指定区域进行格式化,因此可以在 Neovim 中选中几行代码进行格式化。
3、指定文件类型
通常执行 :Format
命令时,会读取当前 Buffer 的 &filetype 选项,但是如果需要指定其他文件类型,比如 java,可以使用如下格式执行命令:
:Format! java
4、指定 formatter
如果一个文件类型有多个 formatters,可以在执行改命令时指定一个 formatter 的名字,比如 :Format prettier
前面提到 :Format
命令支持指定区域格式,这里需要借助一个插件 context_filetype.vim。
通过这个插件获取到代码块的区域和文件类型,传给 Format 命令。
以下示例使用 <Leader>f
格式化光标所在的代码块:
function! s:format_code_block() abort
let cf = context_filetype#get()
if cf.filetype !=# 'markdown'
let command = printf('%s,%sFormat! %s', cf.range[0][0], cf.range[1][0], cf.filetype)
exe command
endif
endfunction
nnoremap <silent> <Leader>f <cmd><sid>format_code_block()<cr>
前段时间整理自己的个人维基发现笔记太零散,很难快速定位到自己需要的笔记。 以往借助自己的 flygrep 插件根据记忆的关键字倒是可以找到相关的内容, 但是随着笔记越来越多,关键字筛选已经不足以快速定位了。 于是就想找一个更好的方式来组织管理日常的笔记,后来就了解到了《卢曼卡片盒笔记法》
最开始,我是想找一个三端(手机、电脑、网页)同步的笔记软件,但是似乎没有合适的。因为都是文本编辑,而且如果要使用卡片盒笔记法的话, 那么笔记就需要遵循一定的格式。日常使用 Neovim 也非常多,几乎文本的编辑都是在 Neovim 内完成的,索性就不再舍近求远去找笔记软件了, 就直接在 Neovim 内完成。检索了下 Github 发现还是有不少现成的插件实现,但是功能似乎都不是很完善。
因此自己 Fork 了一个插件,在这个插件的基础上做了一些功能的改进和增加,有兴趣的话欢迎尝试:vim-zettelkasten。
浏览笔记列表时,展示笔记的引用数量以及被引用数量,同时列出笔记包含的 Tags 列表,如下图所示:
同时,在 ZkBrowser 窗口内支持的快捷键包括:
快捷键 | 功能描述 |
---|---|
q |
退出 ZkBrowser 窗口 |
<LeftRelease> |
鼠标左键点击 Tag,筛选包含该 Tag 的笔记 |
gf |
打开光标 ID 下的笔记 |
Ctrl-l |
清除 Tags 筛选,列出所有笔记 |
Ctrl-] |
使用 preview-window 窗口预览笔记 |
[I |
使用 quickfix-window 列出 References |
F2 |
打开 Tags 列表侧栏 |
侧栏的效果图如下,可以在侧栏窗口中使用回车键或者鼠标左键点击 Tag 实现筛选:
侧栏中,标签按照首字母归类,鼠标左键点击归类的字母,可以折叠这些标签。
当笔记 Tags 非常多的时候,可以使用 telescope.nvim 进行检索,检索后默认的回车键行为是列出包含该 Tag 的所有笔记。
默认的快捷键为 SPC m z g
。
笔记的标题(Title)也可以使用 telescope.nvim 进行检索,默认的快捷键是 SPC m z f
。
在编辑笔记过程中,可以使用快捷键 ctrl-x ctrl-u
来打开补全窗口,补全引用的笔记 ID。
如果是使用 SpaceVim,可以通过 zettelkasten 模块选项设置笔记模板的文件夹,新建一些常用的笔记模板,比如:
[[layers]]
name = 'zettelkasten'
zettel_dir = 'D:\me\zettelkasten'
zettel_template_dir = 'D:\me\zettelkasten_template'
通常基于默认新建笔记直接使用 SPC m z n
快捷键即可,如果需要基于其他模板来新建,可以使用快捷键 SPC m z t
调用 Telescope 检索常用模板。
因为 SpaceVim 默认是兼容 Neovim 和 Vim 的,因此后面是有计划让这个插件也支持 Vim 的,但是为了减少重复实现的一些代码,可能是需要 Vim 有 +lua
特性。
新的工作环境感觉不是想象中那么理想,但是也在努力去适应。 最近感觉也比较压抑,本来周末想去看的一部电影,因为一些事情耽搁了,今晚抽空一个人去电影院看了。
正如电影的名字一样,即便是生活很多时候不是很如意,但是有些人还是很善于发现身边的一些“好东西”。
可能是因为自己工作的原因,很多时候太过追求结果,而忽略了过程中一些美好的事情。
曾今的我一直固执的认为谎言就是谎言,没有善恶之分,因为总有方式更好地呈现事实。 但很多时候,一些善意的谎言其实可以让事情变得更加简单。
其实,说实在的,电影不是特别的好看,很多情节、对话太过于刻意。 就感觉正常的两个人对话,不该是这样,很多时候刻意的制造一些“鸡汤”类的说教, 或者刻意的制造一些搞笑的对话。
但是,其中确实有很多“鸡汤”值得去好好想想,比如:
1、小女孩上台表演前的那段对话,有时候因为害怕别人的眼光、说辞、看法, 而退缩,其实,这个社会上,在意你的人,真的没有你想象的那么多。 可能明天,他就会忘记你所做的事。
2、又比如小孩的作文,我最喜欢的、以及出现了两次的跟幻想相关的话题。 我觉得小孩子还没有这样的思想,更多的是电影呈现给观众的一些观点。 是的,即便是现在,后面的生活,还有很多年,你永远不会知道自己最喜欢的将会是什么,还是要保留一些幻想。