bf4e6c3637b00b0260ddae6f54ee8009f9ef9dc7
[p5sagit/Devel-REPL.git] / lib / Devel / REPL / Plugin / CompletionDriver / INC.pm
1 use strict;
2 use warnings;
3 package Devel::REPL::Plugin::CompletionDriver::INC;
4
5 use Devel::REPL::Plugin;
6 use Devel::REPL::Plugin::Completion;    # die early if cannot load
7 use File::Next;
8 use File::Spec;
9 use namespace::autoclean;
10
11 sub BEFORE_PLUGIN {
12     my $self = shift;
13     $self->load_plugin('Completion');
14 }
15
16 around complete => sub {
17   my $orig = shift;
18   my ($self, $text, $document) = @_;
19
20   my $last = $self->last_ppi_element($document, 'PPI::Statement::Include');
21
22   return $orig->(@_)
23     unless $last->isa('PPI::Statement::Include');
24
25   my @elements = $last->children;
26   shift @elements; # use or require
27
28   # too late for us to care, they're completing on something like
29   #     use List::Util qw(m
30   # OR they just have "use " and are tab completing. we'll spare them the flood
31   return $orig->(@_)
32     if @elements != 1;
33
34   my $package = shift @elements;
35   my $outsep  = '::';
36   my $insep   = "::";
37   my $keep_extension = 0;
38   my $prefix  = '';
39
40   # require "Foo/Bar.pm" -- not supported yet, ->string doesn't work for
41   # partially completed elements
42   #if ($package->isa('PPI::Token::Quote'))
43   #{
44   #  # we need to strip off the leading quote and stash it
45   #  $package = $package->string;
46   #  my $start = index($package->quote, $package);
47   #  $prefix = substr($package->quote, 0, $start);
48
49   #  # we're completing something like: require "Foo/Bar.pm"
50   #  $outsep = $insep = '/';
51   #  $keep_extension = 1;
52   #}
53   if ($package =~ /'/)
54   {
55     # the goofball is using the ancient ' package sep, we'll humor him
56     $outsep = "'";
57     $insep = "'|::";
58   }
59
60   my @directories = split $insep, $package;
61
62   # split drops trailing fields
63   push @directories, '' if $package =~ /(?:$insep)$/;
64   my $final = pop @directories;
65   my $final_re = qr/^\Q$final/;
66
67   my @found;
68
69   # most VCSes don't litter every single fucking directory with garbage. if you
70   # know of any other, just stick them in here. No one wants to complete
71   # Devel::REPL::Plugin::.svn
72   my %ignored =
73   (
74       '.'    => 1,
75       '..'   => 1,
76       '.svn' => 1,
77   );
78
79   # this will take a directory and add to @found all of the possible matches
80   my $add_recursively;
81   $add_recursively = sub {
82     my ($path, $iteration, @more) = @_;
83     opendir((my $dirhandle), $path) || return;
84     for (grep { !$ignored{$_} } readdir $dirhandle)
85     {
86       my $match = $_;
87
88       # if this is the first time around, we need respect whatever the user had
89       # at the very end when he pressed tab
90       next if $iteration == 0 && $match !~ $final_re;
91
92       my $fullmatch = File::Spec->rel2abs($match, $path);
93       if (-d $fullmatch)
94       {
95         $add_recursively->($fullmatch, $iteration + 1, @more, $match);
96       }
97       else
98       {
99         $match =~ s/\..*// unless $keep_extension;
100         push @found, join '', $prefix,
101                               join $outsep, @directories, @more, $match;
102       }
103     }
104   };
105
106   # look through all of
107   INC: for (@INC)
108   {
109     my $path = $_;
110
111     # match all of the fragments they have, so "use Moose::Meta::At<tab>"
112     # will only begin looking in ../Moose/Meta/
113     for my $subdir (@directories)
114     {
115       $path = File::Spec->catdir($path, $subdir);
116       -d $path or next INC;
117     }
118
119     $add_recursively->($path, 0);
120   }
121
122   return $orig->(@_), @found;
123 };
124
125 1;
126
127 __END__
128
129 =head1 NAME
130
131 Devel::REPL::Plugin::CompletionDriver::INC - Complete module names in use and require
132
133 =head1 AUTHOR
134
135 Shawn M Moore, C<< <sartak at gmail dot com> >>
136
137 =cut
138