Thursday, January 20, 2011

modeline on steroids

Modeline is a great tool. Yet, sometimes it is also quite limiting: it can change only limited number of options. My most often gripe - one can't change the 'makeprg' to add the target (for what in past I attempted to use the workaround).

Reading through the modeline documentation, I have found an idea how to improve the modeline to allow to execute random VIM commands, not just setting few options.

Here it goes.

function! CustomModeLine(cid)
        let i = &modelines
        let lln = line("$")
        if i > lln | let i = lln | endif
        while i>0
                let l = getline(lln-i+1)
                if l =~ a:cid
                        exec strpart(l, stridx(l, a:cid)+strlen(a:cid))
                let i = i-1

au BufReadPost * :call CustomModeLine("VIM666:")

Now, after adding that to the .vimrc, one can put something like this in the last lines (among &modelines last of them) of the source file:

// VIM666:set makeprg=make\ aaaa
// VIM666:sy keyword cType block_id_t

When opening the file in VIM, the scriptlet would find the lines and :execute them. First would add to the makeprg a default target "aaaa", second would make symbol "block_id_t" to be highlighted as a C/C++ type (typedef from my pet not-really-a-project).

Why the VIM666:?

Well. VIM creator has allowed the modelines to change only limited set of options for the reason:

No other commands than "set" are supported, for security reasons (somebody
might create a Trojan horse text file with modelines). And not all options
can be set. For some options a flag is set, so that when it's used the
|sandbox| is effective.

Thus, randomizing the "VIM666" into something more random and unpredictable is strongly advised. Sadly, :sandbox isn't configurable and thus incapable of disallowing only certain actions - at the moment it is all-or-nothing type of command in VIM.

Tuesday, January 11, 2011

[DUMP] VIM custom folding for Rational Purify's plog files


" Fold (collapse) the Purify problem descriptions into single line
" As the comment to the fold, Purify message would appear with
" list of files/libraries in the call stack.
"-CHAIN_LENGTH=32" parameter in PURIFYOPTIONS is highly recommended
" custom fold function for Purify's .plog files
function! PuriFold(lnum)
        let l = getline(a:lnum)
        " fold starts at 'XXX:' marker (though exclude File In Use)
        if l =~ '^[A-Z][A-Z][A-Z]:' && l !~ '^FIU:'
                return 1

        " store fold level of previous line
        let pfl = foldlevel(a:lnum-1) > 0

        " check whether it is a continuation of a fold
        if pfl && (=~ '^\t' || l =~ '^  \*' || l =~ '^    ')
                return 1

        if pfl
                return '<1'     " close fold if open
                return 0        " not a fold otherwise

" custom fold text function to quickly see the approx location of the problem
function! Puri_GatherNames(fl, ll)
        let l:i = a:fl
        let l:x = ''
        let l:count = 0
        while l:i <= a:ll && l:count < 10
                let l:l = getline( i )
                if l:l =~ '\*unknown func\*'
                        let l:l = ''
                let l:tmp = matchstr( l:l, '\[[^\]]\+\]$' )
                if strlen(l:tmp) == 0
                        let l:i = l:i+1
                if l:tmp =~ '^\[libclntsh\.so'
                        let l:tmp = '[*ORA*]'
                if l:tmp =~ '^\[libstlport\.so' ||
\                  l:tmp =~ '^\[_\(pair\|tree\|alloc\|map\|construct\)\.h:' ||
\                  l:tmp =~ '^\[_\(tree\)\.c:'
                        let l:tmp = '[*STL*]'
                if strlen(l:tmp)>0 && l:tmp!=strpart(l:x,strlen(l:x)-strlen(l:tmp))
                        if l:tmp == '[crt1.o]' || l:tmp == '[rtlib.o]' ||
\                          l:tmp == '[crti.o]' || l:tmp == '[libCrun.a]' ||
\                          l:tmp == '[]' || l:tmp == '[]' ||
\                          l:tmp == '[]'
                                " nothing, ignore system libraries
                                let l:x = l:x.' '.l:tmp
                                let l:count = l:count+1
                "echo x
                let l:i = l:i+1
        return l:x
function! Puri_FoldText()
        return foldtext().Puri_GatherNames(v:foldstart, v:foldend)

