package Vile::Search; # # This package contains perl subroutines which are intended as a # drop in replacement for vile's search facilities. Not all features # (such as visual matches) are implemented yet. # # These bindings are proper motions, however, so things like 'dn' # or 'd/foo' will work as expected. # # Usage # ----- # :perl require "search.pl"; *CHUNKSIZE = \100; my $direction = ''; #open TTY, ">/dev/tty"; # for debugging # # Get the pattern to search for, either from the user or from the # previously stashed value in vile's search variable. # sub getpat { my ($how, $ldirection) = @_; if (defined($how) and $how eq 'noprompt') { $pat = Vile::get('search') } else { $direction = $ldirection; $pat = Vile::mlreply_no_opts($direction eq 'forward' ? 'Perl search: ' : 'Reverse perl search: ', scalar Vile::get('search')); } Vile::set(search => $pat) if defined($pat); return $pat; } # # Back references (e.g, \1, \2, etc.) need to be adjusted to work # properly since our search expression contains parenthesized # expressions too. # sub fixbackreferences { my $pat = shift; my $adj = shift; my $lpcount = 0; # number of unescaped left parens $lpcount++ while $pat =~ /(^|[^\\])\(/g; if ($lpcount > 0) { $pat =~ s/\\(\[1-9][0-9]*|.)/ "\\" . (($1 + 0 && $1 <= $lpcount) ? $1+$adj : $1)/gex; } return $pat; } # # Search forward. This is not as straightforward as it could be since # we attempt to fetch the lines in chunks for efficient searching. # sub fsearch { my $pat = getpat(shift, 'forward'); return 0 unless defined($pat); my $wrap = 0; my $cb = $Vile::current_buffer; my @start_dot = $cb->current_position; my $lastline = ($cb->setregion(1,'$'))[2]; my $chunkstart = $start_dot[0]; my $pos = $start_dot[1]+1; if ($pos >= ($cb->setregion($chunkstart,0,$chunkstart,'$$'))[3]) { $pos = 0; $chunkstart++; $chunkstart = 1 if ($chunkstart > $lastline); } my $chunkend = $chunkstart + $CHUNKSIZE; $pat = fixbackreferences($pat, 1); while (1) { $cb->set_region($chunkstart, $chunkend-1); $chunk = $cb->fetch; pos($chunk) = $pos; $pos = 0; if ($chunk =~ /($pat)/mg) { my $lc = 0; my $matchlen = length($1); $chunk = substr($chunk, 0, pos($chunk)); $lc++ while $chunk =~ /\n/g; $chunk =~ s/.*\n//g; $cb->current_position($chunkstart + $lc, length($chunk) - $matchlen); if ($wrap) { @dot = $cb->current_position; if ($start_dot[0] == $dot[0] and $start_dot[1] == $dot[1]) { print "Only one occurence of pattern"; } else { print "[Search wrapped past end of buffer]"; } } return 1; } } continue { $chunkstart = $chunkend; if ($wrap) { last if $chunkstart > $start_dot[0]; } elsif ($chunkstart > $lastline) { $wrap = 1; $chunkstart = 1; } $chunkend = $chunkstart + $CHUNKSIZE; } print "Not found"; return 0; } # # Search backward # sub rsearch { my $pat = getpat(shift, 'backward'); return 0 unless defined($pat); my $wrap = 0; my $cb = $Vile::current_buffer; my @start_dot = $cb->current_position; my $lastline = ($cb->setregion(1,'$'))[2]; my $chunkend = $start_dot[0]+1; my $pmpat; if ($start_dot[1] == 0) { if ($chunkend <= 2) { $chunkend = $lastline+1; } else { $chunkend--; } $pmpat = '.*'; } else { $pmpat = ".{0,@{[$start_dot[1]-1]}}"; } my $chunkstart = $chunkend - 1; $chunkstart = 1 unless $chunkstart > 0; $cb->set_region($chunkstart, 0, $chunkend-1, '$'); $pat = fixbackreferences($pat, 2); # $ matches at both the newline and the position after the newline. # Eliminate one of these cases. $pat =~ s/(^|[^\\])\$$/$1(?=\n\$)/; while (1) { $chunk = $cb->fetch; pos($chunk) = $pos; $pos = 0; if (my ($prematch, $match) = $chunk =~ /\A($pmpat)($pat)/mg) { my $lc = 0; $lc++ while ($prematch =~ /\n/g); $prematch =~ s/.*\n//g; $cb->current_position($chunkstart + $lc, length($prematch) ); if ($wrap) { @dot = $cb->current_position; if ($start_dot[0] == $dot[0] and $start_dot[1] == $dot[1]) { print "Only one occurence of pattern"; } else { print "[Search wrapped past end of buffer]"; } } return 1; } } continue { $chunkend = $chunkstart; if ($wrap) { last if $chunkend <= $start_dot[0]; } elsif ($chunkend <= 1) { $wrap = 1; $chunkend = $lastline + 1; } $chunkstart = $chunkend - $CHUNKSIZE; $chunkstart = 1 unless $chunkstart > 0; $cb->set_region($chunkstart, $chunkend-1); $pmpat = "[\000-\377]*"; } print "Not found"; return 0; } # # Find next occurrence of pattern in the current direction # sub searchnext { $direction eq 'forward' ? fsearch('noprompt') : rsearch('noprompt'); } # # Find previous occurrence of pattern in current direction # sub searchprev { $direction eq 'forward' ? rsearch('noprompt') : fsearch('noprompt'); } # # Register the above as Vile procedures # Vile::register_motion 'perl-fsearch' => \&fsearch, "Forward search with perl"; Vile::register_motion 'perl-rsearch' => \&rsearch, "Reverse search with perl"; Vile::register_motion 'perl-search-next' => \&searchnext, "Search next"; Vile::register_motion 'perl-search-prev' => \&searchprev, "Search prev"; # # Set up the standard keybindings # Vile::command("bind-key perl-fsearch /"); Vile::command("bind-key perl-rsearch ?"); Vile::command("bind-key perl-search-next n"); Vile::command("bind-key perl-search-prev N"); 1;