所以我有一个这样的文本文件:
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()
,这意味着每个选定行调用一次函数。不好,对吗?所以据我所知,没有办法解决这个问题。我该怎么做才能使功能:
答案 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!
。
修改强>
快速解释一下我的工作流程以及长篇单行的原因。
:g
和ex命令。答案 1 :(得分:1)
我知道我的vim7.3-script
比adscriven
长得多
但它可以完成工作。
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]}