" activate the folds when .plog file is opened
au BufReadPost *.plog set foldexpr=PuriFold(v:lnum)
au BufReadPost *.plog set foldmethod=expr
" use custom fold text function
au BufReadPost *.plog set foldtext=Puri_FoldText()

P.S. WT Rational Purify?

Monday, January 10, 2011

VIM inline calculator revisited, floating point numbers comapatible

As it turned out, sometimes I really like to use the VIM as a calculator, with free editing, history and even access to env vars. (Unintentionally, calls to VIM functions also do work.)

Yesterday I needed to calculate few more things than usually and also using floating point numbers. I have recalled that VIM 7.2 added support for Float type and tried it out. After some tinkering, I managed to make my calculator even more useful to me than expected.

The previous version of calculator looked relatively innocent:

:map <silent> <F11> :exec "s!^\\([^=]\\{-}\\)\\( *=.*\\)\\=$!\\1 = ".eval(substitute(getline(".")," *=.*$","",""))."!"<CR>

The new version looks more serious and even less intimidating:

function! CalcGetValue(tag)
        let tag_marker = "#"
        let s = search( '^'.a:tag.tag_marker, "bn" )
        if s == 0 | throw "Calc error: tag ".tag." not found" | endif
        " avoid substitute() as we are called from inside substitute()
        let line = getline( s )
        let idx = strridx( line, "=" )
        if idx == -1 |  throw "Calc error: line with tag ".tag."doesn't contain the '='" | endif
        return strpart( line, idx+1 )

function! CalcLine(ln)
        let tag_marker = "#"
        let s = getline(a:ln)

        " remove old result if any
        let x = substitute( s, '[[:space:]]*=.*'"""" )
        " strip the tag, if any
        let x1 = substitute( x, '[a-zA-Z0-9]\+'.tag_marker.'[[:space:]]*'"""" )
        " replace values by the tag
        let x1 = substitute( x1, tag_marker.'\([a-zA-Z0-9]\+\)''\=CalcGetValue(submatch(1))''g' )
        " evaluate
        let v = 0
                let v = "".eval(x1)
        catch /E806:/
                " VIM can't convert float to string automagically, apply printf("%g")
                let v = "".eval('printf( "%g", '.x1.')')
        " finish the job
        call setline(a:ln, x.' = '.v)

:map <silent> <F11> :call CalcLine(".")<CR>

The new version addresses two my problems (old) allow simple reuse of the previous calculations and (new) allow floating point numbers.

At the core, the usage of calculator remained pretty much the same as before. For example, typing 2*2 and pressing F11 would change the line into 2*2 = 4.

To allow reuse of previous calculation results, I have introduced the tags. A line with a calculation result can be prefixes with label# tag (e.g. label# 2*2 = 4). Results of the calculation can be accessed in another expression using #label, e.g. 33*#label. During evaluation, the scriptlet above would search the buffer for string starting with label# and put into the original expression whatever is found after the '=' on the matching line. (What has turned out to be a minor, nice-to-have feature: expression itself is optional; line label# = value works as a definition of constant).

Example: calculate area and volume given the radius. Type this:

radius# = 5
pi# = 3.1415
area# pow(#radius,2)*#pi
volume# pow(#radius,3)*#pi*4.0/3.0

First two lines work like constants denoting Pi and the radius. Press the F11 (or ^O + F11 if in insert mode) when cursor on 3rd and 4th lines to evaluate the expressions and see the results:

area# pow(#radius,2)*#pi = 78.5375
volume# pow(#radius,3)*#pi*4.0/3.0 = 523.583333

The new (to me) features that are used in the new calculator:

1. search() to find a line number of a string matching given pattern (used to search in the buffer for the line with a tag).

2. submatch(), when inside the substitution, to access a submatch (functional equivalent of \1, \2 and so on).

3. \= in s/// and substitute() to substitute with result of an expression instead of a constant.

4. E806 + :try. VIM can't convert float to string automatically. Error E806 is generated when that is attempted. Scriptlet catches the error and applies suggested by documentation conversion method - printf(). The construct with try/catch also ensures that integer calculation remains integer. Only if there are floating point numbers involved, exception would be thrown and the conversion would be performed (spares the redundant '.0' in the integer calculations).