1

How can I make a viml function that scans highlighted text for all occurrences of [ <number> u ] and [ <number>u ] and sums them?

EXAMPLE:

The text

Here is some text. [0.1u]
Here is some more text [0.2 u]
And here is some noise: 0.2

should yield 0.3.

0
2

The following solution works for me:

vnoremap <silent><expr> <c-x> YankAndAnalyse()
nmap     <silent>       <c-x> vip<c-x><esc>

function! YankAndAnalyse()
  return 'y'
        \ . ":call Analyse()\<CR>"
        \ . ":silent normal! gv\<CR>"
endfunction

function! Analyse()
  let selection = split(getreg(''), '\n')
  let numbers = map(selection,
        \ 'matchstr(v:val, ''\[\s*\zs\d\+\%(\.\d\+\)\?\ze\s*u\s*]'')')
  call filter(numbers, '!empty(v:val)')
  call map(numbers, 'str2float(v:val)')
  if empty(numbers) | return | endif

  echohl Identifier
  echon 'Sum: ' . printf('%g', eval(join(numbers, ' + ')))
  echohl None
endfunction

Here YankAndAnalyse is a simple wrapper function that first fills the default register. Analyse reads the default register and parses the numbers that match your critera. The YankAndAnalyse function is mapped to CTRL-x in visual mode (feel free to choose whatever you want), and the normal mode map simply applies the visual mode map to the current paragraph.

See the documentation for more help, e.g. :h matchstr() for help on the matchstr function.


Update: The above solution only sums the first matched number in each line. In order to sum all matched numbers, one may use the following version of the Analyse function:

function! Analyse()
  let input = getreg('')
  let sum = 0
  let n = 1
  let new = matchstr(input, '\[\s*\zs\d\+\%(\.\d\+\)\?\ze\s*u\s*]', 0, n)
  while !empty(new)
    let n += 1
    let sum += str2float(new)
    let new = matchstr(input, '\[\s*\zs\d\+\%(\.\d\+\)\?\ze\s*u\s*]', 0, n)
  endwhile

  echohl Identifier
  echon 'Sum: ' . printf('%g', sum)
  echohl None
  return
endfunction
2
  • If there are two [0.1u] and [0.1u] on the same line, then this script only sums them as 0.1, not 0.2. Is there a way to make this aggregate the sum across one line as well as across multiple lines? Jun 3 '17 at 16:08
  • Sorry, I almost forgot to answer this. I've updated the answer with a slightly modified version of the Analyse function that does what you want. Jun 16 '17 at 22:26
0

I've learned to avoid loop to speed-up processing times in vim scripts.

Given Karl's answer, my analyse function will be:

function! Analyse() abort
   let tab = []
   " I fill the tab with all the matching numbers thanks to substitute + \= + add(...)[0]
   call substitute(@", '\v\[\zs(\d+(\.\d+)?|\.\d+)\ze\s*u\]', '\=add(t,submatch(0))[0]', 'g')
   " I fix the numbers written as '.1' into '0.1'
   call map(tab, '(v:val[0]=="." ? "0" :"") . v:val')
   " Now, I only need to reduce the tab
   return eval(join(tab, '+'))
endfunction

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.