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

Bri*_*cks 14 vim

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

Item a: <total>
  Subitem: 10 min
  Subitem 2: 20 min
Run Code Online (Sandbox Code Playgroud)

我想<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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

198*_*983 19

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

fun Foo() range
    ...
endfun
Run Code Online (Sandbox Code Playgroud)

然后你的函数可以使用特殊参数a:firstline和a:lastline来处理范围本身.(详见: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
Run Code Online (Sandbox Code Playgroud)

和定义为的函数

fun! Sum(n)
    let g:s += a:n
    return a:n
endfun
Run Code Online (Sandbox Code Playgroud)

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

: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
Run Code Online (Sandbox Code Playgroud)

顺便说一下,上面的命令作用于整个缓冲区.如果先突出显示一个范围然后按下该:键,vim将自动添加您的命令,'<,'>意味着以下命令将从突出显示的范围的开始到结束操作.

例如,突出显示第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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

然后,当命令到达第二个项目时,该命令将以"无效范围"错误中止.您可以让Vim忽略它并继续执行,无论前缀是否为整个命令:silent!.

编辑

只是快速解释我的工作流程以及为什么长的单线程.

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

  • 我花了很长时间才明白......所以这里有一个解释:g启动一个全局命令/ ^ Item /该命令在每一行匹配运行^ Item let g:s = 0 mark a存储游标的位置在a +, - /\nItem\| \%$/s /\v(\ d +)\ ze\s + min $ /\= Sum(submatch(0))/ g这是一个替代命令的巨大野兽.直到第一个's'的部分是范围,'s'和'$'之间的部分是模式,函数调用是替换(没有实际被替换)'a回到标记为/的位置<total> /\=(g:s.'min')/最后替换<total> (4认同)
  • 它......很漂亮......说真的,感谢我收到的最好的答案. (2认同)
  • 顺便说一句,对于那些认为上面的:g命令看起来很复杂的人来说,事实并非如此.语法非常简洁,可能会给它外观,但操作非常简单; 没有任何巧妙的技巧.只需查看精彩文档中没有的内容,或者询问SO或vim_use Google小组. (2认同)