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