如何从 Vim 的命令行历史记录中永久删除特定条目而不会丢失最近编辑的文件的更改列表?



当我们执行Vim-Ex命令时,它会被保存在命令行历史记录中,我们可以通过用q:打开命令行窗口来访问它。

然而,有时,我希望能够永久删除其中的一些。例如,如果我执行命令:a,命令行窗口中的所有后续命令都将被识别为语法组vimInsert的元素,该语法组链接到高亮显示组String,该高亮显示组在我的配色方案中用青色着色。

为了修复语法突出显示的问题,我可以尝试从命令行窗口中删除:a命令,点击dd,但只有在打开窗口时才会删除该条目。一旦我关闭它并稍后重新打开它,:a命令就会返回。为了使删除永久化,我尝试在vimrc:中添加一个autocmd

augroup my_cmdline_window
au!
au CmdWinLeave * call s:update_history()
augroup END
fu! s:update_history() abort
let new_hist = getline(1, '$')
call histdel(':')
for i in new_hist
call histadd(':', i)
endfor
endfu

它调用一个函数,该函数在关闭之前通过从当前窗口读取命令历史来更新命令历史。它在当前Vim会话运行时工作,但一旦我退出它并启动新的会话,所有删除的条目都会返回。

我认为删除不会在会话中持续存在的原因是,当我退出会话时,Vim会将当前命令行历史记录与存储在~/.viminfo中的历史记录合并。

此文件中保存的内容由'viminfo'选项控制。我的'viminfo'的值是:

viminfo='100,<50,s10,h

它不包含:参数,该参数设置命令行历史记录中要保存的最大项目数。这意味着Vim应该使用'history'选项。我的'history'的默认值是1000

因此,为了强制Vim用当前会话中的一个覆盖存储在~/.viminfo中的命令行历史记录,我尝试在我的autocmd:中使用:wviminfo命令

augroup my_cmdline_window
au!
au CmdWinLeave * call s:update_history()
augroup END
fu! s:update_history() abort
let new_hist = getline(1, '$')
call histdel(':')
for i in new_hist
call histadd(':', i)
endfor
wviminfo!
endfu

我给:wviminfo加了一个bang,根据:h :wviminfo:

When [!] is used, the old information is not read first,
only the internal info is written.

它似乎是有效的,因为有了这个autocmd,命令行窗口中的条目的删除将在会话中持续存在。然而,它似乎也会删除最近编辑的文件的所有变更列表,因为在启动新的Vim会话后,它们的所有变更清单都是空的。

由于:h 'viminfo解释说,当您在'viminfo'中包含'项目时,变更列表和跳转列表也存储在~/.viminfo中,我怀疑使用:wviminfo!后跳转列表和标记也会丢失。

有没有一种方法可以避免更改列表/跳转列表/标记的丢失,同时仍然从命令行历史记录中删除任意条目?


编辑:

我想出了这个:

augroup my_cmdline_window
au!
au CmdWinEnter * let s:old_cmdline_hist = getline(1, line('$')-1)
au CmdWinLeave * call s:update_history()
augroup END
fu! s:update_history() abort
let hist = filter(getline(1, '$'), 'v:val !~# "^\s*$"')
call histdel(':')
for i in hist
call histadd(':', i)
endfor
let viminfo = expand('~/.viminfo')
if !filereadable(viminfo)
return
endif
let info = readfile(viminfo)
let deleted_entries = filter(copy(s:old_cmdline_hist), 'index(hist, v:val) == -1')
call map(deleted_entries, 'index(info, ":".v:val)')
call sort(filter(deleted_entries, 'v:val >= 0'))
if empty(deleted_entries)
return
endif
for entry in reverse(deleted_entries)
call remove(info, entry, entry + 1)
endfor
call writefile(info, viminfo, 'b')
endfu

以下是代码的作用:

当您进入命令行窗口时,第一个autocmd会捕获s:old_cmdline_hist中的命令行历史记录。

当您离开它时,第二个autocmd会更新当前会话的历史记录,并尝试从~/.viminfo中删除已删除的条目。

let hist = filter(getline(1, '$'), 'v:val !~# "^\s*$"')
call histdel(':')
for i in hist
call histadd(':', i)
endfor

此块仅更新当前会话的历史记录。

let viminfo = expand('~/.viminfo')
if !filereadable(viminfo)
return
endif

这个检查~/.viminfo是否可读。如果不是,函数将停止。

let info = readfile(viminfo)
let deleted_entries = filter(copy(s:old_cmdline_hist), 'index(hist, v:val) == -1')

该块获取列表info内的~/.viminfo的内容(1项=1行)。它还获取通过调用filter()删除的条目。后者删除所有不满足条件index(hist, v:val) == -1的条目。如果filter()操作的旧历史的条目不在新历史内,则此表达式为true。

call map(deleted_entries, 'index(info, ":".v:val)')

这一行将删除的文本条目转换为~/.viminfo中的行地址。

call sort(filter(deleted_entries, 'v:val >= 0'))
if empty(deleted_entries)
return
endif

此块对线路地址进行排序,并删除非正的线路地址,以防在上一步中找不到其中的一些线路地址(index()返回-1)。它还检查此时是否还有线路地址,如果没有,函数将再次停止。

for entry in reverse(deleted_entries)
call remove(info, entry, entry + 1)
endfor

此块从info中删除相关行。它删除了2行(entryentry + 1),因为对于保存在~/.viminfo中的每个命令行,还有一行似乎是时间戳。此外,它按相反的顺序删除,不必更新要删除的行的地址(每次删除一行,下一行的地址都会减少一)。

call writefile(info, viminfo, 'b')

这一行用新内容覆盖~/.viminfo,新内容应该和以前一样,没有在命令行窗口中删除行。

我不把它作为答案发布,因为我不确定它是否可靠,也没有任何副作用。在测试代码之前,我备份了~/.viminfo

第2版:

我认为它无法删除包含回车的行。可能存在导致类似问题的其他特殊字符(如空字符^@)。

历史记录以纯文本形式存储在.viminfo文件中。所以你可以打开你的.viminfo并删除那个bug行。

或者,您可以通过histdel()函数清除历史记录,例如,对于搜索:

:call histdel('/')

您可以通过数字或正则表达式删除某些历史记录条目。参见:help histdel

最新更新