So I have a text file like this:
Item a: <total>
Subitem: 10 min
Subitem 2: 20 min
I'd like to replace <total>
with the total of 10 and 20. Right now I'm doing it with the following functions:
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
gets the sum of numbers passed in, SumSelection
calls sum over all the numbers in selected lines, and (supposedly) Shift+e calls SumSelection in visual mode (or whatever you choose to call it.)
Problem is, when I hit Shift+e when I have some lines selected, instead of :call SumSelection()
I really get :'<,'>call SumSelection()
which means the function gets called once per selected line. No good, right? So as far as I can tell there's no way around this. What can I do to get the function to:
Well, to have a function execute just once over a range, append the range
keyword. E.g.
fun Foo() range
...
endfun
Then your function can take care of the range itself with the special parameters a:firstline
and a:lastline. (See :help a:firstline
for details.)
However I think that in this case your requirements are just about simple enough to be accomplished with
a one-liner using :global
.
We could do with better specified inputs and outputs really. But assuming a file containing
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
and a function defined as
fun! Sum(n)
let g:s += a:n
return a:n
endfun
then this will sum the subitems (all one line)
: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')/
and produce this output
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
By the way, the above command acts on the whole buffer. If you highlight a range first and then hit the :
key, vim will automatically prepend your command with '<,'>
meaning that the following command will operate from the start to the end of the highlighted range.
E.g. highlighting lines 1 to 5 and then running the command (:'<,'> g/...
) produces this output
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
One final note. If one of the groups has no subitems, e.g.
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
then the command will abort with an 'Invalid range' error when it reaches the second item. You can get Vim to ignore this
and carry on regardless by prefixing the whole command with :silent!
.
EDIT
Just a quick explanation of my workflow and why the long one-liner.
:g
and ex commands a lot.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With