Commit | Line | Data |
e4ac8502 |
1 | package Devel::REPL::Plugin::Completion; |
1989c3d2 |
2 | use Devel::REPL::Plugin; |
3 | use Scalar::Util 'weaken'; |
4 | use PPI; |
e4ac8502 |
5 | use namespace::clean -except => [ 'meta' ]; |
6 | |
1989c3d2 |
7 | has current_matches => ( |
314f2293 |
8 | is => 'rw', |
9 | isa => 'ArrayRef', |
10 | lazy => 1, |
11 | default => sub { [] }, |
1989c3d2 |
12 | ); |
ac71b56c |
13 | |
1989c3d2 |
14 | has match_index => ( |
314f2293 |
15 | is => 'rw', |
16 | isa => 'Int', |
17 | lazy => 1, |
18 | default => sub { 0 }, |
1989c3d2 |
19 | ); |
e4ac8502 |
20 | |
97d28d6b |
21 | has no_term_class_warning => ( |
22 | isa => "Bool", |
23 | is => "rw", |
24 | default => 0, |
25 | ); |
26 | |
1989c3d2 |
27 | sub BEFORE_PLUGIN { |
28 | my ($self) = @_; |
e4ac8502 |
29 | |
1989c3d2 |
30 | my $weakself = $self; |
31 | weaken($weakself); |
ac71b56c |
32 | |
1989c3d2 |
33 | $self->term->Attribs->{attempted_completion_function} = sub { |
34 | $weakself->_completion(@_); |
35 | }; |
e4ac8502 |
36 | } |
37 | |
97d28d6b |
38 | sub AFTER_PLUGIN { |
39 | my ($self) = @_; |
40 | |
41 | warn "Term::ReadLine::Gnu is required for the Completion plugin to work" |
42 | unless $self->term->isa("Term::ReadLine::Gnu") and !$self->no_term_class_warning; |
43 | } |
44 | |
1989c3d2 |
45 | sub _completion { |
46 | my ($self, $text, $line, $start, $end) = @_; |
47 | |
48 | # we're discarding everything after the cursor for completion purposes |
314f2293 |
49 | # we can't just use $text because we want all the code before the cursor to |
50 | # matter, not just the current word |
1989c3d2 |
51 | substr($line, $end) = ''; |
52 | |
53 | my $document = PPI::Document->new(\$line); |
54 | return unless defined($document); |
55 | |
314f2293 |
56 | $document->prune('PPI::Token::Whitespace'); |
57 | |
1989c3d2 |
58 | my @matches = $self->complete($text, $document); |
ac71b56c |
59 | |
1989c3d2 |
60 | # iterate through the completions |
61 | return $self->term->completion_matches($text, sub { |
62 | my ($text, $state) = @_; |
63 | |
64 | if (!$state) { |
65 | $self->current_matches(\@matches); |
66 | $self->match_index(0); |
67 | } |
68 | else { |
69 | $self->match_index($self->match_index + 1); |
ac71b56c |
70 | } |
71 | |
1989c3d2 |
72 | return $self->current_matches->[$self->match_index]; |
73 | }); |
74 | } |
75 | |
76 | sub complete { |
77 | return (); |
78 | } |
e4ac8502 |
79 | |
8051a5e0 |
80 | # recursively find the last element |
81 | sub last_ppi_element { |
82 | my ($self, $document, $type) = @_; |
83 | my $last = $document; |
84 | while ($last->can('last_element') && defined($last->last_element)) { |
85 | $last = $last->last_element; |
86 | return $last if $type && $last->isa($type); |
87 | } |
88 | return $last; |
89 | } |
90 | |
e4ac8502 |
91 | 1; |
1989c3d2 |
92 | |
cfd1094b |
93 | __END__ |
94 | |
95 | =head1 NAME |
96 | |
97 | Devel::REPL::Plugin::Completion - Extensible tab completion |
98 | |
30b459d4 |
99 | =head1 AUTHOR |
100 | |
101 | Shawn M Moore, C<< <sartak at gmail dot com> >> |
102 | |
cfd1094b |
103 | =cut |
104 | |