Show Perl subname in vim statusline
I asked on the vim mailing list how to see the name of Perl's current sub/method in the status line and Alan Young, the author of PPIx::IndexLines has a great suggestion which unfortunately relied on PPI. I'm working with very large modules and PPI ground to a halt for me. As a result, I took his suggestion and worked out the following.
First, make sure that your .vimrc has set laststatus=2 in it. That will ensure that you always get a status line, even if you only have one window (i.e., don't have split windows). Then drop the following into your .vim/ftplugin/perl.vim:
if ! exists("g:did_perl_statusline")
setlocal statusline+=%(\ %{StatusLineIndexLine()}%)
setlocal statusline+=%=
setlocal statusline+=%f\
setlocal statusline+=%P
let g:did_perl_statusline = 1
endif
if has( 'perl' )
perl << EOP
use strict;
sub current_sub {
my $curwin = $main::curwin;
my $curbuf = $main::curbuf;
my @document = map { $curbuf->Get($_) } 0 .. $curbuf->Count;
my ( $line_number, $column ) = $curwin->Cursor;
my $sub_name = '(not in sub)';
for my $i ( reverse ( 1 .. $line_number -1 ) ) {
my $line = $document[$i];
if ( $line =~ /^\s*sub\s+(\w+)\b/ ) {
$sub_name = $1;
last;
}
}
VIM::DoCommand "let subName='$line_number: $sub_name'";
}
EOP
function! StatusLineIndexLine()
perl current_sub()
return subName
endfunction
endif
All this does is naïvely read backwards from the current line to get "sub $name" and report $name. It will fail on many common cases. However, it's fast. Very fast. Unlike the PPI solution, I can use this and manually correct any files which don't fit this convention.
It's a quick and nasty hack, but already I'm finding it very useful. Suggestions welcome :)
Note that this requires Perl support. Just ":echo has('perl')" and if it displays '1', you're good to go. Then type "help perl-using" to see what's going on.
Update: I've updated that statusline. There's a space after the '\' and it now shows the column, filename and percent. See ":help statusline" or this blog post for more ideas.
Update2: Changed "set" to "setlocal" so we don't screw with non-Perl buffers.
Update3: If I do an ":e $anotherfile", I lose the new status line. Eliminating the exists("g:did_perl_statusline") seems to fix this.
from the snarky-but-true dept...
Alternatively, use short enough subs that you can always see the sub name when editing the sub (and manually correct any files which don't fit this convention).
Nifty!
Now if we could get petdance to include that snippet, we might see the first 2011 commit here: https://github.com/petdance/vim-perl
@wickline: from the snarky-but-true dept...
I know your coding ability and I have tremendous respect for it. Now put your money where your mouth is and send me your CV so we can get you hired and I can watch you publicly retract that statement a year later :)
In Emacs this is:
Which you can automatically load with a mode hook. It'll work with any programming mode that supports Imenu. Which is almost all of them.
A bit of bike-shedding, but I like:
my $line = $document[$i]; last if $line =~ /^}/ and $i != ($line_number-1); if ( $line =~ /^\s*sub\s+(\w+\b.*?){?$/ ) {Or just rotate the monitor vertically; works ;)
From the bike-shedding department, the lines:
my @document = map { $curbuf->Get($_) } 0 .. $curbuf->Count; my ( $line_number, $column ) = $curwin->Cursor;can become:
my ( $line_number, $column ) = $curwin->Cursor; $line = $curbuf->Get($line_number--) while $line_number >=0 && !/^\s* sub \s+ (\w+)/x;.. so you can avoid ->Get-ting all 8k lines when you need "only" 200 ;)
From the bike-shedding department, the lines:
my @document = map { $curbuf->Get($_) } 0 .. $curbuf->Count; my ( $line_number, $column ) = $curwin->Cursor;could become:
my ( $line_number, $column ) = $curwin->Cursor; $line = $curbuf->Get($line_number--) while $line_number >=0 && !/^\s* sub \s+ (\w+)/x;.. so you can avoid ->Get-ting all 8k lines when you need "only" 200 ;)
Uh, double comment above, you may remove one.
Corrections after having used this a bit:
makes the variable local to the buffer instead of g: which is global
my $line_number = ($curwin->Cursor)[0]; my $line = ''; $line = $curbuf->Get( $line_number ) while $line_number-- > 0 && $line !~ /^\s*sub\s+(\w+)\b/; my $sub_name = $1 || '(not in sub)'; VIM::DoCommand("let subName='$sub_name'");I do believe this is faster than getting all lines for each cursor movement :)
Here's a function that determines the name of the current sub in much the same way, but it's in Vim script, so it works even if you don't have Perl embedded in Vim:
function! PerlCurrentSubName() let s:currline = line('.') let s:currcol = col('.') normal $ let [s:line, s:column] = searchpos('^\s*sub\s\+\zs\(\w\+\)','bcW') if (s:line != 0) let s:subname = expand('') call cursor(s:currline, s:currcol) return s:subname else return '(not in sub)' endif endfunctionActually, in this function we need to call cursor() even if there was no match, because "normal $" would insist on always keeping the cursor on the end of the line...
function! PerlCurrentSubName() let s:currline = line('.') let s:currcol = col('.') normal $ let [s:line, s:column] = searchpos('^\s*sub\s\+\zs\(\w\+\)','bcW') if (s:line != 0) let s:subname = expand('') else let s:subname = '(not in sub)' endif call cursor(s:currline, s:currcol) return s:subname endfunctionThere may be a better way of doing this without "normal $", and maybe there's a way of getting the result of the subpattern in search() directly without having to jump to the location and expanding cword, but my vim-fu is weak...
I played with your script for about an hour and made an improvement. This will display 'N/A' if you're outside the subroutine. If you have a history of your closing brace indent not matching your sub declaration indent you may want to alter the $indent portion. Have fun!
.vimrc syntax on setlocal laststatus=2 setlocal statusline=\ %{HasPaste()}%F%m%r%h\ %w\ \ CWD:\ %r%{CurDir()}%h\ \ \ Position:\ %p%%\ %l/%L,%c if has("autocmd") autocmd BufReadPost * if &syntax == 'perl' | source ~/.vim/perl_current_subroutine | endif endif.vim/perl_current_subroutine if ! exists("b:did_perl_statusline") && &syntax == 'perl' setlocal statusline+=%(\ \ \ Subroutine:\ %{StatusLineIndexLine()}%) let b:did_perl_statusline = 1 endif if has('perl') perl Cursor)[0]; my $line = $curbuf->Get($line_number); my $indent = ''; if ($line !~ /^(\s*)sub\s+(\w+)\b/) { $line = $curbuf->Get($line_number) while ($line_number-- > 0 && $line !~ /^(\s*)sub\s+(\w+)\b/); ($indent, $sub_name) = ($1, $2); } else { $line_number--; ($indent, $sub_name) = ($1, $2); } #if found, (try to) find the end of the subroutine if ($sub_name ne 'N/A') { my $end = $curbuf->Count(); $line = $curbuf->Get($line_number); if ($line !~ /}\s*$/) { $line = $curbuf->Get($line_number) while ($line_number++ Cursor)[0] > $line_number); } VIM::DoCommand("let current_perl_subroutine_name='$sub_name'"); } EOP function! StatusLineIndexLine() perl current_perl_subroutine() return current_perl_subroutine_name endfunction endif