无意间阅读到一篇文章 《震惊!竟然有人在 GitHub 上冒充我的身份!》, 大致看了下 Github 网站对于 Commit 的归属的判断规则。
只需要 Commit 的邮箱在某人的账号设置的邮箱列表内。那么就会将此次提交显示为是某人的归属。这就意味着,任何一个人都可以使用 git config user.email "YOUR_EMAIL"
这一命令设置成别人的邮箱,伪装成他人进行提交。
如果只需要设置邮箱就可以伪装成用户进行提交,那不是乱套了?因此大多数软件维护者都会要求 Commit 签名。Github 其实支持多种方式签名,这里大致记录一下自己设置 GPG 签名的方案。
Windows 系统下,我使用的 scoop 包管理器,通过 scoop install gpg
就可以安装好了。 使用 gpg --version
看一下版本信息。
D:\wsdjeg>gpg --version
gpg (GnuPG) 2.4.7
libgcrypt 1.11.0
Copyright (C) 2024 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Home: D:\Scoop\apps\gpg\current\home
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2
参考 Github 的文档生成并上传 GPG 密钥, 上传完成后使用 https:/github.com/{username}.gpg
格式链接就可以访问你的公钥。
例如我的:wsdjeg.gpg
使用 where gpg
查看 gpg 命令的绝对路径:
d:\wsdjeg>where gpg
D:\Scoop\apps\gpg\current\bin\gpg.exe
清除之前的设置:
git config --global --unset gpg.format
之前导入已备份的私钥后,使用 git commit -S
一直提示没有私钥,找了很久原因才知道,原来需要设置 Git 的签名方式及程序,可能是原来设置过。
git config --global gpg.program "D:\Scoop\apps\gpg\current\bin\gpg.exe"
使用 gpg --list-secret-keys --keyid-format=long
列出 GPG 密钥:
d:\wsdjeg\my-blog>gpg --list-secret-keys --keyid-format=long
D:\Scoop\apps\gpg\current\home\pubring.kbx
------------------------------------------
sec rsa2048/41BB7053E835C848 2021-09-21 [SC]
9C957B574786F570AC69625041BB7053E835C848
uid [ unknown] Shidong Wang (Shidong's GPG key) <[email protected]>
ssb rsa2048/D3E3902EF4E8074C 2021-09-21 [E]
设置 Git 签名的 ID:
git config --global user.signingkey 41BB7053E835C848
设置 Git 默认启用 Commit 签名:
git config --global commit.gpgsign true
当初,Neovim 刚刚推出异步 job 特性的时候,我就使用 Vim Script 写过一个实时检索的插件 FlyGrep.vim。
最开始的实现是使用 :split
命令分屏展示搜索结果,使用 :echo
命令配合 while true getchar()
在 cmdline 内模拟输入框。
但是 :split
命令分屏时,总是带动整个界面其他窗口内容的移动。随着 Neovim 增加悬浮窗口这一特性,
我把 Flygrep 搜索结果窗口及底部提示状态都使用浮窗来实现,这就不会受到原先窗口界面布局的影响了。但是目前 Flygrep 的浮窗还是底部半屏窗口。
现在大多数浮窗插件都是在屏幕中间打开窗口,下面就从零开始一步一步实现一个简单的实时代码检索插件。
最终效果图如下:
首先时窗口界面,整体界面占据窗口中间 80% 区域,分上下两部分,下面窗口仅有一行,作为一个输入窗口,上面窗口作为搜索结果展示窗口。
-- 窗口位置
-- 宽度: columns 的 80%
local screen_width = math.floor(vim.o.columns * 0.8)
-- 起始位位置: lines * 10%, columns * 10%
local start_col = math.floor(vim.o.columns * 0.1)
local start_row = math.floor(vim.o.lines * 0.1)
-- 整体高度:lines 的 80%
local screen_height = math.floor(vim.o.lines * 0.8)
local prompt_bufid = vim.api.nvim_create_buf(false, true)
local prompt_winid = vim.api.nvim_open_win(prompt_bufid, true, {
relative = 'editor',
width = screen_width,
height = 1,
col = start_col,
row = start_row + screen_height - 3,
focusable = true,
border = 'rounded',
title = 'Input',
title_pos = 'center',
-- noautocmd = true,
})
local result_bufid = vim.api.nvim_create_buf(false, true)
local result_winid = vim.api.nvim_open_win(result_bufid, false, {
relative = 'editor',
width = screen_width,
height = screen_height - 5,
col = start_col,
row = start_row,
focusable = false,
border = 'rounded',
title = 'Result',
title_pos = 'center',
-- noautocmd = true,
})
在底部窗口输入内容时,后台自动执行搜索命令,并在搜索结果窗口实时展示。这里需要监控 TextChangeI
这一事件,在事件 callback 函数内调用搜索命令。
local job = require('spacevim.api.job')
local augroup = vim.api.nvim_create_augroup('floatgrep', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'TextChangedI' }, {
group = augroup,
buffer = prompt_bufid,
callback = function(ev)
local text = vim.api.nvim_buf_get_lines(prompt_bufid, 0, 1, false)[1]
if text ~= '' then
local grep_cmd = {
'rg',
'--no-heading',
'--color=never',
'--with-filename',
'--line-number',
'--column',
'-g',
'!.git',
'-e',
text,
'.',
}
job.start(grep_cmd, {
on_stdout = function(id, data)
if vim.fn.getbufline(result_bufid, 1)[1] == '' then
vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, data)
else
vim.api.nvim_buf_set_lines(result_bufid, -1, -1, false, data)
end
end,
})
else
vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, {})
end
end,
})
上述代码中,我使用了 spacevim job API,其实,我也考虑过使用 vim.system()
函数,但是异步搜索完全不调用,
可能是写法有误, 使用 vim.system()
写法如下(无效):
vim.api.nvim_create_autocmd({ 'TextChangedI' }, {
group = augroup,
buffer = prompt_bufid,
callback = function(ev)
local text = vim.api.nvim_buf_get_lines(prompt_bufid, 0, 1, false)[1]
if text ~= '' then
local grep_cmd = {
'rg',
'--no-heading',
'--color=never',
'--with-filename',
'--line-number',
'--column',
'-g',
'!.git',
'-e',
text,
'.',
}
vim.system(grep_cmd, {
stdout = function(err, data)
vim.api.nvim_buf_set_lines(result_bufid, 0, -1, false, vim.split(data, '\n'))
end,
})
end
end,
})
因为输入框只有一行,因此避免回车键换行,同时增加在搜索结果窗口内上下移动两个快捷键。
-- 使用 Esc 关闭整个界面
vim.keymap.set('i', '<Esc>', function()
vim.cmd('noautocmd stopinsert')
vim.api.nvim_win_close(prompt_winid, true)
vim.api.nvim_win_close(result_winid, true)
end, { buffer = prompt_bufid })
-- 搜索结果行转换成文件名、光标位置
local function get_file_pos(line)
local filename = vim.fn.fnameescape(vim.fn.split(line, [[:\d\+:]])[1])
local linenr =
vim.fn.str2nr(string.sub(vim.fn.matchstr(line, [[:\d\+:]]), 2, -2))
local colum = vim.fn.str2nr(
string.sub(vim.fn.matchstr(line, [[\(:\d\+\)\@<=:\d\+:]]), 2, -2)
)
return filename, linenr, colum
end
-- 使用回车键打开光标所在的搜索结果,同时关闭界面
vim.keymap.set('i', '<Enter>', function()
vim.cmd('noautocmd stopinsert')
-- 获取搜索结果光表行
local line_number = vim.api.nvim_win_get_cursor(result_winid)[1]
local filename, linenr, colum = get_file_pos(
vim.api.nvim_buf_get_lines(
result_bufid,
line_number - 1,
line_number,
false
)[1]
)
vim.api.nvim_win_close(prompt_winid, true)
vim.api.nvim_win_close(result_winid, true)
vim.cmd('edit ' .. filename)
vim.api.nvim_win_set_cursor(0, { linenr, colum })
end, { buffer = prompt_bufid })
-- 使用 Tab/Shift-Tab 上下移动搜素结果
vim.keymap.set('i', '<Tab>', function()
local line_number = vim.api.nvim_win_get_cursor(result_winid)[1]
vim.api.nvim_win_set_cursor(result_winid, { line_number + 1, 0 })
end, { buffer = prompt_bufid })
vim.keymap.set('i', '<S-Tab>', function()
local line_number = vim.api.nvim_win_get_cursor(result_winid)[1]
vim.api.nvim_win_set_cursor(result_winid, { line_number - 1, 0 })
end, { buffer = prompt_bufid })
-- 高亮文件名及位置
vim.fn.matchadd(
'Comment',
[[\([A-Z]:\)\?[^:]*:\d\+:\(\d\+:\)\?]],
10,
-1,
{ window = result_winid }
)
可以使用 extmarks 美化输入框,添加一个 >
符号,禁用行号。
vim.api.nvim_set_option_value('number', false, { win = prompt_winid })
vim.api.nvim_set_option_value('relativenumber', false, { win = prompt_winid })
local extns = vim.api.nvim_create_namespace('floatgrep_ext')
vim.api.nvim_buf_set_extmark(prompt_bufid, extns, 0, 0, {
sign_text = '>',
sign_hl_group = 'Error',
})
以上代码仅供参考,实际上还有很多细节并未完全考虑到, 比如 callback 函数内对于未执行完成的 job 的处理、 比如增加输入与搜索之间的延迟减少快速输入过程中不必要的额外执行搜索命令。
完整的代码可以看:simple_float_grep
Neovim 一直在迭代更新,本文以 Neovim v0.10.0 版本为基础进行功能演示。
按照 :h extmark
描述 Extended marks (extmarks) 是跟踪缓冲区文本变化的的特定位置的注释。位置从 0 开始,位于第一个字符的前方。
f o o b a r line contents
0 1 2 3 4 5 character positions (0-based)
0 1 2 3 4 5 6 extmark positions (0-based)
与 Neovim Extended marks 相关的函数有两个:nvim_buf_get_extmark
和 nvim_buf_set_extmark
。
nvim_buf_set_extmark
nvim_buf_set_extmark
的函数签名是:
nvim_buf_set_extmarks({buffer}, {ns_id}, {start}, {col}, {opts})
可以看到函数调用的参数包括 buffer
、ns_id
、start
、col
、opts
。
buffer
指的是设置 extmarks 对应的缓冲区 ID,ns_id
全称是 name space id, 可以由 nvim_create_namespace
新建。
其中 opts
是一个 Lua table,比如:
vim.api.nvim_buf_set_extmark(
0,
vim.api.nvim_create_namespace('test_extmark'),
28,
3,
{ end_row = 28, end_col = 5, hl_group = 'TODO' }
)
opts
支持的 Key 值及其含义包括:
id
如果是新建一个 extmark 可以缺省,如果是修改某个已存在的 extmark,需要指定 id.
end_row
和 end_col
指定 extmark 结束的行和列,这里的列指的是行首、行尾或者字符之间,都是从 0 开始。
hi_group
hl_eol
这是一个布尔值,控制 extmark 覆盖的行尾无字符区域是否需要高亮。
virt_text
这是一个列表,其中每一个元素结构是 [text, highlight]
。默认是添加在 extmark 开始的行行尾。
示例代码:
vim.api.nvim_buf_set_extmark(4, vim.api.nvim_create_namespace("test_extmark"), 47, 3, {
end_row = 47,
end_col = 5,
hl_group = "TODO",
virt_text = { { "This is ", "Comment" }, { "hello", "Number" }, { " extmarks", "TODO" } },
})
virt_text_pos
设置虚拟文本的位置,可以设定的值为:
eol
:行尾最后一个字符右边overlay
: 在 extmark 起始位置显示虚拟文本,覆盖的字符不右移right_align
: 在窗口最右侧显示inline
: 在 extmark 起始位置显示虚拟文本,覆盖的字符右移virt_text_win_col
将 virtual text 展示在 fixed screen line,屏幕中可见的第一行为 0,往下依次加1。
virt_text_hide
hl_mode
设置高亮颜色的模式:
replace
: 默认,只显示 virtual text 颜色combine
: combine with background text colorblend
: blend with background text colorvirt_lins
设置虚拟多行文本,每一行结构是多个 [text, highlight]
组成的列表。
virt_lines_above
当设置虚拟多行文本时,默认是在 extmark 起始行下方,该选项设为 true
,可以在上方显示。
virt_lines_leftcol
虚拟多行文本左对齐,绕过 sign 和 行号列
right_gravity
和 end_right_gravity
控制在 extmark 左右侧末端添加字符时 extmark 的位置扩展行为
winbar
Neovim 0.8.0 就增加了 'winbar'
这一选项。winbar 实际上类似于状态栏(statusline),只不过 winbar 是显示在每一个窗口的顶部。
它的设置格式与状态栏也完全一致。
这里做了一个简单的示例效果图:
" 使用全局状态栏,状态栏只在底部显示,水平分割窗口将不再显示状态栏。
set laststatus=3
" 隐藏顶部标签栏
set showtabline=0
前面也提到了 Neovim 实际上早就增加了 winbar,我是因为习惯了现有的状态栏和标签栏,不太像改变操作习惯。 最近正好想尝试使用一下 winbar 特性,因为之前写过自定义的标签栏、状态栏,因此配置起来还算顺手。
详细代码可以看我的 Github 仓库:wsdjeg/winbar.nvim
实现的逻辑也比较简单,监控指定的 Neovim 事件,在 callback 函数内调用 redraw_winbar 函数。
local augroup = vim.api.nvim_create_augroup('winbar.nvim', {
clear = true,
})
vim.api.nvim_create_autocmd({ 'BufWinEnter' }, {
group = augroup,
pattern = '*',
callback = function(e)
redraw_winbar()
end,
})
我看过 fgheng/winbar.nvim
插件的代码,最后一次更新时间是 2022 年七月,在他的插件里 setup 函数直接这样写:
function M.setup(opts)
config.set_options(opts)
local winbar = require('winbar.winbar')
winbar.init()
if opts.enabled == true then
vim.api.nvim_create_autocmd({ 'DirChanged', 'CursorMoved', 'BufWinEnter', 'BufFilePost', 'InsertEnter', 'BufWritePost' }, {
callback = function()
winbar.show_winbar()
end
})
end
end
这会存在一个非常严重问题,就是如果使用者不小心调用了两次甚至多次 setup 函数,那么就会发现,实际上定义了多个重复的 autocmd。 因此不管是使用 Lua 来创建 autocmd 还是早期 Vim 下使用 Vim Script 创建 autocmd,都建议使用 augroup。
redraw_winbar
函数实现简单到极致的实现:
local function redraw_winbar()
local file_name = vim.fn.expand('%:t')
if file_name == '' then
return
end
local value = '%#SpaceVim_winbar#' .. file_name .. ' %#SpaceVim_winbar_Normal#' .. default_conf.winbar_seperator .. '%#Normal#'
vim.api.nvim_set_option_value('winbar', value, { scope = 'local' })
end
这里面其实有很多功能可以去做的,比如:
local file_name = vim.fn.expand('%:t')
local ft = vim.filetype.match({ filename = file_name })
for _, v in ipairs(excluded_filetypes) do
if v == ft then
return
end
end
local excluded_regex = [[\.txt$]]
local re = vim.regex(excluded_regex)
local file_name = vim.fn.expand('%:t')
for _, v in ipairs(excluded_filetypes) do
if re:match(file_name) then
return
end
end
在 Neovim 或者 Vim 中,QuickFix 窗口内的内容默认的格式是:
<filename>|<lnum> col <col>|<text>
最近有需求修改 QuickFix 窗口内容的格式,使其更加美观一些,修改前,QuickFix 窗口打开后内容如下:
而修改后最终展示的效果如下:
下面展示了两种修改方法,一个是在老旧的 Vim 内实现的方法,另外一个是借助 Vim 8.2 的 qftf
选项实现的方式。
早在 Vim 7.4 之前,可以使用 autocmd 来手动修改,其实现的逻辑是监控 BufReadPost
事件
function! QuickFixFormat()
let qflist = map(getqflist(),
\ 'extend(v:val, {"filename" : bufname(v:val.bufnr)})')
let prefix_len = 2 + max(map(copy(qflist),
\ 'strchars(v:val.filename . v:val.lnum)'))
let fmt = '%-' . prefix_len . 's' . '%s'
setlocal modifiable
call setline('1', map(qflist,
\ 'printf(fmt, v:val.filename . ":" . v:val.lnum, "| " . v:val.text)'))
setlocal nomodifiable nomodified
endfunction
augroup QuickFixFormat
autocmd!
autocmd BufReadPost quickfix call QuickFixFormat()
augroup END
上述代码中,还可以把分隔符 |
修改成占据整行的竖线 │
,以实现完整分割线效果。
完成窗口内容修改后,还存在一个问题,QuickFix 窗口里内容的高亮是通过 Vim 默认的 syntax/qf.vim
文件实现的,在这里需要覆盖默认的 qf
FileType 的语法高亮:
Vim 可以新建 ~/.vim/syntax/qf.vim
文件,Neovim 是 ~/.config/nvim/syntax/qf.vim
。
if exists('b:current_syntax')
finish
endif
syn match qfFileName "^[^│]*" contains=qfLineNr
syn match qfSeparator "│"
syn match qfLineNr ":\d*" contained
" The default highlighting.
hi def link qfFileName Directory
hi def link qfLineNr LineNr
hi def link qfSeparator VertSplit
let b:current_syntax = 'qf'
quickfixtextfunc
上面的方法是强制修改 QuickFix Buffer 内容实现的格式化,Vim 8.2 提供了 quickfixtextfunc
选项,
可以通过设定这一选项来实现格式化 QuickFix 窗口,修改第一段代码为:
function! QuickFixFormat(info)
let qflist = getqflist({'id' : a:info.id, 'items' : 1}).items
let qflist = map(qflist,
\ 'extend(v:val, {"filename" : bufname(v:val.bufnr)})')
let prefix_len = 2 + max(map(copy(qflist),
\ 'strchars(v:val.filename . v:val.lnum)'))
let fmt = '%-' . prefix_len . 's' . '%s'
return map(qflist,
\ 'printf(fmt, v:val.filename . ":" . v:val.lnum, "│ " . v:val.text)')
endfunction
set quickfixtextfunc=QuickFixFormat
可以通过 if exists('&quickfixtextfunc')
来检测当前 Vim 或者 Neovim 是否支持 quickfixtextfunc
选项。
结束了一天的工作,晚饭后去酒店旁边的电影院看了《误杀3》。
看完重生,再来开这个确实觉得剧情太容易猜到剧情的发展了,看就没太大悬念了。不同于重生里面以毒品为话题,这部电影是以拐卖儿童这一敏感话题展开的,先不谈拍的好坏。起码让这个话题被更多的人关注到加以防范也算是起到一丁点的好处,让社会跟相关部门更多地关注到这个问题那就更好了。 回过头来再想想整部电影的剧情,感觉其中还是处处提供了线索,看的过程中还是能感觉出来一些人物的心理活动的。
郑炳睿
一开始确实没想到他原来居然是人贩子,求生其实是生命的本能。人类虽然是高等动物,通过思想可以客服本能,但是还是会有人没办法克服,会依靠本能去做一些决定。 为了能活下去而参与贩卖人口等等,后来还是为了能活下去选择按下女儿那边炸弹的遥控器。虽然几个镜头也极力体现他对女儿的疼爱,比如看到女儿被伤害,哭的眼泪口水哒哒的。 关于这个角色心理的选择,我觉得有一个大的转变,从一开始怕死,而选择按下女儿那边炸弹遥控器,到后来奋不顾身地为女儿挡枪。
但是上面图片中的介绍真是扯淡, 郑炳睿出手爆杀人贩???他才是人贩子。图片剧情简介也完全是跟剧情实际情况截然相反。
李慧萍
电影开头第一眼,这女孩丢失八成跟她有关系,肯定是人贩子。后来证实下来确实是她参与的,但是没想到的是她原来也是是为了复仇。前后出镜截然两种不同风格人物,演员在演演员。
张景贤
误解了,一开始福利院收娃的长头发我以为是他。加上半夜出来杀郑炳睿,我还以为他是达蒙的人。后来看到原来他的妻子是梁素娥,才知道他也是要复仇的。
雅英
我脸盲,看了好几次才把她跟丢失娃的母亲对上号。
吴达毅
最后杀他的是谁?
施福安和Tinaya
没啥感触,死得其所。
阿卢
第一个镜头,就看到仇恨的眼光,审判室里的大笑也没看懂,直到知道他也是丢失儿童的家属,一切才显得那么理所当然。
达蒙
最后啥他的人是谁?
门多萨
一开始以为他是一无是处的领导。
梁素娥
最有感触的还是梁素娥的剧情,感觉词穷了,不知道该怎么写。第一次出镜的时候,感觉挺好看,一张混血儿脸,心善,“我也是孩子母亲,我一定会找到真相”,台词可能我记错了,但是我想她所想表达的意思以及她所坚持的事情一定是这样的,也为此付出了生命,也包括还没出生的孩子。
阿佐
开第一枪的男警察?
阿婕
不记得谁了,女警察?
郑婷婷
最后开枪的是谁?