Vim Scripting - 为所有选定的行调用一次函数

时间:2011-12-21 22:18:24

标签: vim

所以我有一个这样的文本文件:

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min

我想将<total>替换为总数10和20.现在我正在使用以下函数执行此操作:

let g:S = 0  "result in global variable S
function! Sum(number)
  let g:S = g:S + a:number
  return a:number
endfunction

function! SumSelection()
  let g:S=0
  '<,'>s/\d\+/\=Sum(submatch(0))/g
  echo g:S
endfunction

vnoremap <s-e> call SumSelection()<cr>

Sum获取传入的数字总和,SumSelection调用所选行中所有数字的总和,并且(假设)Shift + e以可视模式调用SumSelection(或者您选择调用的任何内容)它)。

问题是,当我选择一些行时按Shift + e,而不是:call SumSelection()我真的得到:'<,'>call SumSelection(),这意味着每个选定行调用一次函数。不好,对吗?所以据我所知,没有办法解决这个问题。我该怎么做才能使功能:

  1. 只能被叫一次
  2. 可能以更有效的方式总计这些

2 个答案:

答案 0 :(得分:19)

好吧,要让一个函数只在一个范围内执行一次,请附加range关键字。 E.g。

fun Foo() range
    ...
endfun

然后你的函数可以使用特殊参数a:firstline来处理范围本身 和a:最后一行。 (有关详细信息,请参阅:help a:firstline。)

但是我认为在这种情况下,您的要求非常简单,可以完成 单行使用:global

我们可以做更好的指定输入和输出。但假设一个文件包含

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min
Item b: <total>
  Subitem: 10 min
  Subitem 2: 23 min
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

和定义为

的函数
fun! Sum(n)
    let g:s += a:n
    return a:n
endfun

然后这将总结子项(所有一行)

:g/^Item/ let g:s = 0 | mark a | +,-/\nItem\|\%$/ s/\v(\d+)\ze\s+min$/\=Sum(submatch(0))/g | 'a s/<total>/\=(g:s . ' min')/

并生成此输出

Item a: 30 min
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33 min
  Subitem: 10 min
  Subitem 2: 23 min
Item c: 76 min
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

顺便说一句,上面的命令作用于整个缓冲区。如果先突出显示一个范围,然后点击:键,vim会自动在命令前添加'<,'>,这意味着以下命令将从突出显示的范围的开头到结尾操作。

E.g。突出显示第1行到第5行然后运行命令(:'<,'> g/...)会产生此输出

Item a: 30 min
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33 min
  Subitem: 10 min
  Subitem 2: 23 min
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

最后一点说明。如果其中一个组没有子项,例如

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min
Item b: <total>
Item c: <total>
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

然后该命令将中止“无效范围”&#39;到达第二个项目时出错。你可以让Vim忽略这一点 并且不管前缀是:silent!

整个命令

修改

快速解释一下我的工作流程以及长篇单行的原因。

  1. 我喜欢:g和ex命令。
  2. 我使用命令行历史记录和命令行窗口以交互方式构建命令进行编辑。
  3. 当我完成后,我将这样的一次性命令粘贴到缓冲区中:我有一个映射来执行当前行作为ex命令。这样,在我需要时,命令总是很容易获得。对于更常规的使用,您可能需要命令,函数或ftplugin。

答案 1 :(得分:1)

我知道我的vim7.3-scriptadscriven长得多 但它可以完成工作。

fun! SubTotal()
    fun! Collect()
        " define some global vars
        if line('.') == 1
            let g:dict = {}
            let g:key = ''
        endif

        " set current key && add key to dict
        fun! AddKey(k)
            let g:key = a:k
            let g:dict[a:k]= []
        endfun

        " add value to dict[key]
        fun! AddValue(v)
            if g:key != ''
                call add(g:dict[g:key], a:v)
            endif
        endfun

        " scan every line
        let line = getline('.')
        if line =~ '^Item'
            call AddKey(matchstr(line, '^Item\s\+\zs\w\+\ze\s*:'))
        elseif line =~ '^\s*Subitem'
            call AddValue(str2nr(matchstr(line, '\d\+\ze\s*min$')))
        endif
        return line
    endfun

    " collect data line by line
    silent %s/.*/\=Collect()/
    " replace <total> with sum
    silent g/^Item/s/^Item\s\+\(\w\+\)\s*:\s*\zs.*/\=eval(join(g:dict[submatch(1)], '+'))/
endfun

Item a: 30
  Subitem: 10 min
  Subitem 2: 20 min
Item b: 33
  Subitem: 10 min
  Subitem 2: 23 min
Item c: 76
  Subitem: 10 min
  Subitem 2: 23 min
  Subitem 3: 43 min

:echo dict
{'a': [10, 20], 'b': [10, 23], 'c': [10, 23, 43]}