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

时间:2017-07-28 05:19:49

标签: vim

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

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

要修复语法突出显示,我可以尝试从命令行窗口中点击: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添加了: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')

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

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中保存的每个命令行,还有第2行似乎是时间戳。此外,它以相反的顺序删除,不必更新要删除的行的地址(每次删除行时,下一行的地址减1)。

call writefile(info, viminfo, 'b')

此行用新内容覆盖~/.viminfo,新内容应与以前相同,不在命令行窗口中删除行。

我不会将其作为答案发布,因为我不确定它是否可靠并且没有任何副作用。在测试代​​码之前,我对~/.viminfo进行了备份。

EDIT2:

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

1 个答案:

答案 0 :(得分:0)

历史记录以纯文本格式存储在.viminfo文件中。因此,您只需打开.viminfo并删除该错误行即可。

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

:call histdel('/')

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