Monday, March 26, 2012

Mapping: disable the search history modification

A nice trick from :h histdel() to remove the last entry from search history:

:call histdel("search", -1)
:let @/ = histget("search", -1)


That can be used the following way (a mapping to insert HTML's paragraph break (</p>\n<p>) before the word under cursor):

map <silent> <F3> :s!^\(\s*\)\(.\{}\)\s\+\(\S*\%#\S*\)!\1\2</p>\r\1<p>\3!<CR>:call histdel("search", -1)<CR>:let @/ = histget("search", -1)<CR>


With the histdel() andreset of @/, the search pattern replaced by :s is reset back to what it was before and thus the n/N keys work as expected. (You will not believe it: I have spent many years in VIM without knowing about the N shortcut. Discovering it by an accident was like enlightenment to me.)

OK, the mapping gets obsessively long, but works as expected. One can't have it all.

Sunday, January 29, 2012

[off-topic] Perl on Windows: handling files with Unicode characters

Using Strawberry Perl.

The rename with the proper Win32 module initialization:


# init
use strict;
use warnings;
use utf8;
use Win32::OLE qw(in);
Win32::OLE->Option( CP => Win32::OLE::CP_UTF8 );

# the code
my $fso = Win32::OLE->new("Scripting.FileSystemObject");
$fso->MoveFile( ".\\".$old_name , ".\\".$new_name );


Recursively scan directory tree:


# the code
sub scan1
{
my ($f, $l) = @_;
my $obj = Win32::OLE->new('Scripting.FileSystemObject');

my $folder = $obj->GetFolder( $f );
die "ERROR: $f" unless $folder;
foreach my $file (in $folder->Files) {
print $file->{Name}, ", size ", $file->{Size}, "bytes\n";
}

my $collection = $folder->{SubFolders};
foreach my $value (in $collection) {
my $foldername = $value->{Name};
scan1( "$f\\$foldername", $l );
}
}
my @l;
scan1( 'c:\\Movies', \@l );


Scripting.FileSystemObject documentation.

Tuesday, January 17, 2012

A use case for \%#

Some time ago, per chance, quite obscure feature of VIM's regular expressions: \%# - :h /\%# says "Matches with the cursor position."

At first I couldn't see any use for it.

But then, while editing per hand some XMLs, I stumbled upon a problem: how to insert a tag break (close and open), while adding the indentation? Normal VIM macrii(*) can insert - but one looses the cursor position as soon as movement commands are used. But to make a copy of the indentation, one needs to move the cursor to the beginning of the line.

Edit0. OK. It just dawned on me. One could first insert the XML tag break + new line. Then copy indentation. Uh. Need to sleep more and more often. But I already wrote the post so what the heck I'll just post it.

Then I recalled that the regular expressions could match current cursor position. And I had a hunch that the \%# could be used for the purpose. The Enlightenment come only few days later and took shape of that for the <p> tag:

:s!^\(\s*\)\(.\{}\)\s\+\(\k*\%#\k*\)!\1\2</p>\r\1<p>\3!

First () submatch are the spaces (= indentation) of the current line.

Second () submatch is everything up to the word under cursor (matched non-greedily as I had some fancy problems with greedy match here).

Third () submatch is the actual word under cursor, anchored by the \%# to the current cursor position. (The redundant spaces before the word are trimmed between the second and third submatches.)

That all is replaced by. Original line: \1\2 is the line up to the word under cursor, closing </p> tag and \r for new line. New line: \1 which is the wanted indentation, opening <p> tag and finally the word (obviously followed by the rest of the original line).

P.S. \S (anything but space) in place of \k works pretty well too.

P.P.S. VIM should support some sort of nesting of regular expressions. During editing lots of pieces could be reused - but only reuse regular expressions do support is the copy-paste. The Clue.


(*) Because for the particular task at hand, macros have infested irreversibly my vimrc. Just like virii. Any press of a wrong button, and only the undo can sort out what the hell has just happened.

Sunday, January 08, 2012

[link] LOCALE settings and regexp classes

Spent some time editing a German text (actually an FB2 book) in VIM. Hell of a job, because as it turned out, I wasn't blind: VIM really doesn't support locale in the regular expressions.

Workaround is to use \k (also suggested \i doesn't match ß). But that's only half workaround, since \k is case insensitive and case is used in German (e.g. nouns are capitalized).

Another workaround is to use Perl or Python integration for regular expressions, since both provide locale support in the regular expressions. But I haven't gone that far yet.

Edit1. Do NOT use Perl for the purpose. Locale/utf8 support is totally and utterly messed up.

P.S. Here are some of my FB2 editing helper functions.



" some Fiction Book functions

"
" merge adjacent paragraphs
"
function! ParaMany_ToSingle() range
        let lines = getline(a:firstlinea:lastline)
        call filter( lines, 'v:val !~ "^$"' )
        let xind = substitute( lines[0], '^\(\s*\).*''\1''' )
        for i in range( 0len(lines)-1 )
                if i != 0
                        let lines[i] = substitute( lines[i], '^\s*<p>[ \t]*''''' )
"                       echo i." < ".lines[i] | sleep 2
                endif
                if i != len(lines)-1
                        let lines[i] = substitute( lines[i], '[ \t]*</p>\s*$''''' )
"                       echo i." > ".lines[i] | sleep 2
                endif
        endfor
"       echo "xxx:".join( lines, " " ) | sleep 2
        if a:lastline > a:firstline
                exec ':'.(a:firstline+1).",".a:lastline."d"
        endif
        let text = join( lines, " " )
        let text = substitute( text, '[ \t]\{2,}'' ''g' )
        let text = substitute( text, '^\s\+''''' )
        call setline( a:firstline, xind.text )
endfunction

" gvim helper keyboard shortcuts (c-up/-down do not work in terminal)
function! Keyboard_ParaCUpDown()
        map <C-Up> :-1,.call ParaMany_ToSingle()<CR>
        map <C-Down> :.,+1call ParaMany_ToSingle()<CR><Up>
endfunction

"
"  insert section break, using the line's text as the section title
"
function! Para_ToSectionBreak()
        let t = getline(".")

        " capture the line indentation
        let xind = substitute( t, '^\(\s*\).*''\1''' )
        " etch a bit from section tag indentation
        let xindS = substitute( xind, '^\(\s*\)\s$''\1''' )

        " clean-up tags
        let t = substitute( t, '\v\</{0,1}[a-z]+[^>]*\>''''g' )
        let t = substitute( t, '[ \t]\+$''''' )
        let t = substitute( t, '^[ \t]\+''''' )

        " generate id, ensure starts with letter or _
        let id = substitute( t, '[^a-zA-Z0-9_-]''_''g' )
        let id = substitute( id, '^\([^a-zA-Z_]\)''_\1''g' )

        let l = [xindS.'</section>',
\               '',
\               xindS.'<section>',
\               xind.'<title>',
\               xind.'<p id="'.id.'">'.t.'</p>',
\               xind.'</title>' ]
        call setline( ".", l[0)
        call  append( ".", l[1:] )

        call cursor( line(".")+len(l)0 )
endfunction

"
"  convert line's text to subtitle
"
function! Para_ToSubtitle()
        let t = getline(".")
        let xi = substitute( t, '^\(\s*\).*''\1''' )
        let t = substitute( t, '\v\</{0,1}[a-z]+[^>]*\>''''g' )
        let t = substitute( t, '[ \t]\+$''''' )
        let t = substitute( t, '^[ \t]\+''''' )
        call setline( ".", xi.'<subtitle>'.t.'</subtitle>' )
endfunction

Friday, November 25, 2011

[link] More Fun with Vimscript

More Fun with Vimscript by Juliet Kemp.

Mentions something totally new to me: ZOMG! VIM has a function debugger!!

:h debug-scripts.

Example :debug call CustomeFunc().

P.S. Wow, and profiler too! :h profiling

Sunday, February 20, 2011

[link] Getting more out of Vim - some tips

Some VIM tips from FSM (Free Software Magazine).

The neat trick to count words - :s/pattern//gn - somehow I have missed it before.

^O/^I (in normal mode, :h jump-motions) are cool too. Work like Back/Forward buttons on the web browsers.

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))
                endif
                let i = i-1
        endwhile
endfunction

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

Dump.


"
" 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
        endif

        " 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
        endif

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

" 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 = ''
                endif
                let l:tmp = matchstr( l:l, '\[[^\]]\+\]$' )
                if strlen(l:tmp) == 0
                        let l:i = l:i+1
                        continue
                endif
                if l:tmp =~ '^\[libclntsh\.so'
                        let l:tmp = '[*ORA*]'
                endif
                if l:tmp =~ '^\[libstlport\.so' ||
\                  l:tmp =~ '^\[_\(pair\|tree\|alloc\|map\|construct\)\.h:' ||
\                  l:tmp =~ '^\[_\(tree\)\.c:'
                        let l:tmp = '[*STL*]'
                endif
                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 == '[libc.so.1]' || l:tmp == '[libnsl.so.1]' ||
\                          l:tmp == '[libsocket.so.1]'
                                " nothing, ignore system libraries
                        else
                                let l:x = l:x.' '.l:tmp
                                let l:count = l:count+1
                        endif
                endif
                "echo x
                let l:i = l:i+1
        endwhile
        return l:x
endfunction
function! Puri_FoldText()
        return foldtext().Puri_GatherNames(v:foldstart, v:foldend)
endfunction

" 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 )
endfunction

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
        try
                let v = "".eval(x1)
        catch /E806:/
                " VIM can't convert float to string automagically, apply printf("%g")
                let v = "".eval('printf( "%g", '.x1.')')
        endtry
        " finish the job
        call setline(a:ln, x.' = '.v)
endfunction

: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).

Thursday, December 16, 2010

Make (next) found match to appear in the screen's center

Turned out to be easier than expected:

nnoremap n nzz
nnoremap N Nzz


Simply adds the zz (see :h z.) to n and N commands.

P.S. Original question and answer as SO.

Edit1 Problem with the solution is that the message search hit BOTTOM, continuing at TOP, due to the zz command, now doesn't appear on the screen anymore. Trying now to use the other tip: set scrolloff=5. Will see how that works.

Edit2 Yep, the set scrolloff=5 works for me much better than the nzz.