"
" Script Name: sumofselection.vim
" Version: 1.10
" Last Change: May 7th, 2017
" Author: Ivo van Kamp
"
" Description:
" -----------
" Sums up a visual (block) selection of numbers and echoes result.
" Arithmetic operators are supported: ^ ** * % / - + ()
" Without an operator the addition sign is added between numbers.
" The result is stored in the unnamed register "" and register "0.
" You can paste the result in normal mode with p or "0p. And in
" insert mode with 0. The sum is stored in register "s.
"
" Usage:
" ------
" After visual (block) selection of text press 't' to echo the sum
" on the command line, or in insert mode press Alt-= to insert the
" sum of the last equation found on the current line.
"
" Configuration:
" --------------
" If you want to use other keys to calculate the sum, please add
" the following to your .vimrc, and replace with the desired
" key sequence:
"
" vmap SumOfSelection
" imap SumOfLastEquation
"
" The number of decimals can be configured by adding:
"
" let g:sumofselection_nrofdecimals = [Nr of decimals]
"
" A negative number means full precision. The default value is 2
" decimal places. The rounding method is set to 'common' (i.e.
" half round up).
" See also: https://www.mathsisfun.com/numbers/rounding-methods.html.
"
" Install:
" --------
" Copy this file into Vim's plugin directory.
"
" Links:
" ------
" http://www.vim.org/scripts/
" http://learnvimscriptthehardway.stevelosh.com/chapters/26.html
" http://vim.1045645.n5.nabble.com/my-vimrc-td3396590.html
if exists("g:loaded_sumofselection")
finish
endif
let g:loaded_sumofselection = 1
if !exists("g:sumofselection_nrofdecimals")
let g:sumofselection_nrofdecimals = 2
endif
vno SumOfSelection :call SumOfSelection()
ino SumOfLastEquation :call SumOfLastEquation()
" Default bindings
if !hasmapto('SumOfSelection', 'v')
if maparg('t','v') == ""
vmap t SumOfSelection
endif
endif
if !hasmapto('SumOfLastEquation', 'i')
if maparg(' SumOfLastEquation
endif
endif
" SumOfSelection() sums up a visual (block) selection of
" numbers and echoes the result.
"
" Arithmetic operators are supported: ^ ** * % / - + ()
" Without an operator the addition sign is added between numbers.
"
" The result is stored in the unnamed register "" and register "0.
" You can paste the result in normal mode with p or "0p. And in
" insert mode with 0. The sum is stored in register "s.
"
function! SumOfSelection() range
let @" = ""
let @0 = ""
let @s = ""
let resultString = ""
let resultList = []
let sum = ""
let total = ""
let warning = ""
let error = ""
let isVisual = 0
let hasError = 0
let yankedText = @*
redir! => resultString
silent perl << EOF
use strict;
use warnings;
use bignum;
my $mathSymbols = "*^\%\\-+\/";
my $sum = 0;
my $result = "";
my $warning = "";
my $nrOfDecimals = 0;
my $success = 0;
local $SIG{__WARN__} = sub {
$warning = shift =~ s/^\s+//r
};
($success, $sum) = VIM::Eval('yankedText');
$sum = $success==1 ? $sum : "";
$sum =~ s/(^|\n)[^0-9]*(\n|$)/ /g; # Replace lines with non-numeric chars with a space
$sum =~ s/[^0-9]*\.+([^0-9.]|$)+//g; # Remove non-numeric chars and their non decimal marks
$sum =~ s/[^0-9\.()$mathSymbols]+/ /g; # Replace chars not a number, point, parenthesis, or math symbol with a space
$sum =~ s/([0-9])\s+([0-9])/$1+$2/g; # If no symbol given between numbers use addition
$sum =~ s/\s//g; # Remove spaces
$sum =~ s/\*{2}/\^/g; # Temporarily use caret as exponentiation symbol
$sum =~ s/([\.$mathSymbols])([\.$mathSymbols])+/$1/g; # Keep only first of consecutive points or math symbols
$sum =~ s/([$mathSymbols]){1}/ $1 /g; # Insert some spaces to make sum easier to read
$sum =~ s/\^/\*\*/g; # Translate caret to Perl exponentiation symbol
$sum =~ s/[$mathSymbols]+\s*$//g; # Remove trailing math symbols
$result = eval($sum);
$result = defined $result ? $result : "";
($success, $nrOfDecimals) = VIM::Eval('g:sumofselection_nrofdecimals');
if ($success==1 && $result!="")
{
Math::BigFloat->round_mode('common');
$result = Math::BigFloat->new($result);
$nrOfDecimals = $nrOfDecimals < 0 ? undef : -$nrOfDecimals;
$result->bfround($nrOfDecimals);
}
if ($@) {
VIM::Msg($sum."|".$result."|".$warning."|".$@);
}
else {
VIM::Msg($sum."|".$result);
}
EOF
redir END
let resultString = substitute(resultString, '[\r\n]', '', 'g')
let resultList = split(resultString,"|")
let sum = get(resultList, 0)
let total = get(resultList, 1, "")
let warning = get(resultList, 2, "")
let error = get(resultList, 3, "")
" If the current line is not equal to the selection assume visual selection
let isVisual = getline(".")!=strpart(yankedText, 0, strlen(yankedText))
let hasError = error!=""
if (isVisual)
" Restore previous visual mode and escape (i.e. restore cursor pos).
silent exe "normal! gv\e"
endif
let @s=sum " save equation to register s
redraw " force redraw to prevent any postponed redraws (:help echo)
if (!hasError)
if (total!="")
let @"=total
let @0=total
echo total
endif
else
" Be more verbose after a multiline calculation error
if (isVisual)
" Truncate sum
let sum = strlen(sum)>50 ? strpart(sum,0,50)." [...]" : sum
echo "Calculation failed\n"
echo "Sum : ".sum."\n"
if (warning!="")
echo "Warning : ".warning
endif
echo "Error : ".error
else
" If there is a warning it is usually more
" precise than the error message.
if (warning!="")
echo "Warning : ".warning
else
echo "Error : ".error
endif
endif
endif
endfunction
" Search backwards on the current line for the first equals sign
" that has at least one number behind it. Yank everything after
" the equals sign into "*, then call SumOfSelection() and paste
" the result after the cursor if calculation was successful.
function! SumOfLastEquation()
let currentLine = getline(".")
let currentLineNumber = line(".")
let nrOfDigits = 0
let orgPos = getpos(".")
let start = 0
let stop = 0
let lnum = 0
" Walk backwards until it has taken at least one
" numeric character to get to an equals sign.
while (nrOfDigits < 1)
let stop = getpos(".")[2]
let lnum = search("=", "b", currentLineNumber)
" If '=' was found on current line
if (lnum != 0)
let start = getpos(".")[2]
let nrOfDigits = strlen(substitute(strpart(currentLine, start, stop-start), '[^0-9]*', '', 'g'))
else
let start=0
break
endif
endwhile
call setpos(".", orgPos)
" Set and release visual selection
" to make SumOfSelection() keep cursor pos
silent exe "normal! \e"
" Set visual selection register
let @*=strpart(currentLine, start, stop-start)
call SumOfSelection()
" If result, insert result
if (@0 != "")
startinsert
call feedkeys("\a\0")
endif
endfunction