package Psh::Completion; use strict; require Psh::Util; require Psh::OS; my $APPEND="not_implemented"; @Psh::Completion::bookmarks= (); @Psh::Completion::autoload=(); my %module_loaded=(); sub init { my $attribs=$Psh::term->Attribs; # The following is ridiculous, but.... if( $Psh::term->ReadLine eq 'Term::ReadLine::Perl') { $APPEND='completer_terminator_character'; } elsif( $Psh::term->ReadLine eq 'Term::ReadLine::Gnu') { $APPEND='completion_append_character'; } # Only ::Gnu understand it, and ::Perl ignores it silently. $attribs->{completion_display_matches_hook} = \&display_match_list; } sub start_module { my $command= shift; my $file= $Psh::Completion::modules{$command}; return unless $file; return if $module_loaded{$file}; open(FILE, "< $file"); my @lines= ; close(FILE); if (@lines) { my $text= join('',@lines); Psh::process_variable($text); $module_loaded{$file}=1; } } { my $kh_loaded=0; sub bookmarks { return @Psh::Completion::bookmarks if $kh_loaded; push @Psh::Completion::bookmarks, Psh::OS::get_known_hosts(); $kh_loaded=1; return @Psh::Completion::bookmarks; } } # Returns a list of possible file completions sub cmpl_filenames { my $text= shift; my $executable_only= shift||0; my $exclam=0; # HACK HACK HACK - this needs to be fixed some other way - # the completion code is severly messed I fear $text= Psh::Parser::unquote($text); $text=~ s/\\//g; # HACK END if ( $executable_only) { if ($text=~s/^\!//) { $exclam=1; } } my $globtext= $text; my $prepend= ''; if( substr($text,0,1) eq '"') { $prepend='"'; $globtext= substr($text,1); } my @result; if (substr($globtext,0,1) eq '~' and !($globtext=~/\//)) { # after ~ try username completion @result= cmpl_usernames($globtext); $Psh::Completion::ac="/" if @result; return @result; } @result= Psh::OS::glob("$globtext*"); if( Psh::Options::has_option('fignore')) { my @ignore= Psh::Options::get_option('fignore'); @result= grep { my $item= $_; my $result= ! grep { Psh::Util::ends_with($item,$_) } @ignore; $result; } @result; } if ( $executable_only) { @result= grep { -x $_ || -d _ } @result; } @result= map { -d $_ ? "$_/" : $_ } @result; # HACK: This won't help much if user tries to do another completion # on the same item afterwards @result= map { s/([ \'\"\´\`])/\\$1/g; $_ } @result unless $prepend eq '"'; if(@result==1) { if (substr($result[0],-1) eq '/') { $Psh::Completion::ac=''; } $Psh::Completion::ac=$prepend.$Psh::Completion::ac if $prepend; } foreach (@result) { if( m|/([^/]+\/?)$| ) { $_=$1; } } return @result; } # Returns a list of possible directory completions sub cmpl_directories { my $text= shift; my $globtext= $text; my $prepend= ''; if( substr($text,0,1) eq '"') { $prepend='"'; $globtext= substr($text,1); } my @result; if (substr($globtext,0,1) eq '~' and !($globtext=~/\//)) { # after ~ try username completion @result= cmpl_usernames($globtext); $Psh::Completion::ac="/" if @result; return @result; } @result= grep { -d $_ } Psh::OS::glob("$globtext*"); $Psh::Completion::ac=$prepend||''; @result= map { $_.'/' } @result; foreach (@result) { if( m|/([^/]+/?)$| ) { $_=$1; } } return @result; } # Returns an array with possible username completions sub cmpl_usernames { my $text= shift; my @result= grep { Psh::Util::starts_with($_,$text) } Psh::OS::get_all_users(); return @result; } # # Tries to find executables for possible completions sub cmpl_executable { my $cmd= shift; my @result = (); my $exclam=0; if ($cmd=~s/^\!//) { $exclam=1; } if (Psh::Strategy::active('built_in')) { if ($Psh::Support::Alias::loaded) { push @result, grep { Psh::Util::starts_with($_,$cmd) } Psh::Support::Alias::get_alias_commands(); } push @result, grep { Psh::Util::starts_with($_,$cmd) } Psh::Support::Builtins::get_builtin_commands(); } push @result, cmpl_directories($cmd) if $Psh::Completion::complete_first_word_dirs; local $^W= 0; Psh::Util::which($cmd); # set up absed_path if not already set and check foreach my $dir (@Psh::absed_path) { push( @result, map { $exclam?'!'.$_:$_ } grep { -x $dir.'/'.$_ && ! -d _ } Psh::OS::glob("$cmd*",$dir) ); } return @result; } # # Completes perl symbols { my %type; BEGIN { %type = ('$' => 'SCALAR', '*' => 'SCALAR', '@' => 'ARRAY', '$#' => 'ARRAY', '%' => 'HASH', '&' => 'CODE'); } sub cmpl_symbol { my ($text, $line, $start) = @_; my ($prefix, $pre, $pkg, $sym); no strict qw(refs); ($prefix, $pre, $pkg) = ($text =~ m/^((\$#|[\@\$%&])(.*::)?)/); my @packages = grep /::$/, $pkg ? keys %$pkg : keys %::; $pkg = ($Psh::PerlEval::current_package eq 'main' ? '::' : $Psh::PerlEval::current_package . '::') unless $pkg; my @symbols; if ($pre eq '$') { no strict 'vars'; # make `eval' quiet # I cannot use `defined *$sym{SCALAR}', # since it is always true. @symbols = grep (/^\w+$/ && (eval "defined $prefix$_" || ($sym = $pkg . $_, defined *$sym{ARRAY} || defined *$sym{HASH})), keys %$pkg); } else { @symbols = grep (/^\w+$/ && ($sym = $pkg . $_, defined *$sym{$type{$pre}}), keys %$pkg); } # Do we need a user customizable variable to ignore @packages? return grep(/^\Q$text/, map($prefix . $_, @packages, @symbols)); } } # # Completes key names for Perl hashes # sub cmpl_hashkeys { my ($text, $line, $start) = @_; my $tmp= substr($line, 0, $start + 1); my ($var,$arrow) = ($tmp =~ m/^[\$\%]([\w:]+)\s*(->)?\s*\{\s*['"]?/); return () unless $var; no strict 'refs'; $var = "$Psh::PerlEval::current_package::$var" unless ($var =~ m/::/); return () unless $var; if ($arrow) { my $hashref = eval "\$$var"; return grep(/^\Q$text/, keys %$hashref); } else { return grep(/^\Q$text/, keys %$var); } } sub _search_ISA { my ($mypkg) = @_; no strict qw(refs); my $isa = "${mypkg}::ISA"; return $mypkg, map _search_ISA($_), @$isa; } sub cmpl_method { my ($text, $line, $start) = @_; my ($var, $pkg, $sym, $pk); $var = (substr($line, 0, $start + 1) =~ m/\$([\w:]+)\s*->\s*$/)[0]; $pkg = ref eval (($var =~ m/::/) ? "\$$var" : "\$$Psh::PerlEval::current_package::$var"); no strict qw(refs); return grep(/^\Q$text/, map { $pk = $_ . '::'; grep (/^\w+$/ && ($sym = "${pk}$_", defined *$sym{CODE}), keys %$pk); } _search_ISA($pkg)); } { # complete perl bare words (Perl function, subroutines, filehandle) sub cmpl_perl_function { my ($text) = @_; my ($prefix, $pkg, $sym); no strict qw(refs); ($prefix, $pkg) = ($text =~ m/^((.*::)?)/); my @packages = grep /::$/, $pkg ? keys %$pkg : keys %::; $pkg = ($Psh::PerlEval::current_package eq 'main' ? '::' : $Psh::PerlEval::current_package . '::') unless $pkg; my @subs = grep (/^\w+$/ && ($sym = $pkg . $_, defined *$sym{CODE} || defined *$sym{FILEHANDLE}), keys %$pkg); # Do we need a user customizable variable to ignore @packages? my @result= grep(/^\Q$text/, !$prefix && @Psh::Completion::keyword, map($prefix . $_, @packages, @subs)); if (@result==1) { $Psh::Completion::ac=''; } return @result; } BEGIN { # from perl5.004_02 perlfunc @Psh::Completion::keyword = qw( chomp chop chr crypt hex index lc lcfirst length oct ord pack q qq reverse rindex sprintf substr tr uc ucfirst y m pos quotemeta s split study qr abs atan2 cos exp hex int log oct rand sin sqrt srand pop push shift splice unshift grep join map qw reverse sort unpack delete each exists keys values binmode close closedir dbmclose dbmopen die eof fileno flock format getc print printf read readdir rewinddir seek seekdir select syscall sysread sysseek syswrite tell telldir truncate warn write pack read syscall sysread syswrite unpack vec chdir chmod chown chroot fcntl glob ioctl link lstat mkdir open opendir readlink rename rmdir stat symlink umask unlink utime caller continue die do dump eval exit goto last next redo return sub wantarray caller import local my package use defined dump eval formline local my reset scalar undef wantarray alarm exec fork getpgrp getppid getpriority kill pipe qx setpgrp setpriority sleep system times wait waitpid do import no package require use bless dbmclose dbmopen package ref tie tied untie use accept bind connect getpeername getsockname getsockopt listen recv send setsockopt shutdown socket socketpair msgctl msgget msgrcv msgsnd semctl semget semop shmctl shmget shmread shmwrite endgrent endhostent endnetent endpwent getgrent getgrgid getgrnam getlogin getpwent getpwnam getpwuid setgrent setpwent endprotoent endservent gethostbyaddr gethostbyname gethostent getnetbyaddr getnetbyname getnetent getprotobyname getprotobynumber getprotoent getservbyname getservbyport getservent sethostent setnetent setprotoent setservent gmtime localtime time times abs bless chomp chr exists formline glob import lc lcfirst map my no prototype qx qw readline readpipe ref sub sysopen tie tied uc ucfirst untie use dbmclose dbmopen ); } } # # completion(text,line,start,end) # # Main Completion function # sub completion { my ($text, $line, $start) = @_; my $attribs = $Psh::term->Attribs; my @tmp=(); my $cut= 0; my $starttext= substr($line, 0, $start); if ($starttext =~ /((?:\S|\\\s)+\\\s)$/) { $text= $1.$text; $cut= length($1); $start-= $cut; $starttext= substr($line, 0, $start); } my $startchar= substr($line, $start, 1); $starttext =~ /^\s*(\S+)\s+/; my $command= $1 || ''; my $pretext= ''; if( $starttext =~ /--\w+=(\S*)$/) { $pretext= $1; } elsif ( $starttext =~ /\s(\S*)$/) { $pretext= $1; } elsif( $starttext =~ /^(\S*)$/) { $pretext= $1; } # are we in backticks or after a pipe ? if( $starttext =~ /.*[\|\`]\s*(\S+)\s+/) { $command= $1; } my $firstflag= $starttext !~/\s/ || 0; $Psh::Completion::ac=' '; $command =~ m|^\s*(\S*/)?(\S*)|; if ($Psh::debugging and ($Psh::debugging eq '1' or $Psh::debugging =~ /c/)) { Psh::Util::print_debug_class('c',"\n"); Psh::Util::print_debug_class('c',"Completion: text=$text, line=$line, start=$start, starttext=$starttext, command=$command, first=$firstflag\n"); } my $dir=$1||''; my $base=$2||''; my $cmd; if ($Psh::Completion::modules{$cmd= $dir.$base} or $Psh::Completion::modules{$cmd= $base}) { start_module($cmd); } if ($Psh::PCompletion::LOADED) { # Check completion-spec is defined or not. my $cmd; my $cs = $Psh::PCompletion::COMPSPEC{$cmd = $dir . $base} || $Psh::PCompletion::COMPSPEC{$cmd = $base}; my $universal=0; unless (defined $cs) { $cs= $Psh::PCompletion::COMPSPEC{'*'}; $cmd= $dir.$base; $universal=1; } # Do programmable completion if completion-spec is defined. # This is done here to keep the compatibility with bash. if (defined $cs) { # remove prefix string if it is already prefixed. $text =~ s/^\Q$cs->{prefix}// if (defined $cs->{prefix}); @tmp = Psh::PCompletion::pcomp_list($cs, $text, $line, $start, $cmd); if (!@tmp) { if ($cs->{option} and $cs->{option} eq 'default') { } elsif ($universal) { # ignore both cases } else { return (); } } else { $attribs->{$APPEND}=$Psh::Completion::ac; return @tmp; } } } if ($starttext =~ m/\$([\w:]+)\s*(->)?\s*{\s*['"]?$/) { # $foo{key, $foo->{key @tmp= cmpl_hashkeys($text, $line, $start); $Psh::Completion::ac = '}'; } elsif ($starttext =~ m/\$([\w:]+)\s*->\s*['"]?$/) { # $foo->method @tmp= cmpl_method($text, $line, $start); $Psh::Completion::ac = ' '; } elsif ( $text =~ /^\$#|[\@\$%&]/) { # $foo, @foo, $#foo, %foo, &foo @tmp= cmpl_symbol($text, $line, $start); $Psh::Completion::ac = ''; } elsif( $firstflag || $starttext =~ /[\|\`]\s*$/) { # we have the first word in the line or a pipe sign/backtick in front # of the current item, so we try to complete executables if ($pretext=~m/\//) { @tmp = cmpl_filenames($pretext.$text,1) } else { @tmp= cmpl_executable($text); } unless ($pretext) { # Afterwards we add possible matches for perl barewords push @tmp, cmpl_perl_function($text); } } else { @tmp = cmpl_filenames($pretext.$text); } if (Psh::Strategy::active('built_in') and grep { $_ eq $command } Psh::Support::Builtins::get_builtin_commands() ) { my $pkg= ucfirst($command); eval "require Psh::Builtins::$pkg"; Psh::Util::print_debug_class('e',"Error: $@") if $@; my @tmp2= eval 'Psh::Builtins::'.$pkg.'::cmpl_'."$command('$text','$pretext','$starttext','$line')"; if( @tmp2 && $tmp2[0]) { shift(@tmp2); @tmp= @tmp2; } else { shift(@tmp2); push @tmp, @tmp2; } } $attribs->{$APPEND}=$Psh::Completion::ac; if ($cut) { @tmp= map { substr($_, $cut)} @tmp; } return @tmp; } sub display_match_list { my($matches, $num_matches, $max_length) = @_; my @matches= @$matches; # make a copy, otherwise there's memory managment trouble shift @matches; map { $_ =~ s/^((\$#|[\@\$%&])?).*::(.+)/$3/; }(@matches); my $col='01;34'; if ($ENV{LS_COLORS}) { my @tmp= split /:/, $ENV{LS_COLORS}; foreach (@tmp) { if (substr($_,0,3) eq 'di=') { $col= substr($_,3); last; } } } map { $_ =~ s/^([^\/]+)\/$/\001\e[${col}m\002$1\001\e[00m\002\//; } (@matches); print STDOUT "\n"; Psh::Util::print_list(\@matches,$max_length); # eval { # local $^W=0; # $Psh::term->display_match_list($matches); # }; eval { local $^W=0; $Psh::term->forced_update_display if defined $Psh::term; }; } 1; __END__ =head1 NAME Psh::Completion - containing the completion routines of psh. Currently works with Term::ReadLine::Gnu and Term::ReadLine::Perl. =head1 SYNOPSIS =head1 DESCRIPTION =head1 AUTHOR Markus Peter, warp@spin.de Hiroo Hayashi, hiroo.hayashi@computer.org =head1 SEE ALSO =cut