branch : pmacs2
This commit is contained in:
moculus 2008-05-30 06:27:42 +00:00
parent d2e8d35ad6
commit 132d488c5d
4 changed files with 178 additions and 159 deletions

View File

@ -478,8 +478,8 @@ class IperlBuffer(InterpreterBuffer):
return ('iperl', '-p') return ('iperl', '-p')
def get_env(self): def get_env(self):
return {'PERL5LIB': self.application.config.get('perl.lib', '.')} return {'PERL5LIB': self.application.config.get('perl.lib', '.')}
def get_completions(self, x1, x2, line): def readline_completions(self, x1, x2, line):
self.pipe.stdin.write("COMPLETE:%d:%d:%s\n" % (x1, x2, line)) self.pipe.stdin.write("READLINE:%d:%d:%s\n" % (x1, x2, line))
self.pipe.stdin.flush() self.pipe.stdin.flush()
(typ_, value) = self.pipe_readline() (typ_, value) = self.pipe_readline()
assert typ_ == 'COMPLETIONS', '%r %r' % (typ_, value) assert typ_ == 'COMPLETIONS', '%r %r' % (typ_, value)

View File

@ -27,6 +27,7 @@ class IperlExec(method.Method):
b.pipe.stdin.write("ENTER:%s\n" % s) b.pipe.stdin.write("ENTER:%s\n" % s)
b.pipe.stdin.flush() b.pipe.stdin.flush()
output = b.pipe_read() output = b.pipe_read()
if output:
b.insert_string(b.get_buffer_end(), output, force=True) b.insert_string(b.get_buffer_end(), output, force=True)
class IperlTab(method.Method): class IperlTab(method.Method):
@ -47,12 +48,11 @@ class IperlTab(method.Method):
x1 -= 1 x1 -= 1
word = line[x1:x2] word = line[x1:x2]
candidates = b.get_completions(x1, x2, s) candidates = b.readline_completions(x1, x2, s)
if candidates: if candidates:
s = completer.find_common_string(candidates) s = completer.find_common_string(candidates)
w.buffer.delete(Point(x1, 0), Point(x2, 0), force=True)
w.insert_string_at_cursor(s) w.insert_string_at_cursor(s)
mode.mini.use_completion_window(a, s, candidates) mode.mini.use_completion_window(a, s, [word+s for s in candidates])
class IperlPathStart(method.Method): class IperlPathStart(method.Method):
'''Interactively run perl statements in the context of a buffer''' '''Interactively run perl statements in the context of a buffer'''

View File

@ -496,7 +496,7 @@ class PerlSemanticComplete(method.introspect.TokenComplete):
line = w.buffer.lines[t.y] line = w.buffer.lines[t.y]
(x1, x2) = (t.x, t.end_x()) (x1, x2) = (t.x, t.end_x())
candidates = b.get_completions(x1, x2, line) candidates = [t.string + s for s in b.readline_completions(x1, x2, line)]
minlen = None minlen = None
for candidate in candidates: for candidate in candidates:

View File

