Vim状态栏:单词搜索

时间:2015-06-11 00:29:36

标签: search vim statusline

我为此努力寻找并且无法找到我追求的东西。

在我的状态行上,我想要计算当前文件中发生的匹配数。下面的vim命令返回我想要的内容。我需要返回的号码才能显示在我的状态栏中。

:%s/^I^I//n

vim返回:16行 16匹配

FYI说明:我正在使用CSV文件。我正在搜索两个制表符(^ I ^ I)因为这表示我仍然需要处理的行。因此,我想要的状态行将指示当前文件中剩余的工作量。

我不知道如何在状态行上输入vim命令,我知道%{}可用于运行函数但是如何运行 vim搜索命令?我尝试过以下各种变体,但它们显然不对,最终会出错。

:set statusline+= %{s/^I^I//n}

帮我修一个kenobi,你是我唯一的希望!

2 个答案:

答案 0 :(得分:3)

首先要提到的是,对于大型文件,此功能完全不切实际。原因是在每个光标移动之后,在每个命令完成之后重新绘制状态行,并且可能跟随我甚至不知道的其他事件。在整个缓冲区上执行正则表达式搜索,此外,不仅是当前缓冲区,而且每个可见窗口(因为每个窗口都有自己的状态行),会显着降低速度。不要误解我的意思;这个功能背后的想法是一个很好的,因为它可以让你立即完全自动地显示你的剩余工作,但计算机根本不是无限的性能(不幸的是),所以这很容易变成一个问题。我编辑了包含数百万行文本的文件,单个正则表达式搜索在这些缓冲区上花费了很多秒。

但是如果您的文件仍然很小,我已经找到了三种可能的解决方案,您可以通过这些解决方案来实现这一目标。

解决方案#1:exe:s和重定向输出

您可以使用函数中的:exe以参数化模式运行:s命令,并使用:redir将输出重定向到本地变量。

不幸的是,这有两个不良的副作用,在这个特性的背景下,它将是完整的交易破坏者,因为它们会在每次重绘状态行时出现:

  1. 光标移动到当前行的开头。 (个人注意事项:我从未理解为什么vim会这样做,无论您是从状态行调用中运行:s还是在vim命令行上手动输入它。)
  2. 视觉选择(如果有)将丢失。
  3. (实际上可能会有更多我不知道的副作用。)

    可以通过getcurpos()setpos()保存并恢复光标位置来修复光标问题。请注意,它必须是getcurpos()而不是getpos(),因为后者不会返回curswant字段,这对于保留光标所需的列是必要的。驻留在,可能与光标不同的列是"实际上" at(例如,如果光标移动到较短的行中)。不幸的是,getcurpos()是vim的最新成员,即7.4.313,并且根据我的测试,它似乎甚至无法正常工作。幸运的是,有较旧的winsaveview()winrestview()函数可以完美兼容地完成任务。所以现在,我们将使用它们。

    解决方案#1a:使用gv

    恢复视觉选择

    可以通过在正常模式下运行gv来解决视觉选择问题思想,但由于某种原因,视觉选择在执行此操作时会完全损坏。我已经在Cygwin CLI和Windows gvim上对此进行了测试,但我没有解决方案(关于恢复视觉选择)。

    无论如何,这是上述设计的结果:

    fun! MatchCount(pat,...)
        "" return the number of matches for pat in the active buffer, by executing an :s call and redirecting the output to a local variable
        "" saves and restores both the cursor position and the visual selection, which are clobbered by the :s call, although the latter restoration doesn't work very well for some reason as of vim-7.4.729
        "" supports global matching (/g flag) by taking an optional second argument appended to :s flags
        if (a:0 > 1)| throw 'too many arguments'| endif
        let flags = a:0 == 1 ? a:000[0] : ''
        let mode = mode()
        let pos = winsaveview()
        redir => output| sil exe '%s/'.a:pat.'//ne'.flags| redir END
        call winrestview(pos)
        if (mode == 'v' || mode == 'V' || mode == nr2char(22))
            exe 'norm!gv'
        endif
        if (match(output,'Pattern not found') != -1)
            return 0
        else
            return str2nr(substitute(output,'^[\s\n]*\(\d\+\).*','\1',''))
        endif
        return 
    endfun
    
    set statusline+=\ [%{MatchCount('\\t\\t')}]
    

    一些随机的说明:

    • 在匹配计数提取模式中使用^[\s\n]*对于通过重定向期间捕获的前导换行(无法确定为何会发生这种情况)是必要的。另一种方法是使用点原子上的非贪婪乘数(即^.\{-})跳过任何字符到第一个数字。
    • statusline选项值中反斜杠的加倍是必要的,因为在解析选项值本身时会发生反斜杠插值/删除。通常,单引号字符串不会导致反斜杠插值/删除,并且我们的pat字符串一旦被解析,最终会直接与传递给:s的{​​{1}}字符串连接,因此&# 39;在这些点上没有反斜杠插值/删除(至少在评估:exe命令之前,当我们的反斜杠的反斜杠插值发生时,这就是我们想要的) 。我发现这有点令人困惑,因为在:s构造内部,你期望它是一个普通的纯粹的VimScript表达式,但这就是它的工作方式。
    • 我为%{}命令添加了/e标志。这对于处理零匹配缓冲区的情况是必要的。通常,如果匹配为零,:s实际上会抛出错误。对于状态行调用,这是一个很大的问题,因为在尝试重绘状态行时抛出的任何错误都会导致vim将:s选项置为无效,以防止重复出错。我最初寻找涉及捕获错误的解决方案,例如:try:catch,但没有任何效果;一旦抛出错误,就会在vim源(statusline)中设置一个我们无法设置的标志,因此called_emsg在此时注定失败。幸运的是,我发现了statusline标志,它可以防止错误被抛出。

    解决方案#1b:使用缓冲区本地缓存躲避可视模式

    我对视觉选择问题并不满意,所以我写了一个替代解决方案。如果视觉模式生效,此解决方案实际上避免了运行搜索,而是从缓冲区本地缓存中提取最后已知的搜索计数。我很确定这永远不会导致搜索计数变得过时,因为在不放弃可视模式的情况下编辑缓冲区是不可能的(我非常确定......)。

    所以现在/e函数不会弄乱视觉模式:

    MatchCount()

    现在我们需要这个助手"谓词"函数告诉我们何时(不)安全地运行fun! MatchCount(pat,...) if (a:0 > 1)| throw 'too many arguments'| endif let flags = a:0 == 1 ? a:000[0] : '' let pos = winsaveview() redir => output| sil exe '%s/'.a:pat.'//ne'.flags| redir END call winrestview(pos) if (match(output,'Pattern not found') != -1) return 0 else return str2nr(substitute(output,'^[\s\n]*\(\d\+\).*','\1','')) endif return endfun 命令:

    :s

    现在我们需要一个在谓词结果上分支的缓存层,只在安全的情况下运行主函数,否则它会从缓冲区本地缓存中提取从最近一次调用中捕获的最后一个已知的返回值。主要功能采取那些确切的论点:

    fun! IsVisualMode(mode)
        return a:mode == 'v' || a:mode == 'V' || a:mode == nr2char(22)
    endfun
    

    我们需要这个帮助函数,我在somewhere on the Internet年前发现了这个函数:

    fun! BufferCallCache(buf,callName,callArgs,callElseCache)
        let callCache = getbufvar(a:buf,'callCache')
        if (type(callCache) != type({}))
            unlet callCache
            let callCache = {}
            call UnletBufVar(a:buf,'callCache')
            call setbufvar(a:buf,'callCache',callCache)
        endif
        if (a:callElseCache)
            let newValue = call(a:callName,a:callArgs)
            if (!has_key(callCache,a:callName.':Args') || !has_key(callCache,a:callName.':Value'))
                let callCache[a:callName.':Args'] = []
                let callCache[a:callName.':Value'] = []
            endif
            let i = len(callCache[a:callName.':Args'])-1
            while (i >= 0)
                let args = callCache[a:callName.':Args'][i]
                if (args == a:callArgs)
                    let callCache[a:callName.':Value'][i] = newValue
                    return newValue
                endif
                let i -= 1
            endwhile
            let callCache[a:callName.':Args'] += [a:callArgs]
            let callCache[a:callName.':Value'] += [newValue]
            return newValue
        else
            if (has_key(callCache,a:callName.':Args') && has_key(callCache,a:callName.':Value'))
                let i = len(callCache[a:callName.':Args'])-1
                while (i >= 0)
                    let args = callCache[a:callName.':Args'][i]
                    if (args == a:callArgs)
                        return callCache[a:callName.':Value'][i]
                    endif
                    let i -= 1
                endwhile
            endif
            return ''
        endif
    endfun
    

    最后,我们可以设置fun! UnletBufVar(bufExpr, varName ) "" source: <http://vim.1045645.n5.nabble.com/unlet-ing-variables-in-buffers-td5714912.html> call filter(getbufvar(a:bufExpr,''), 'v:key != '''.a:varName.'''' ) endfun

    statusline

    解决方案#2:每行调用set statusline+=\ [%{BufferCallCache('','MatchCount',['\\t\\t'],!IsVisualMode(mode()))}]

    我已经想到了另一种可能的解决方案,它实际上要简单得多,并且对于非大型文件似乎表现得很好,即使它涉及更多的VimScript级别的循环和处理。这是循环遍历文件中的每一行并在其上调用match()

    match()

    解决方案#3:反复调用fun! MatchCount(pat) "" return the number of matches for pat in the active buffer, by iterating over all lines and calling match() on them "" does not support global matching (normally achieved with the /g flag on :s) let i = line('$') let c = 0 while (i >= 1) let c += match(getline(i),a:pat) != -1 let i -= 1 endwhile return c endfun set statusline+=\ [%{MatchCount('\\t\\t')}] / search()

    我已经编写了一些稍微复杂的函数来执行全局和线性匹配,分别围绕searchpos()search()构建。我也支持可选的开始和结束界限。

    searchpos()

答案 1 :(得分:0)

也许不完全是你想要的,但如果你在$ HOME / .vimrc文件中放置如下的函数,你可以这样做:

:set statusline+=%!SearchResults('^I^I')

$ HOME / .vimrc中

function SearchResults(q)
  redir => matches
  silent! execute "%s/".a:q."//n"
  redir END
  return substitute(matches, "^.", "", "")
endfunction

如果不出意外,也许会让你更近一点。