Move to Moo for fast bootstrapping.
[p5sagit/Devel-REPL.git] / lib / Devel / REPL / Plugin / Completion.pm
1 package Devel::REPL::Plugin::Completion;
2 use Devel::REPL::Plugin;
3 use Scalar::Util 'weaken';
4 use PPI;
5 use namespace::sweep;
6 use MooX::Types::MooseLike::Base qw(ArrayRef Int Bool);
7
8 has current_matches => (
9    is => 'rw',
10    isa => ArrayRef,
11    lazy => 1,
12    default => sub { [] },
13 );
14
15 has match_index => (
16    is => 'rw',
17    isa => Int,
18    lazy => 1,
19    default => sub { 0 },
20 );
21
22 has no_term_class_warning => (
23    isa => Bool,
24    is  => "rw",
25    default => sub { 0 },
26 );
27
28 has do_readline_filename_completion => (  # so default is no if Completion loaded
29    isa => Bool,
30    is  => "rw",
31    lazy => 1,
32    default => sub { 0 },
33 );
34
35 before 'read' => sub {
36    my ($self) = @_;
37
38    if ((!$self->term->isa("Term::ReadLine::Gnu") and !$self->term->isa("Term::ReadLine::Perl"))
39          and !$self->no_term_class_warning) {
40       warn "Term::ReadLine::Gnu or Term::ReadLine::Perl is required for the Completion plugin to work";
41       $self->no_term_class_warning(1);
42    }
43
44    my $weakself = $self;
45    weaken($weakself);
46
47    if ($self->term->isa("Term::ReadLine::Gnu")) {
48       $self->term->Attribs->{attempted_completion_function} = sub {
49          $weakself->_completion(@_);
50       };
51    }
52
53    if ($self->term->isa("Term::ReadLine::Perl")) {
54       $self->term->Attribs->{completion_function} = sub {
55          $weakself->_completion(@_);
56       };
57    }
58
59 };
60
61 sub _completion {
62    my $is_trp = scalar(@_) == 4 ? 1 : 0;
63    my ($self, $text, $line, $start, $end) = @_;
64    $end = $start+length($text) if $is_trp;
65
66    # we're discarding everything after the cursor for completion purposes
67    # we can't just use $text because we want all the code before the cursor to
68    # matter, not just the current word
69    substr($line, $end) = '';
70
71    my $document = PPI::Document->new(\$line);
72    return unless defined($document);
73
74    $document->prune('PPI::Token::Whitespace');
75
76    my @matches = $self->complete($text, $document);
77
78    # iterate through the completions
79    if ($is_trp) {
80       if (scalar(@matches)) {
81          return @matches;
82       } else {
83          return ($self->do_readline_filename_completion) ? readline::rl_filename_list($text) : () ;
84       }
85    } else {
86       $self->term->Attribs->{attempted_completion_over} = 1 unless $self->do_readline_filename_completion;
87       if (scalar(@matches)) {
88          return $self->term->completion_matches($text, sub {
89                my ($text, $state) = @_;
90
91                if (!$state) {
92                   $self->current_matches(\@matches);
93                   $self->match_index(0);
94                }
95                else {
96                   $self->match_index($self->match_index + 1);
97                }
98
99                return $self->current_matches->[$self->match_index];
100             });
101       } else {
102          return;
103       }
104    }
105 }
106
107 sub complete {
108    return ();
109 }
110
111 # recursively find the last element
112 sub last_ppi_element {
113    my ($self, $document, $type) = @_;
114    my $last = $document;
115    while ($last->can('last_element') && defined($last->last_element)) {
116       $last = $last->last_element;
117       return $last if $type && $last->isa($type);
118    }
119    return $last;
120 }
121
122 1;
123
124 __END__
125
126 =head1 NAME
127
128 Devel::REPL::Plugin::Completion - Extensible tab completion
129
130 =head1 NOTE
131
132 By default, the Completion plugin explicitly does I<not> use the Gnu readline
133 or Term::ReadLine::Perl fallback filename completion.
134
135 Set the attribute C<do_readline_filename_completion> to 1 to enable this feature.
136
137 =head1 AUTHOR
138
139 Shawn M Moore, C<< <sartak at gmail dot com> >>
140
141 =cut
142