Search Vim状态行:单词搜索

Search Vim状态行:单词搜索,search,vim,statusline,Search,Vim,Statusline,我一直在努力寻找这个,但没能找到我想要的 在我的statusline上,我想知道当前文件中出现的匹配数。下面的vim命令返回我想要的内容。我需要返回的号码显示在我的状态行中 :%s/^I^I//n vim返回:16行上有16个匹配 仅供参考说明:我正在CSV文件中工作。我正在搜索两个制表符(^I^I),因为这表示我仍需要处理的行。因此,我所需的状态行将指示当前文件中剩余的工作量 我不知道如何在状态行上输入vim命令,我知道%{}可以用来运行函数,但我如何运行vim搜索命令?我尝试过以下的变体,

我一直在努力寻找这个,但没能找到我想要的

在我的statusline上,我想知道当前文件中出现的匹配数。下面的vim命令返回我想要的内容。我需要返回的号码显示在我的状态行中

:%s/^I^I//n
vim返回:16行上有16个匹配

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

我不知道如何在状态行上输入vim命令,我知道%{}可以用来运行函数,但我如何运行vim搜索命令?我尝试过以下的变体,但它们显然是不正确的,结果只是出现了一个错误

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

帮帮我维米一号克诺比,你是我唯一的希望

可能不完全是您想要的,但是如果您在$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

如果没有其他功能,也许这会让您更接近。

这里要提到的第一件事是,对于大文件,此功能将完全不切实际。原因是状态行在每次光标移动之后,在每个命令完成之后,以及可能在我甚至不知道的其他事件之后被重新绘制。在整个缓冲区上执行正则表达式搜索,而且不仅是在当前缓冲区上,而且在每个可见窗口上执行正则表达式搜索(因为每个窗口都有自己的状态行),会显著降低速度。别误会我;这一功能背后的想法是好的,因为它可以让您立即完全自动地指示剩余的工作,但计算机并不是无限性能的(不幸的是),因此这很容易成为一个问题。我编辑过数百万行文本的文件,在这样的缓冲区中,一次正则表达式搜索可能需要很多秒

但如果您的文件仍然相当小,我已经找到了三种可能的解决方案,您可以通过它们来实现这一点

解决方案#1:exe:s和重定向输出 可以使用from函数以参数化模式运行命令,并将输出重定向到局部变量