@ -14,6 +14,7 @@ use Scalar::Util qw();
# NOTE: some Term::ReadLine implementations don't support features that we sort # NOTE: some Term::ReadLine implementations don't support features that we sort
# of need. Term::ReadLine::Gnu is recommended. # of need. Term::ReadLine::Gnu is recommended.
use Term::ReadLine qw(); use Term::ReadLine qw();
use Term::ReadLine::Gnu qw();
# globals # globals
Getopt::Long::Configure('bundling'); Getopt::Long::Configure('bundling');
@ -21,6 +22,7 @@ $| = 1;
$Data::Dumper::Terse = 1; $Data::Dumper::Terse = 1;
$Data::Dumper::Indent = 1; $Data::Dumper::Indent = 1;
my $PIPE; my $PIPE;
my $PKG = __PACKAGE__;
# usage function # usage function
sub usage { sub usage {
@ -40,88 +42,6 @@ EOT
exit($status); exit($status);
} }
# handy function for inspecting the symbol table
my $pkgregex = qr/^(?:[a-zA-Z_][a-zA-Z0-9_]*::)+$/;
my $nameregex = qr/^[a-zA-Z_][a-zA-Z0-9_]*$/;
# get all the objects of a particular sigil-type rooted in a package
sub sigilread {
my ($pkg, $sigil, $base, $proc) = @_;
my $base = $pkg eq 'main::' ? '' : $pkg;
$proc = {$pkg => 1} unless $proc;
my @results;
foreach my $item (eval("keys(\%${pkg})")) {
my $item2 = $base . $item;
next if $proc->{$item2};
if($item =~ m/$nameregex/) {
if($sigil eq '$') {
if(eval("defined(\$::${item2})")) {
push(@results, $item2);
} elsif(eval("defined(\@::${item2})")) {
push(@results, $item2 . "[");
} elsif(eval("defined(\%::${item2})")) {
push(@results, $item2 . "{");
}
} elsif(eval("defined(${sigil}::${item2})")) {
push(@results, $item2);
}
} elsif($item =~ m/$pkgregex/) {
$proc->{$item2} = 1;
push(@results, sigilread($item2, $sigil, $item2, $proc));
my $pkgname = substr($item2, 0, -2);
}
}
return @results;
}
# find single-step completions for a particular package/name
sub sigilfind {
my ($pkg, $name, $proc) = @_;
my $base = $pkg eq 'main::' ? '' : $pkg;
$proc = {$pkg => 1} unless $proc;
my @results;
foreach my $item (eval("keys(\%${pkg})")) {
my $item2 = $base . $item;
next if $proc->{$item2};
if($item2 =~ m/^$name/) {
if($item =~ m/$nameregex/ && eval("defined(&::${item2})")) {
push(@results, $item2);
} elsif($item =~ m/$pkgregex/) {
$proc->{$item2} = 1;
my $pkgname = substr($item2, 0, -2);
if($name eq $pkgname) {
push(@results, "$pkgname->");
push(@results, sigilfind($item2, $name, $proc));
} else {
push(@results, $pkgname);
}
}
}
}
return @results;
}
# find all functions accessible via a package's @ISA array. this could probably
# be implemented in a more efficient manner.
sub isafind {
my ($pkg, $proc, $names) = @_;
return if $proc && $proc->{$pkg};
$proc = {$pkg => 1} unless $proc;
$names = {} unless $names;
my @pkgs = eval("\@${pkg}::ISA");
foreach my $pkg2 (@pkgs) {
isafind($pkg2, $proc, $names);
}
foreach my $item (eval("keys(\%${pkg}::)")) {
if($item =~ m/$nameregex/) {
$names->{$item} = 1 if eval("defined(&::${pkg}::${item})");
}
}
return keys(%$names);
}
# handy function for printing nice representations of data # handy function for printing nice representations of data
sub repr { sub repr {
my ($item) = @_; my ($item) = @_;
@ -148,75 +68,170 @@ sub resolve_ref {
return ($obj, $reftype, $package); return ($obj, $reftype, $package);
} }
# use sigilfind to get completions for particular word(s) # the completer used by readline
sub complete { sub readline_complete {
my ($word, $line, $x) = @_; my ($word, $line, $x) = @_;
#return complete($word, $line, $x);
my $full_word = readline_full_word($word, $line, $x);
my $delta = $x - length($word) + length($full_word);
my @candidates = complete($full_word);
return map { substr($_, $delta) } @candidates;
#print STDERR "'$word' '$line' $x\n";
$word =~ m/^([&\$%@\*]?)(.*)$/;
my $sigil = $1;
my $name = $2;
my $pre = substr($line, 0, $x);
if(length($line) == $x && $pre =~ m/^[ \t]*$/) {
# hack to handle the case where we really do want a tab
return ("\t");
} }
if($pre =~ m/(\$(?:[a-zA-Z_][a-zA-Z0-9_]*::)*[a-zA-Z_][a-zA-Z0-9_]*)->$/) { # some handy regexs
# ref dereference my $name_re = qr/[a-zA-Z_][a-zA-Z0-9_]*/;
my($obj, $reftype, $package) = resolve_ref($1); my $name2_re = qr/(?:[a-zA-Z_][a-zA-Z0-9_]*)?/;
my $sub_pkg_re = qr/(?:${name_re}::)*/;
my $pkg_re = qr/(?:${name_re}::)*${name_re}/;
my $hash_re = qr/\$(${pkg_re}){/;
my $hash_ref_re = qr/\$(${pkg_re})->{/;
my $obj_method_re = qr/(\$${pkg_re})->/;
my $cls_method_re = qr/(${pkg_re})->/;
my $default_re1 = qr/([\@\$\%\&\*]?)()()(${name_re})/;
my $default_re2 = qr/([\@\$\%\&\*]?)(${pkg_re})(::)(${name_re})/;
my $default_re3 = qr/([\@\$\%\&\*]?)(${pkg_re})(:{0,2})()/;
# XYZ
sub isa_complete {
my ($var_name, $pkg, $method_name) = @_;
my $names = {};
_isa_complete($pkg, $method_name, {}, $names);
return map { "${var_name}->$_" } keys(%$names);
}
sub _isa_complete {
my ($pkg, $name, $proc, $names) = @_;
return if $proc->{$pkg};
$proc->{$pkg} = 1;
map { _isa_complete($_, $name, $proc, $names) } (eval("\@${pkg}::ISA"));
foreach my $item (eval("keys(\%${pkg}::)")) {
next unless $item =~ m/^${name_re}$/;
if(!$names->{$item} && $item =~ m/^$name/) {
$names->{$item} = 1 if eval("defined(&::${pkg}::${item})");
}
}
}
sub sigil_complete {
my ($type, $sigil, $pkg, $sep, $name) = @_;
my $base = $pkg . $sep . $name;
my @names;
my @pkgs = _pkg_complete($pkg, $sep, $name, 1);
foreach my $pkg2 (@pkgs) {
#print " XYZ '$type' '$name' '$pkg2'\n";
foreach my $item (eval("\%${pkg2}::")) {
next unless $item =~ m/^${name_re}$/;
my $base2 = $pkg2 . ($pkg2 ? '::' : '') . $item;
next unless $base2 =~ m/^$base/;
#print " OOP defined(${type}::${base2})\n";
if(eval("defined(${type}::${base2})\n")) {
#print " YES ${sigil}${base2}\n";
push(@names, $sigil . $base2);
}
}
}
return @names;
}
sub pkg_complete {
my ($pkg, $sep, $name) = @_;
my @pkgs = _pkg_complete($pkg, $sep, $name, !($pkg && $sep));
my $base = $pkg . $sep . $name;
return grep {$_ =~ m/^$base/ } @pkgs;
}
sub _pkg_complete {
my ($pkg, $sep, $name, $returnself) = @_;
my @pkgs = $returnself ? ($pkg) : ();
foreach my $item (eval("\%${pkg}::")) {
next unless $item =~ m/^(${name_re})::$/;
my $item2 = $1;
next if $pkg =~ m/::$item2$/;
if($item =~ m/^$name/) {
my $pkg2 = ($pkg ? "${pkg}::" : "") . $item2;
push(@pkgs, _pkg_complete($pkg2, undef, "", 1));
}
}
return @pkgs;
}
sub complete {
my ($word) = @_;
my @words;
if($word =~ m/^${obj_method_re}(${name2_re})$/) {
# obj dereference
my ($obj_name, $method_name) = ($1, $2);
my($obj, $reftype, $package) = resolve_ref($obj_name);
if($reftype eq $package) { if($reftype eq $package) {
unless($method_name) {
if($reftype eq 'CODE') { if($reftype eq 'CODE') {
return ("$word\("); @words = ("$word\(");
} elsif($reftype eq 'HASH') { } elsif($reftype eq 'HASH') {
return ("$word\{"); @words = ("$word\{");
} elsif($reftype eq 'ARRAY') { } elsif($reftype eq 'ARRAY') {
return ("$word\["); @words = ("$word\[");
} else { }
return ();
} }
} else { } else {
my @names = isafind($package); @words = isa_complete($obj_name, $package, $method_name);
return grep { $_ =~ m/^$word/ } @names;
} }
} elsif($pre =~ m/((?:[a-zA-Z_][a-zA-Z0-9_]*::)*[a-zA-Z_][a-zA-Z0-9_]*)->$/) { } elsif($word =~ m/^${cls_method_re}(${name2_re})$/) {
# package dereference # package dereference
my @names = isafind($1); my ($cls_name, $method_name) = ($1, $2);
return grep { $_ =~ m/^$word/ } @names; @words = isa_complete($cls_name, $cls_name, $method_name);
} elsif($pre =~ m/\$((?:[a-zA-Z_][a-zA-Z0-9_]*::)*[a-zA-Z_][a-zA-Z0-9_]*){$/) { } elsif($word =~ m/^${hash_re}(.*)$/) {
# hash keys # literal hash
my $obj = eval("\\\%$1"); my ($hash, $key) = ($1, $2);
return $obj ? grep { $_ =~ m/^$word/ } keys(%$obj) : (); my $obj = eval("\\\%$hash");
} elsif($pre =~ m/(\$(?:[a-zA-Z_][a-zA-Z0-9_]*::)*[a-zA-Z_][a-zA-Z0-9_]*)->{$/) { if($obj) {
# hashref keys @words = map { "\$${hash}{$_" } grep { $_ =~ m/^$key/ } keys(%$obj);
my $obj = eval("$1");
return $obj ? grep { $_ =~ m/^$word/ } keys(%$obj) : ();
} else {
# literals and ambiguous cases
$name =~ m/^((?:[a-zA-Z_][a-zA-Z0-9_]*::)*)/;
my $pkgname = $1 ? $1 : 'main::';
my @candidates;
if($sigil) {
# if we have a sigil, then we can just jump to the actual object
# completions.
@candidates = map {
"${sigil}${_}"
} grep {
$_ =~ m/^$name/
} sigilread($pkgname, $sigil);
} else {
# if we don't have a sigil, the user might wanna call a function,
# or complete a package to make an object-oriented call. so complete
# step-by-step to avoid annoying the user.
@candidates = map {
"${sigil}${_}"
} grep {
$_ =~ m/^$name/
} sigilfind($pkgname, $name);
} }
return sort(@candidates); } elsif($word =~ m/^${hash_ref_re}(.*)$/) {
# hashref
my ($hash, $key) = ($1, $2);
my $obj = eval("\$$hash");
if($obj) {
@words = map { "\$${hash}->{$_" } grep { $_ =~ m/^$key/ } keys(%$obj);
}
} elsif($word =~ m/^${default_re1}$/ || $word =~ m/^${default_re2}$/ || $word =~ m/^${default_re3}$/) {
# package or simple var
my($sigil, $pkg, $sep, $name) = ($1, $2, $3, $4);
my($oldpkg, $oldsep) = ($pkg, $sep);
if($PKG && $pkg eq '' && $sep eq '') {
($pkg, $sep) = ($PKG, '::');
}
if($sigil eq '$') {
push(@words, sigil_complete('$', '$', $pkg, $sep, $name));
push(@words, map {"$_\["} sigil_complete('@', '$', $pkg, $sep, $name));
push(@words, map {"$_\{"} sigil_complete('%', '$', $pkg, $sep, $name));
} elsif($sigil) {
@words = sigil_complete($sigil, $sigil, $pkg, $sep, $name);
} else {
push(@words, pkg_complete($pkg, $sep, $name));
push(@words, sigil_complete('&', '', $pkg, $sep, $name));
}
if($oldpkg ne $pkg) {
@words = map { $_ =~ s/^${pkg}${sep}//; $_ } @words;
}
}
return sort(@words);
}
# use sigilfind to get completions for particular word(s)
sub readline_full_word {
my ($word, $line, $x) = @_;
my $pre = substr($line, 0, $x);
if(length($line) == $x && $pre =~ m/^[ \t]*$/) {
return undef;
} elsif($pre =~ m/$obj_method_re$/) {
return $1 . "->" . $word;
} elsif($pre =~ m/$cls_method_re$/) {
return $1 . "->" . $word;
} elsif($pre =~ m/$hash_re$/) {
return '$' . $1 . "{" . $word;
} elsif($pre =~ m/$hash_ref_re$/) {
return '$' . $1 . "->{" . $word;
} else {
return $word;
} }
} }
@ -264,7 +279,6 @@ sub run {
) || usage(1); ) || usage(1);
# let's display a nice banner to the user # let's display a nice banner to the user
#my ($prompt, $input, $term) = (">>>", "", undef);
my ($prompt, $input, $term) = ($prompt1, "", undef); my ($prompt, $input, $term) = ($prompt1, "", undef);
unless($pipe) { unless($pipe) {
print "Welcome to Erik's Perl Interpreter\n"; print "Welcome to Erik's Perl Interpreter\n";
@ -275,12 +289,12 @@ sub run {
# die if any of them fail to load. # die if any of them fail to load.
foreach my $pair (@preload) { foreach my $pair (@preload) {
if($pair->[0] eq 'eval') { if($pair->[0] eq 'eval') {
eval $pair->[1]; $PKG = eval "$pair->[1]; return __PACKAGE__";
die "failed to eval '$pair->[1]': $@" if $@; die "failed to eval '$pair->[1]': $@" if $@;
} elsif($pair->[0] eq 'run') { } elsif($pair->[0] eq 'run') {
die "no path named $pair->[1] found" unless -e $pair->[1]; die "no path named $pair->[1] found" unless -e $pair->[1];
my $data = slurp($pair->[1]); my $data = slurp($pair->[1]);
eval $data; $PKG = eval "$data; return __PACKAGE__";
die "failed to run $pair->[1]: $@" if $@; die "failed to run $pair->[1]: $@" if $@;
} elsif($pair->[0] eq 'use') { } elsif($pair->[0] eq 'use') {
eval "use $pair->[1]"; eval "use $pair->[1]";
@ -293,7 +307,7 @@ sub run {
$term = Term::ReadLine->new('IPERL'); $term = Term::ReadLine->new('IPERL');
my $attribs = $term->Attribs(); my $attribs = $term->Attribs();
$attribs->{special_prefixes} = '$@%*&'; $attribs->{special_prefixes} = '$@%*&';
$attribs->{completion_function} = \&complete; $attribs->{completion_function} = \&readline_complete;
$attribs->{completion_append_character} = ''; $attribs->{completion_append_character} = '';
} }
@ -314,11 +328,16 @@ sub run {
if($pipe) { if($pipe) {
if($line =~ m/^ENTER:(.*)$/) { if($line =~ m/^ENTER:(.*)$/) {
$line = $1; $line = $1;
} elsif($line =~ m/^COMPLETE:(\d+):(\d+):(.*)$/) { } elsif($line =~ m/^COMPLETE:(.*)$/) {
my @candidates = complete($1);
draw_completions(@candidates);
next;
} elsif($line =~ m/^READLINE:(\d+):(\d+):(.*)$/) {
my $x = $2; my $x = $2;
my $line = $3; my $line = $3;
my $word = substr($line, $1, $x - $1); my $word = substr($line, $1, $x - $1);
draw_completions(complete($line, $word, $x)); my @candidates = readline_complete($line, $word, $x);
draw_completions(@candidates);
next; next;
} else { } else {
print "malformed pipe input line: $line\n"; print "malformed pipe input line: $line\n";
@ -344,17 +363,17 @@ sub run {
($input, $prompt) = ("", $prompt1); ($input, $prompt) = ("", $prompt1);
system($1) unless $pipe; system($1) unless $pipe;
} elsif($line eq ':code') { } elsif($line eq ':code') {
draw_completions(sort(sigilread('main::', '&'))); draw_completions(sort(sigil_complete('&', '&', $PKG, '', '')));
} elsif($line eq ':scalar') { } elsif($line eq ':scalar') {
draw_completions(sort(sigilread('main::', '$'))); draw_completions(sort(sigil_complete('$', '$', $PKG, '', '')));
} elsif($line eq ':hash') { } elsif($line eq ':hash') {
draw_completions(sort(sigilread('main::', '%'))); draw_completions(sort(sigil_complete('%', '%', $PKG, '', '')));
} elsif($line eq ':array') { } elsif($line eq ':array') {
draw_completions(sort(sigilread('main::', '@'))); draw_completions(sort(sigil_complete('@', '@', $PKG, '', '')));
} elsif($line eq ':glob') { } elsif($line eq ':glob') {
draw_completions(sort(sigilread('main::', '*'))); draw_completions(sort(sigil_complete('*', '*', $PKG, '', '')));
} elsif($line =~ m/^:isa (.+)$/) { } elsif($line =~ m/^\:isa (${pkg_re})$/) {
draw_completions(sort(isafind($1))); draw_completions(sort(isa_complete($1, $1, '')));
} elsif($line =~ m/^[\t ]/ || $line =~ m/\{ *$/ || m/\\ *$/) { } elsif($line =~ m/^[\t ]/ || $line =~ m/\{ *$/ || m/\\ *$/) {
# we're dealing with an incomplete statement, so defer execution # we're dealing with an incomplete statement, so defer execution
$line =~ s/\\ *$//; $line =~ s/\\ *$//;