不幸的是,这有两个不良副作用,在本功能的上下文中,这将是完全的交易破坏,因为它们将在每次重新绘制状态行时发生:

  • 光标将移动到当前行的开头。(个人说明:我一直不明白vim为什么会这样做,无论您是通过状态行调用运行
    :s
    ,还是通过在vim命令行上手动键入它。)
  • 视觉选择(如果有)将丢失
  • (实际上可能还有更多我不知道的不利影响。)

    通过和保存并恢复光标位置,可以解决光标问题。请注意,它必须是
    getcurpos()
    ,而不是因为后者不返回
    cursweat
    字段,这是保留光标“想要”驻留的列所必需的,该列可能不同于光标“实际”所在的列(例如,如果光标移动到较短的行中)。不幸的是,
    getcurpos()。幸运的是,有一些旧的和功能可以完美和兼容地完成任务。所以现在,我们将使用这些

    解决方案#1a:使用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]*
      是通过重定向期间捕获的前导行中断所必需的(不确定为什么会发生这种情况)。另一种方法是在点原子上使用非贪婪乘法器跳过任何字符,直到第一个数字,即
      ^.\{-}
    • 选项值中的反斜杠加倍是必要的,因为反斜杠插值/删除是在解析选项值本身的过程中发生的。一般来说,单引号字符串不会导致反斜杠插入/删除,我们的
      pat
      字符串一旦解析,最终会与传递给
      :exe
      :s
      字符串直接连接,因此在这些点上没有反斜杠插入/删除(至少在计算
      :s
      命令之前,当反斜杠的反斜杠插值确实发生时,这是我们想要的)。我发现这有点让人困惑,因为在
      %{}
      构造中,您希望它是一个正常的未完全删除的VimScript表达式,但它就是这样工作的
    • 我为
      :s
      命令添加了
      /e
      标志。这对于处理零匹配的缓冲区是必要的。通常情况下,
      :s
      如果没有匹配,实际上会抛出一个错误。对于状态行调用,这是一个大问题,因为尝试重新绘制状态行时抛出的任何错误都会导致vim为null将
      statusline
      选项作为防止重复错误的防御措施。我最初寻找涉及捕获错误的解决方案,例如and,但没有任何效果;一旦抛出错误,vim源中就会设置一个标志(
      称为\u emsg
      )我们无法取消设置,因此
      状态行
      在这一点上注定要失败。幸运的是,我发现了
      /e
      标志,它可以防止抛出错误
    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
    fun! IsVisualMode(mode)
        return a:mode == 'v' || a:mode == 'V' || a:mode == nr2char(22)
    endfun
    
    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
    
    set statusline+=\ [%{BufferCallCache('','MatchCount',['\\t\\t'],!IsVisualMode(mode()))}]
    
    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')}]
    
    fun! GlobalMatchCount(pat,...)
        "" searches for pattern matches in the active buffer, with optional start and end [line,col] specifications
        "" useful command-line for testing against last-used pattern within last-used visual selection: echo GlobalMatchCount(@/,getpos("'<")[1:2],getpos("'>")[1:2])
        if (a:0 > 2)| echoerr 'too many arguments for function: GlobalMatchCount()'| return| endif
        let start = a:0 >= 1 ? a:000[0] : [1,1]
        let end = a:0 >= 2 ? a:000[1] : [line('$'),2147483647]
        "" validate args
        if (type(start) != type([]) || len(start) != 2 || type(start[0]) != type(0) || type(start[1]) != type(0))| echoerr 'invalid type of argument: start'| return| endif
        if (type(end) != type([]) || len(end) != 2 || type(end[0]) != type(0) || type(end[1]) != type(0))| echoerr 'invalid type of argument: end'| return| endif
        if (end[0] < start[0] || end[0] == start[0] && end[1] < start[1])| echoerr 'invalid arguments: end < start'| return| endif
        "" allow degenerate case of end == start; just return zero immediately
        if (end == start)| return [0,0]| endif
        "" save current cursor position
        let wsv = winsaveview()
        "" set cursor position to start (defaults to start-of-buffer)
        call setpos('.',[0,start[0],start[1],0])
        "" accumulate match count and line count in local vars
        let matchCount = 0
        let lineCount = 0
        "" also must keep track of the last line number in which we found a match for lineCount
        let lastMatchLine = 0
        "" add one if a match exists right at start; must treat this case specially because the main loop must avoid matching at the cursor position
        if (searchpos(a:pat,'cn',start[0])[1] == start[1])
            let matchCount += 1
            let lineCount += 1
            let lastMatchLine = 1
        endif
        "" keep searching until we hit end-of-buffer
        let ret = searchpos(a:pat,'W')
        while (ret[0] != 0)
            "" break if the cursor is now at or past end; must do this prior to incrementing for most recent match, because if the match start is at or past end, it's not a valid match for the caller
            if (ret[0] > end[0] || ret[0] == end[0] && ret[1] >= end[1])
                break
            endif
            let matchCount += 1
            if (ret[0] != lastMatchLine)
                let lineCount += 1
                let lastMatchLine = ret[0]
            endif
            let ret = searchpos(a:pat,'W')
        endwhile
        "" restore original cursor position
        call winrestview(wsv)
        "" return result
        return [matchCount,lineCount]
    endfun
    
    fun! LineMatchCount(pat,...)
        "" searches for pattern matches in the active buffer, with optional start and end line number specifications
        "" useful command-line for testing against last-used pattern within last-used visual selection: echo LineMatchCount(@/,getpos("'<")[1],getpos("'>")[1])
        if (a:0 > 2)| echoerr 'too many arguments for function: LineMatchCount()'| return| endif
        let start = a:0 >= 1 ? a:000[0] : 1
        let end = a:0 >= 2 ? a:000[1] : line('$')
        "" validate args
        if (type(start) != type(0))| echoerr 'invalid type of argument: start'| return| endif
        if (type(end) != type(0))| echoerr 'invalid type of argument: end'| return| endif
        if (end < start)| echoerr 'invalid arguments: end < start'| return| endif
        "" save current cursor position
        let wsv = winsaveview()
        "" set cursor position to start (defaults to start-of-buffer)
        call setpos('.',[0,start,1,0])
        "" accumulate line count in local var
        let lineCount = 0
        "" keep searching until we hit end-of-buffer
        let ret = search(a:pat,'cW')
        while (ret != 0)
            "" break if the latest match was past end; must do this prior to incrementing lineCount for it, because if the match start is past end, it's not a valid match for the caller
            if (ret > end)
                break
            endif
            let lineCount += 1
            "" always move the cursor to the start of the line following the latest match; also, break if we're already at end; otherwise next search would be unnecessary, and could get stuck in an infinite loop if end == line('$')
            if (ret == end)
                break
            endif
            call setpos('.',[0,ret+1,1,0])
            let ret = search(a:pat,'cW')
        endwhile
        "" restore original cursor position
        call winrestview(wsv)
        "" return result
        return lineCount
    endfun