File Coverage

blib/lib/PLS/Parser/Document.pm
Criterion Covered Total %
statement 238 759 31.3
branch 79 426 18.5
condition 138 352 39.2
subroutine 46 91 50.5
pod 29 32 90.6
total 530 1660 31.9


line stmt bran cond sub pod time code
1              
2             use strict;
3 11     11   161894 use warnings;
  11         36  
  11         262  
4 11     11   45  
  11         21  
  11         338  
5             use feature 'state';
6 11     11   67  
  11         22  
  11         1165  
7             use Digest::SHA;
8 11     11   5227 use Encode;
  11         25168  
  11         502  
9 11     11   961 use ExtUtils::Installed;
  11         17009  
  11         781  
10 11     11   4748 use List::Util qw(first any);
  11         975770  
  11         427  
11 11     11   87 use Module::CoreList;
  11         12  
  11         719  
12 11     11   26785 use PPI;
  11         1004495  
  11         130  
13 11     11   14208 use PPI::Find;
  11         1004473  
  11         404  
14 11     11   4096 use PPR;
  11         8985  
  11         300  
15 11     11   1615 use Perl::Tidy;
  11         66260  
  11         238  
16 11     11   10304 use Scalar::Util qw(blessed);
  11         2709901  
  11         1008  
17 11     11   82 use Time::Seconds;
  11         30  
  11         396  
18 11     11   859 use URI;
  11         2155  
  11         2071  
19 11     11   60 use URI::file;
  11         22  
  11         207  
20 11     11   797  
  11         9063  
  11         244  
21             use PLS::Parser::Element;
22 11     11   5205 use PLS::Parser::Element::Constant;
  11         382  
  11         337  
23 11     11   3967 use PLS::Parser::Element::Package;
  11         22  
  11         278  
24 11     11   3935 use PLS::Parser::Element::Subroutine;
  11         25  
  11         274  
25 11     11   4113 use PLS::Parser::Element::VariableStatement;
  11         31  
  11         280  
26 11     11   4389 use PLS::Parser::Index;
  11         23  
  11         478  
27 11     11   839 use PLS::Parser::Pod::ClassMethod;
  11         24  
  11         245  
28 11     11   3880 use PLS::Parser::Pod::Method;
  11         31  
  11         293  
29 11     11   3668 use PLS::Parser::Pod::Package;
  11         73  
  11         270  
30 11     11   687 use PLS::Parser::Pod::Subroutine;
  11         22  
  11         193  
31 11     11   38 use PLS::Parser::Pod::Variable;
  11         20  
  11         148  
32 11     11   3959  
  11         46  
  11         105906  
33             my %FILES;
34             my %VERSIONS;
35              
36             =head1 NAME
37              
38             PLS::Parser::Document
39              
40             =head1 DESCRIPTION
41              
42             This is a class that represents a text document. It has methods
43             for parsing and manipulating the document using L<PPI> and L<PPR>.
44              
45             =head1 METHODS
46              
47             =head2 new
48              
49             This creates a new L<PLS::Parser::Document> object.
50             It takes named parameters.
51              
52             Either C<uri> or C<path> must be passed.
53              
54             C<line> with a line number may be passed, which indicates that only one line
55             of the document should be parsed. This greatly enhances performance for completion items.
56              
57             =cut
58              
59             {
60             my ($class, %args) = @_;
61              
62 33     33 1 134318 my ($path, $uri);
63              
64 33         60 if (length $args{uri})
65             {
66 33 100       82 $path = URI->new($args{uri})->file;
    100          
67             $args{path} = $path;
68 30         80 $uri = $args{uri};
69 30         4656 } ## end if (length $args{uri})
70 30         50 elsif (length $args{path})
71             {
72             $path = $args{path};
73             $uri = URI::file->new($path)->as_string;
74 2         3 $args{uri} = $uri;
75 2         8 } ## end elsif (length $args{path}...)
76 2         193 return unless (length $path and length $uri);
77              
78 33 100 66     137 my $self = bless {
79             path => $path,
80 32         91 uri => $uri
81             }, $class;
82              
83             $self->{index} = PLS::Parser::Index->new();
84             my $document = $self->_get_ppi_document(%args);
85 32         111 return unless (ref $document eq 'PPI::Document');
86 32         101 $self->{document} = $document;
87 32 100       90  
88 30         57 return $self;
89             } ## end sub new
90 30         99  
91             =head2 go_to_definition
92              
93             This finds the definition of a symbol located at a given line and column number.
94              
95             =cut
96              
97             {
98             my ($self, $line_number, $column_number) = @_;
99              
100             my @matches = $self->find_elements_at_location($line_number, $column_number);
101 0     0 1 0  
102             return $self->search_elements_for_definition($line_number, $column_number, @matches);
103 0         0 } ## end sub go_to_definition
104              
105 0         0 =head2 find_current_list
106              
107             This finds the nearest list structure that surrounds the current column on the current line.
108             This is useful for finding which parameter the cursor is on when calling a function.
109              
110             =cut
111              
112             {
113             my ($self, $line_number, $column_number) = @_;
114              
115             my @elements = $self->find_elements_at_location($line_number, $column_number);
116             my $find = PPI::Find->new(sub { $_[0]->isa('PPI::Structure::List') });
117 0     0 1 0  
118             # Find the nearest list structure that completely surrounds the column.
119 0         0 return first { $_->lsp_column_number < $column_number and $column_number < $_->lsp_column_number + length($_->content) }
120 0     0   0 sort { abs($column_number - $a->lsp_column_number) - abs($column_number - $b->lsp_column_number) }
  0         0  
121             map { PLS::Parser::Element->new(element => $_, document => $self->{document}, file => $self->{path}) }
122             map { $find->in($_->element) } @elements;
123 0 0   0   0 } ## end sub find_current_list
124 0         0  
125 0         0 =head2 go_to_definition_of_closest_subroutine
126 0         0  
  0         0  
127             Given a list of elements, this finds the closest subroutine call to the current line and column.
128              
129             =cut
130              
131             {
132             my ($self, $list, $line_number, $column_number) = @_;
133              
134             return if (not blessed($list) or not $list->isa('PLS::Parser::Element') and $list->type eq 'PPI::Structure::List');
135              
136             # Try to find the closest word before the list - this is the function name.
137 0     0 1 0 my $word = $list;
138              
139 0 0 0     0 while (blessed($word) and $word->isa('PLS::Parser::Element') and not $word->element->isa('PPI::Token::Word'))
      0        
140             {
141             $word = $word->previous_sibling;
142 0         0 }
143              
144 0   0     0 return if (not blessed($word) or not $word->isa('PLS::Parser::Element') or not $word->element->isa('PPI::Token::Word'));
      0        
145             my $definitions = $self->search_elements_for_definition($line_number, $column_number, $word);
146 0         0 return $definitions, $word if wantarray;
147             return $definitions;
148             } ## end sub go_to_definition_of_closest_subroutine
149 0 0 0     0  
      0        
150 0         0 =head2 search_elements_for_definition
151 0 0       0  
152 0         0 This tries to find the definition in a list of elements, and returns the first definition found.
153              
154             =cut
155              
156             {
157             my ($self, $line_number, $column_number, @matches) = @_;
158              
159             my $this_files_package;
160             my @this_files_subroutines;
161              
162             if (ref $self->{index} ne 'PLS::Parser::Index')
163 0     0 1 0 {
164             ($this_files_package) = @{$self->get_packages()};
165 0         0 @this_files_subroutines = (@{$self->get_subroutines()}, @{$self->get_constants()});
166             }
167              
168 0 0       0 foreach my $match (@matches)
169             {
170 0         0 if (my ($package, $subroutine) = $match->subroutine_package_and_name())
  0         0  
171 0         0 {
  0         0  
  0         0  
172             if ($match->cursor_on_package($column_number))
173             {
174 0         0 if (ref $self->{index} eq 'PLS::Parser::Index')
175             {
176 0 0       0 return $self->{index}->find_package($package);
177             }
178 0 0       0 else
179             {
180 0 0       0 return [{uri => $self->{uri}, range => $this_files_package->range}] if (ref $this_files_package eq 'PLS::Parser::Element::Package' and $this_files_package->name eq $package);
181              
182 0         0 my $external = $self->find_external_package($package);
183             return [$external] if (ref $external eq 'HASH');
184             } ## end else [ if (ref $self->{index}...)]
185             } ## end if ($match->cursor_on_package...)
186 0 0 0     0  
187             if (length $package)
188 0         0 {
189 0 0       0 if (ref $self->{index} eq 'PLS::Parser::Index')
190             {
191             my $results = $self->{index}->find_package_subroutine($package, $subroutine);
192             return $results if (ref $results eq 'ARRAY' and scalar @{$results});
193 0 0       0 }
194             else
195 0 0       0 {
196             if (ref $this_files_package eq 'PLS::Parser::Element::Package' and $this_files_package->name eq $package)
197 0         0 {
198 0 0 0     0 my $found = first { $_->name eq $subroutine } @this_files_subroutines;
  0         0  
199             return {uri => $self->{uri}, range => $found->range} if (blessed($found) and $found->isa('PLS::Parser::Element'));
200             }
201             } ## end else [ if (ref $self->{index}...)]
202 0 0 0     0  
203             my $external = $self->find_external_subroutine($package, $subroutine);
204 0     0   0 return [$external] if (ref $external eq 'HASH');
  0         0  
205 0 0 0     0 } ## end if (length $package)
206              
207             if (ref $self->{index} eq 'PLS::Parser::Index')
208             {
209 0         0 my $results = $self->{index}->find_subroutine($subroutine);
210 0 0       0 return $results if (ref $results eq 'ARRAY' and scalar @{$results});
211              
212             @this_files_subroutines = (@{$self->get_subroutines()}, @{$self->get_constants()});
213 0 0       0 } ## end if (ref $self->{index}...)
214              
215 0         0 my $found = first { $_->name eq $subroutine } @this_files_subroutines;
216 0 0 0     0 return {uri => $self->{uri}, range => $found->range} if (blessed($found) and $found->isa('PLS::Parser::Element'));
  0         0  
217             } ## end if (my ($package, $subroutine...))
218 0         0 if (my ($class, $method) = $match->class_method_package_and_name())
  0         0  
  0         0  
219             {
220             if (ref $self->{index} eq 'PLS::Parser::Index')
221 0     0   0 {
  0         0  
222 0 0 0     0 my $results = $self->{index}->find_package_subroutine($class, $method);
223              
224 0 0       0 # fall back to treating as a method instead of class method
225             return $results if (ref $results eq 'ARRAY' and scalar @$results);
226 0 0       0 } ## end if (ref $self->{index}...)
227             else
228 0         0 {
229             my $found = first { $_->name eq $method } @this_files_subroutines;
230             return {uri => $self->{uri}, range => $found->range} if (blessed($found) and $found->isa('PLS::Parser::Element'));
231 0 0 0     0 }
232              
233             my $external = $self->find_external_subroutine($class, $method);
234             return [$external] if (ref $external eq 'HASH');
235 0     0   0 } ## end if (my ($class, $method...))
  0         0  
236 0 0 0     0 if (my $method = $match->method_name())
237             {
238             if (ref $self->{index} eq 'PLS::Parser::Index')
239 0         0 {
240 0 0       0 return $self->{index}->find_subroutine($method);
241             }
242 0 0       0 else
243             {
244 0 0       0 my $found = first { $_->name eq $method } @this_files_subroutines;
245             return {uri => $self->{uri}, range => $found->range} if (blessed($found) and $found->isa('PLS::Parser::Element'));
246 0         0 }
247             } ## end if (my $method = $match...)
248             if (my ($package, $import) = $match->package_name($column_number))
249             {
250 0     0   0 if (length $import)
  0         0  
251 0 0 0     0 {
252             if (ref $self->{index} eq 'PLS::Parser::Index')
253             {
254 0 0       0 return $self->{index}->find_package_subroutine($package, $import);
255             }
256 0 0       0 else
257             {
258 0 0       0 my $external = $self->find_external_subroutine($package, $import);
259             return [$external] if (ref $external eq 'HASH');
260 0         0 }
261             } ## end if (length $import)
262             else
263             {
264 0         0 if (ref $self->{index} eq 'PLS::Parser::Index')
265 0 0       0 {
266             return $self->{index}->find_package($package);
267             }
268             else
269             {
270 0 0       0 my $external = $self->find_external_package($package);
271             return [$external] if (ref $external eq 'HASH');
272 0         0 }
273             } ## end else [ if (length $import) ]
274             } ## end if (my ($package, $import...))
275             if (my $variable = $match->variable_name())
276 0         0 {
277 0 0       0 return $self->go_to_variable_definition($variable, $match, $line_number, $column_number);
278             }
279             } ## end foreach my $match (@matches...)
280              
281 0 0       0 # If all else fails, see if we're on a POD link.
282             if (my $link = $self->pod_link($line_number, $column_number))
283 0         0 {
284             my @pieces = split /::/, $link;
285             my $subroutine_name = pop @pieces;
286             my $package_name = join '::', @pieces;
287              
288 0 0       0 if (ref $self->{index} eq 'PLS::Parser::Index')
289             {
290 0         0 my $package = $self->{index}->find_package($link);
291 0         0 return $package if (ref $package eq 'ARRAY' and scalar @{$package});
292 0         0  
293             return $self->{index}->find_package_subroutine($package_name, $subroutine_name) if (length $package_name);
294 0 0       0 return $self->{index}->find_subroutine($subroutine_name);
295             } ## end if (ref $self->{index}...)
296 0         0 else
297 0 0 0     0 {
  0         0  
298             my $external = $self->find_external_package($link);
299 0 0       0 return [$external] if (ref $external eq 'HASH');
300 0         0  
301             $external = $self->find_external_subroutine($package_name, $subroutine_name);
302             return [$external] if (ref $external eq 'HASH');
303             } ## end else [ if (ref $self->{index}...)]
304 0         0  
305 0 0       0 } ## end if (my $link = $self->...)
306              
307 0         0 return;
308 0 0       0 } ## end sub search_elements_for_definition
309              
310             =head2 pod_link
311              
312             This determines if the line and column are within a POD LE<lt>E<gt> code,
313 0         0 and returns the contents of the link if so.
314              
315             =cut
316              
317             {
318             my ($self, $line_number, $column_number) = @_;
319              
320             $line_number++;
321              
322             my $find = PPI::Find->new(
323             sub {
324             my ($element) = @_;
325 0     0 1 0 return 0 unless $element->isa('PPI::Token::Pod');
326             return 0 if $element->line_number > $line_number;
327 0         0 return 0 if $element->line_number + scalar($element->lines) < $line_number;
328             return 1;
329             }
330             );
331 0     0   0  
332 0 0       0 return unless (scalar $find->in($self->{document}));
333 0 0       0  
334 0 0       0 open my $fh, '<', $self->get_full_text() or return;
335 0         0  
336             while (my $line = <$fh>)
337 0         0 {
338             next unless $. == $line_number;
339 0 0       0 chomp $line;
340              
341 0 0       0 while (
342             $line =~ m{
343 0         0 L< # starting L<
344             (?:
345 0 0       0 <+ # optional additional <
346 0         0 \s+ # spaces required if any additional <
347             )?
348 0         0 (.+?) # the actual link content
349             (?:
350             \s+ # spaces required if any additional >
351             +>+ # optional additional >
352             )?
353             > # final closing >
354             }gx
355             )
356             {
357             my $start = $-[1];
358             my $end = $+[1];
359             my $link = $1;
360              
361             next if ($start > $column_number or $column_number > $end);
362              
363             # Get just the name - remove the text and section parts
364 0         0 $link =~ s/^[^<]*\|//;
365 0         0 $link =~ s/\/[^>]*$//;
366 0         0 return $link;
367             } ## end while ($line =~ m{ ) (})
368 0 0 0     0  
369             last;
370             } ## end while (my $line = <$fh>)
371 0         0  
372 0         0 return;
373 0         0 } ## end sub pod_link
374              
375             =head2 find_pod
376 0         0  
377             This attempts to find POD for the symbol at the given location.
378              
379 0         0 =cut
380              
381             {
382             my ($self, $uri, $line_number, $column_number) = @_;
383              
384             my @elements = $self->find_elements_at_location($line_number, $column_number);
385              
386             foreach my $element (@elements)
387             {
388             my ($package, $subroutine, $variable, $import);
389              
390 0     0 1 0 if (($package, $import) = $element->package_name($column_number))
391             {
392 0         0 my %args = (index => $self->{index}, element => $element, package => $package);
393             my $class_name = 'PLS::Parser::Pod::Package';
394 0         0  
395             if (length $import)
396 0         0 {
397             if ($import =~ /^[\$\@\%]/)
398 0 0       0 {
399             $args{variable} = $import;
400 0         0 $class_name = 'PLS::Parser::Pod::Variable';
401 0         0 }
402             else
403 0 0       0 {
404             $args{subroutine} = $import;
405 0 0       0 $args{packages} = [$package];
406             delete $args{package};
407 0         0 $class_name = 'PLS::Parser::Pod::Subroutine';
408 0         0 } ## end else [ if ($import =~ /^[\$\@\%]/...)]
409             } ## end if (length $import)
410              
411             my $pod = $class_name->new(%args);
412 0         0 my $ok = $pod->find();
413 0         0 return (1, $pod) if $ok;
414 0         0 } ## end if (($package, $import...))
415 0         0 if (($package, $subroutine) = $element->class_method_package_and_name())
416             {
417             my $pod =
418             PLS::Parser::Pod::ClassMethod->new(
419 0         0 index => $self->{index},
420 0         0 element => $element,
421 0 0       0 packages => [$package],
422             subroutine => $subroutine
423 0 0       0 );
424             my $ok = $pod->find();
425             return (1, $pod) if $ok;
426             } ## end if (($package, $subroutine...))
427             if ($subroutine = $element->method_name())
428 0         0 {
429             my $pod =
430             PLS::Parser::Pod::Method->new(
431             index => $self->{index},
432 0         0 element => $element,
433 0 0       0 subroutine => $subroutine
434             );
435 0 0       0 my $ok = $pod->find();
436             return (1, $pod) if $ok;
437             } ## end if ($subroutine = $element...)
438             if (($package, $subroutine) = $element->subroutine_package_and_name())
439             {
440 0         0 my @packages = length $package ? ($package) : ();
441              
442             my $pod =
443 0         0 PLS::Parser::Pod::Subroutine->new(
444 0 0       0 uri => $uri,
445             index => $self->{index},
446 0 0       0 element => $element,
447             packages => \@packages,
448 0 0       0 subroutine => $subroutine,
449             include_builtins => 1
450             );
451             my $ok = $pod->find();
452             return (1, $pod) if $ok;
453             } ## end if (($package, $subroutine...))
454 0         0 if ($variable = $element->variable_name())
455             {
456             my $pod =
457             PLS::Parser::Pod::Variable->new(
458             index => $self->{index},
459 0         0 element => $element,
460 0 0       0 variable => $variable
461             );
462 0 0       0 my $ok = $pod->find();
463             return (1, $pod) if $ok;
464             } ## end if ($variable = $element...)
465             if ($element->type eq 'PPI::Token::Operator' and $element->content =~ /^-[rwxoRWXOezsfdlpSbctugkTBMAC]$/)
466             {
467 0         0 my $pod = PLS::Parser::Pod::Subroutine->new(
468             index => $self->{index},
469             element => $element,
470 0         0 subroutine => '-X',
471 0 0       0 include_builtins => 1
472             );
473 0 0 0     0 my $ok = $pod->find();
474             return (1, $pod) if $ok;
475             } ## end if ($element->type eq ...)
476             } ## end foreach my $element (@elements...)
477 0         0  
478             return 0;
479             } ## end sub find_pod
480              
481 0         0 {
482 0 0       0 my ($self, $line_number, $column_number) = @_;
483              
484             ($line_number, $column_number) = _ppi_location($line_number, $column_number);
485             $line_number = 1 if $self->{one_line};
486 0         0  
487             my $find = PPI::Find->new(
488             sub {
489             my ($element) = @_;
490              
491 27     27 0 41 return 0 unless $element->line_number == $line_number;
492             return 0 if $element->column_number > $column_number;
493 27         54 return 0 if $element->column_number + (length $element->content) < $column_number;
494 27 50       59 return 1;
495             }
496             );
497              
498 137     137   2376 my @matches = $find->in($self->{document});
499             @matches =
500 137 50       288 sort { (abs $column_number - $a->column_number) <=> (abs $column_number - $b->column_number) } @matches;
501 137 100       2262 @matches = map { PLS::Parser::Element->new(document => $self->{document}, element => $_, file => $self->{path}) } @matches;
502 133 100       1952 return @matches;
503 101         2601 } ## end sub find_elements_at_location
504              
505 27         223 =head2 find_external_subroutine
506              
507 27         252 This attempts to find the location of a subroutine inside an external module,
508             by name.
509 27         474  
  109         2469  
510 27         691 =cut
  101         225  
511 27         143  
512             {
513             my ($self, $package_name, $subroutine_name) = @_;
514              
515             my $include = PLS::Parser::Pod->get_clean_inc();
516             my $package = Module::Metadata->new_from_module($package_name, inc => $include);
517             return if (ref $package ne 'Module::Metadata');
518              
519             my $doc = PLS::Parser::Document->new(path => $package->filename);
520             return if (ref $doc ne 'PLS::Parser::Document');
521              
522             foreach my $subroutine (@{$doc->get_subroutines()})
523 0     0 1 0 {
524             next unless ($subroutine->name eq $subroutine_name);
525 0         0  
526 0         0 return {
527 0 0       0 uri => URI::file->new($package->filename)->as_string,
528             range => $subroutine->range(),
529 0         0 signature => $subroutine->location_info->{signature}
530 0 0       0 };
531             } ## end foreach my $subroutine (@{$doc...})
532 0         0  
  0         0  
533             return;
534 0 0       0 } ## end sub find_external_subroutine
535              
536             =head2 find_external_package
537              
538             This attempts to find the location of an external package by name.
539              
540 0         0 =cut
541              
542             {
543 0         0 my ($self, $package_name) = @_;
544              
545             return unless (length $package_name);
546              
547             my $include = PLS::Parser::Pod->get_clean_inc();
548             my $metadata = Module::Metadata->new_from_module($package_name, inc => $include);
549              
550             return if (ref $metadata ne 'Module::Metadata');
551              
552             my $document = PLS::Parser::Document->new(path => $metadata->filename);
553             return if (ref $document ne 'PLS::Parser::Document');
554 0     0 1 0  
555             foreach my $package (@{$document->get_packages()})
556 0 0       0 {
557             next unless ($package->name eq $package_name);
558 0         0  
559 0         0 return {
560             uri => URI::file->new($metadata->filename)->as_string,
561 0 0       0 range => $package->range()
562             };
563 0         0 } ## end foreach my $package (@{$document...})
564 0 0       0  
565             return;
566 0         0 } ## end sub find_external_package
  0         0  
567              
568 0 0       0 =head2 go_to_variable_definition
569              
570             This finds the definition of a variable.
571 0         0  
572             This B<probably> only works correctly for C<my>, C<local>, and C<state> variables,
573             but may also work for C<our> variables as long as they are in the same file.
574              
575             =cut
576 0         0  
577             {
578             my ($self, $variable, $element, $line_number, $column_number) = @_;
579              
580             my $cursor = $element->element;
581             my $prev_cursor;
582             my $document = $cursor->top;
583              
584             my $declaration;
585             state $var_rx = qr/((?&PerlVariable))$PPR::GRAMMAR/;
586              
587             OUTER: while (1)
588             {
589             $prev_cursor = $cursor;
590 0     0 1 0 $cursor = $cursor->parent;
591              
592 0         0 next unless blessed($cursor);
593 0         0  
594 0         0 if ($cursor->isa('PPI::Structure::Block') or $cursor->isa('PPI::Document'))
595             {
596 0         0 CHILDREN: foreach my $child ($cursor->children)
597 0         0 {
598             last CHILDREN if $child == $prev_cursor;
599 0         0 next unless blessed($child);
600              
601 0         0 if ($child->isa('PPI::Statement::Variable') and any { $_ eq $variable } $child->variables)
602 0         0 {
603             $declaration = $child;
604 0 0       0 last OUTER;
605             }
606 0 0 0     0 if ($child->isa('PPI::Statement::Include') and $child->type eq 'use' and $child->pragma eq 'vars')
    0          
607             {
608 0         0 while ($child =~ /$var_rx/g)
609             {
610 0 0       0 next if ($1 ne $variable);
611 0 0       0 $declaration = $child;
612             last OUTER;
613 0 0 0 0   0 } ## end while ($child =~ /$var_rx/g...)
  0         0  
614             } ## end if ($child->isa('PPI::Statement::Include'...))
615 0         0 } ## end foreach my $child ($cursor->...)
616 0         0 } ## end if ($cursor->isa('PPI::Structure::Block'...))
617             elsif ($cursor->isa('PPI::Statement::Compound'))
618 0 0 0     0 {
      0        
619             if ($cursor->type eq 'foreach')
620 0         0 {
621             CHILDREN: foreach my $child ($cursor->children)
622 0 0       0 {
623 0         0 last CHILDREN if $child == $prev_cursor;
624 0         0 next unless blessed($child);
625              
626             if ($child->isa('PPI::Token::Word') and $child =~ /^my|our|local|state$/)
627             {
628             if (blessed($child->snext_sibling) and $child->snext_sibling->isa('PPI::Token::Symbol') and $child->snext_sibling->symbol eq $variable)
629             {
630             #$declaration = $child->snext_sibling;
631 0 0       0 $declaration = $cursor;
632             last OUTER;
633 0         0 } ## end if (blessed($child->snext_sibling...))
634             } ## end if ($child->isa('PPI::Token::Word'...))
635 0 0       0 } ## end foreach my $child ($cursor->...)
636 0 0       0 } ## end if ($cursor->type eq 'foreach'...)
637             else
638 0 0 0     0 {
639             my $condition = first { $_->isa('PPI::Structure::Condition') } grep { blessed($_) } $cursor->children;
640 0 0 0     0 next OUTER if (not blessed($condition) or not $condition->isa('PPI::Structure::Condition'));
      0        
641              
642             CHILDREN: foreach my $child ($condition->children)
643 0         0 {
644 0         0 last CHILDREN if $child == $prev_cursor;
645             next unless blessed($child);
646              
647             if ($child->isa('PPI::Statement::Variable') and any { $_ eq $variable } $child->variables)
648             {
649             $declaration = $child;
650             last OUTER;
651 0     0   0 }
  0         0  
  0         0  
652 0 0 0     0 } ## end foreach my $child ($condition...)
653             } ## end else [ if ($cursor->type eq 'foreach'...)]
654 0         0 } ## end elsif ($cursor->isa('PPI::Statement::Compound'...))
655              
656 0 0       0 last if $cursor == $document;
657 0 0       0 } ## end while (1)
658              
659 0 0 0 0   0 return if (not blessed($declaration) or not $declaration->isa('PPI::Element'));
  0         0  
660              
661 0         0 $element = PLS::Parser::Element->new(file => $self->{path}, document => $self->{document}, element => $declaration);
662 0         0  
663             return [
664             {
665             uri => $self->{uri},
666             range => $element->range()
667             }
668 0 0       0 ];
669             } ## end sub go_to_variable_definition
670              
671 0 0 0     0 =head2 open_file
672              
673 0         0 This adds a file and its text to a list of open files.
674              
675             =cut
676              
677             {
678 0         0 my ($class, %args) = @_;
679              
680             return unless $args{languageId} eq 'perl';
681              
682             $FILES{$args{uri}} = \($args{text});
683             $VERSIONS{$args{uri}} = $args{version};
684              
685             return;
686             } ## end sub open_file
687              
688             =head2 open_files
689              
690             This provides a list of names of files that are currently open.
691 3     3 1 2154  
692             =cut
693 3 50       36  
694             {
695 3         30 return [keys %FILES];
696 3         35 }
697              
698 3         19 =head2 update_file
699              
700             This patches an open file in memory to keep it synched with
701             the actual file in the editor.
702              
703             =cut
704              
705             {
706             my ($class, @args) = @_;
707              
708             my %args = @args;
709 9     9 1 114  
710             my $file = $FILES{$args{uri}};
711             return if (ref $file ne 'SCALAR');
712              
713             $VERSIONS{$args{uri}} = $args{version};
714              
715             foreach my $change (@{$args{changes}})
716             {
717             if (ref $change->{range} eq 'HASH')
718             {
719             my @lines = _split_lines($$file);
720             my @replacement = _split_lines($change->{text});
721 0     0 1 0  
722             my ($starting_text, $ending_text);
723 0         0  
724             # get the text that we're not replacing at the start and end of each selection
725 0         0 # this needs to be done in UTF-16 according to the LSP specification.
726 0 0       0 # the byte order doesn't matter because we're decoding immediately,
727             # so we are using little endian.
728 0         0  
729             if ($#lines >= $change->{range}{start}{line})
730 0         0 {
  0         0  
731             my $first_line = Encode::encode('UTF-16LE', $lines[$change->{range}{start}{line}]);
732 0 0       0  
733             # each code unit is two bytes long
734 0         0 my $starting_code_unit = $change->{range}{start}{character} * 2;
735 0         0 $starting_text = substr $first_line, 0, $starting_code_unit;
736             $starting_text = Encode::decode('UTF-16LE', $starting_text);
737 0         0 } ## end if ($#lines >= $change...)
738              
739             if ($#lines >= $change->{range}{end}{line})
740             {
741             my $last_line = Encode::encode('UTF-16LE', $lines[$change->{range}{end}{line}]);
742              
743             # each code unit is two bytes long
744 0 0       0 my $ending_code_unit = $change->{range}{end}{character} * 2;
745             $ending_text = substr $last_line, $ending_code_unit;
746 0         0 $ending_text = Encode::decode('UTF-16LE', $ending_text);
747             } ## end if ($#lines >= $change...)
748              
749 0         0 # append the existing text to the replacement
750 0         0 if (length $starting_text)
751 0         0 {
752             $replacement[0] = length $replacement[0] ? $starting_text . $replacement[0] : $starting_text;
753             }
754 0 0       0 if (length $ending_text)
755             {
756 0         0 if (scalar @replacement)
757             {
758             $replacement[-1] .= $ending_text;
759 0         0 }
760 0         0 else
761 0         0 {
762             $replacement[0] = $ending_text;
763             }
764             } ## end if (length $ending_text...)
765 0 0       0  
766             # replace the lines in the range (which may not match the number of lines in the replacement)
767 0 0       0 # with the replacement, including the existing text that is not changing, that we appended above
768             my $lines_replacing = $change->{range}{end}{line} - $change->{range}{start}{line} + 1;
769 0 0       0 splice @lines, $change->{range}{start}{line}, $lines_replacing, @replacement;
770             $$file = join '', @lines;
771 0 0       0 } ## end if (ref $change->{range...})
772             else
773 0         0 {
774             # no range means we're updating the entire document
775             $$file = $change->{text};
776             }
777 0         0 } ## end foreach my $change (@{$args...})
778              
779             return;
780             } ## end sub update_file
781              
782             =head2 close_file
783 0         0  
784 0         0 This removes a file from the list of open files.
785 0         0  
786             =cut
787              
788             {
789             my ($class, @args) = @_;
790 0         0  
791             my %args = @args;
792              
793             delete $FILES{$args{uri}};
794 0         0 delete $VERSIONS{$args{uri}};
795              
796             return;
797             } ## end sub close_file
798              
799             =head2 get_subroutines
800              
801             This gets a list of all subroutines in a document.
802              
803             =cut
804              
805 0     0 1 0 {
806             my ($self) = @_;
807 0         0  
808             my $find = PPI::Find->new(
809 0         0 sub {
810 0         0 $_[0]->isa('PPI::Statement::Sub') and not $_[0]->isa('PPI::Statement::Scheduled') and ref $_[0]->block eq 'PPI::Structure::Block';
811             }
812 0         0 );
813             return [map { PLS::Parser::Element::Subroutine->new(document => $self->{document}, element => $_, file => $self->{path}) } $find->in($self->{document})];
814             } ## end sub get_subroutines
815              
816             =head2 get_constants
817              
818             This gets a list of all constants in a document.
819              
820             Only constants declared with C<use constant> are found.
821              
822             =cut
823 0     0 1 0  
824             {
825             my ($self, $element) = @_;
826              
827 0 0 0 0   0 my @matches;
828              
829 0         0 if (ref $element eq 'PPI::Statement::Include')
830 0         0 {
  0         0  
831             @matches = ($element);
832             }
833             else
834             {
835             my $find = PPI::Find->new(
836             sub {
837             my ($element) = @_;
838              
839             return 0 unless $element->isa('PPI::Statement::Include');
840             return unless $element->type eq 'use';
841             return (length $element->module and $element->module eq 'constant');
842             }
843 0     0 1 0 );
844              
845 0         0 @matches = $find->in($self->{document});
846             } ## end else [ if (ref $element eq 'PPI::Statement::Include'...)]
847 0 0       0  
848             my @constants;
849 0         0  
850             foreach my $match (@matches)
851             {
852             my ($constructor) = grep { $_->isa('PPI::Structure::Constructor') } $match->children;
853              
854             if (ref $constructor eq 'PPI::Structure::Constructor')
855 0     0   0 {
856             push @constants, grep { _is_constant($_) }
857 0 0       0 map { $_->children }
858 0 0       0 grep { $_->isa('PPI::Statement::Expression') } $constructor->children;
859 0   0     0 } ## end if (ref $constructor eq...)
860             else
861 0         0 {
862             push @constants, grep { _is_constant($_) } $match->children;
863 0         0 }
864             } ## end foreach my $match (@matches...)
865              
866 0         0 return [map { PLS::Parser::Element::Constant->new(document => $self->{document}, element => $_, file => $self->{path}) } @constants];
867             } ## end sub get_constants
868 0         0  
869             =head2 get_packages
870 0         0  
  0         0  
871             This gets a list of all packages in a document.
872 0 0       0  
873             =cut
874 0         0  
875 0         0 {
876 0         0 my ($self) = @_;
  0         0  
877              
878             my $find = PPI::Find->new(sub { $_[0]->isa('PPI::Statement::Package') });
879             return [map { PLS::Parser::Element::Package->new(document => $self->{document}, element => $_, file => $self->{path}) } $find->in($self->{document})];
880 0         0 } ## end sub get_packages
  0         0  
881              
882             =head2 get_variable_statements
883              
884 0         0 This gets a list of all variable statements in a document.
  0         0  
885             A variable statement is a statement which declares one or more variables.
886              
887             =cut
888              
889             {
890             my ($self, $element) = @_;
891              
892             my @elements;
893              
894             if (blessed($element) and $element->isa('PPI::Statement::Variable'))
895 0     0 1 0 {
896             @elements = ($element);
897 0     0   0 }
  0         0  
898 0         0 else
  0         0  
899             {
900             my $find = PPI::Find->new(sub { $_[0]->isa('PPI::Statement::Variable') });
901             @elements = $find->in($self->{document});
902             }
903              
904             return [map { PLS::Parser::Element::VariableStatement->new(document => $self->{document}, element => $_, file => $self->{path}) } @elements];
905             } ## end sub get_variable_statements
906              
907             =head2 get_full_text
908              
909             This returns a SCALAR reference of the in-memory text of the current document.
910 0     0 1 0  
911             =cut
912 0         0  
913             {
914 0 0 0     0 my ($self) = @_;
915              
916 0         0 return $self->text_from_uri($self->{uri});
917             }
918              
919             =head2 get_variables_fast
920 0     0   0  
  0         0  
921 0         0 This gets a list of all variables in the current document.
922             It uses L<PPR> to do so, which is faster than L<PPI>, but only provides a list of strings.
923              
924 0         0 =cut
  0         0  
925              
926             {
927             my ($self, $text) = @_;
928              
929             $text = $self->get_full_text() if (ref $text ne 'SCALAR');
930             return [] if (ref $text ne 'SCALAR');
931              
932             state $variable_decl_rx = qr/((?&PerlVariableDeclaration))$PPR::GRAMMAR/;
933             state $lvalue_rx = qr/((?&PerlLvalue))$PPR::GRAMMAR/;
934             state $variable_rx = qr/((?&PerlVariable))$PPR::GRAMMAR/;
935 0     0 1 0 my @variables;
936              
937 0         0 while ($$text =~ /$variable_rx/g)
938             {
939             my $declaration = $1;
940             my ($lvalue) = $declaration =~ /$lvalue_rx/;
941              
942             next unless (length $lvalue);
943              
944             while ($lvalue =~ /$variable_rx/g)
945             {
946             my $variable = $1;
947             next unless (length $variable);
948             $variable =~ s/^\s+|\s+$//g;
949 0     0 1 0  
950             push @variables, $variable;
951 0 0       0 } ## end while ($lvalue =~ /$variable_rx/g...)
952 0 0       0 } ## end while ($$text =~ /$variable_rx/g...)
953              
954 0         0 return \@variables;
955 0         0 } ## end sub get_variables_fast
956 0         0  
957 0         0 =head2 get_packages_fast
958              
959 0         0 This gets a list of all packages in the current document.
960             It uses L<PPR> to do so, which is faster than L<PPI>, but only provides a list of strings.
961 0         0  
962 0         0 =cut
963              
964 0 0       0 {
965             my ($self, $text) = @_;
966 0         0  
967             $text = $self->get_full_text() if (ref $text ne 'SCALAR');
968 0         0 return [] if (ref $text ne 'SCALAR');
969 0 0       0  
970 0         0 state $package_rx = qr/((?&PerlPackageDeclaration))$PPR::GRAMMAR/;
971             my @packages;
972 0         0  
973             while ($$text =~ /$package_rx/g)
974             {
975             my ($package) = $1 =~ /^package\s+(\S+)\s*;\s*$/;
976 0         0 next unless (length $package);
977              
978             push @packages, $package;
979             } ## end while ($$text =~ /$package_rx/g...)
980              
981             return \@packages;
982             } ## end sub get_packages_fast
983              
984             =head2 get_subroutines_fast
985              
986             This gets a list of all subroutines in the current document.
987             It uses L<PPR> to do so, which is faster than L<PPI>, but only provides a list of strings.
988 0     0 1 0  
989             =cut
990 0 0       0  
991 0 0       0 {
992             my ($self, $text) = @_;
993 0         0  
994 0         0 $text = $self->get_full_text() if (ref $text ne 'SCALAR');
995             return [] if (ref $text ne 'SCALAR');
996 0         0  
997             state $sub_rx = qr/sub\b(?&PerlOWS)((?&PerlOldQualifiedIdentifier))$PPR::GRAMMAR/;
998 0         0 my @subroutine_declarations;
999 0 0       0  
1000             while ($$text =~ /$sub_rx/g)
1001 0         0 {
1002             push @subroutine_declarations, $1;
1003             }
1004 0         0  
1005             return [
1006             map { s/^\s+|\s+$//r }
1007             grep { defined } @subroutine_declarations
1008             ];
1009             } ## end sub get_subroutines_fast
1010              
1011             =head2 get_constants_fast
1012              
1013             This gets a list of all constants in the current document.
1014             It uses L<PPR> to do so, which is faster than L<PPI>, but only provides a list of strings.
1015              
1016 0     0 1 0 This only finds constants declared with C<use constant>.
1017              
1018 0 0       0 =cut
1019 0 0       0  
1020             {
1021 0         0 my ($self, $text) = @_;
1022 0         0  
1023             $text = $self->get_full_text() if (ref $text ne 'SCALAR');
1024 0         0 return [] if (ref $text ne 'SCALAR');
1025              
1026 0         0 state $block_rx = qr/use\h+constant(?&PerlOWS)((?&PerlBlock))$PPR::GRAMMAR/;
1027             state $bareword_rx = qr/((?&PerlBareword))(?&PerlOWS)(?&PerlComma)$PPR::GRAMMAR/;
1028             state $one_constant_rx = qr/use\h+constant\h+((?&PerlBareword))(?&PerlOWS)(?&PerlComma)$PPR::GRAMMAR/;
1029             my @constants;
1030 0         0  
1031 0         0 while ($$text =~ /$block_rx/g)
  0         0  
1032             {
1033             my $block = $1;
1034              
1035             while ($block =~ /$bareword_rx/g)
1036             {
1037             my $constant = $1;
1038              
1039             next unless (length $constant);
1040             $constant =~ s/^\s+|\s+$//g;
1041              
1042             push @constants, $constant;
1043             } ## end while ($block =~ /$bareword_rx/g...)
1044             } ## end while ($$text =~ /$block_rx/g...)
1045              
1046 0     0 1 0 while ($$text =~ /$one_constant_rx/g)
1047             {
1048 0 0       0 my $constant = $1;
1049 0 0       0 next unless (length $constant);
1050             $constant =~ s/^\s+|\s+$//g;
1051 0         0  
1052 0         0 push @constants, $constant;
1053 0         0 } ## end while ($$text =~ /$one_constant_rx/g...)
1054 0         0  
1055             return \@constants;
1056 0         0 } ## end sub get_constants_fast
1057              
1058 0         0 {
1059             my ($self, $text) = @_;
1060 0         0  
1061             $text = $self->get_full_text() if (ref $text ne 'SCALAR');
1062 0         0 return [] if (ref $text ne 'SCALAR');
1063              
1064 0 0       0 state $use_rx = qr/((?&PerlUseStatement))$PPR::GRAMMAR/;
1065 0         0 state $identifier_rx = qr/use\h+((?&PerlQualifiedIdentifier))(?&PerlOWS)(?&PerlList)?$PPR::GRAMMAR/;
1066              
1067 0         0 my @imports;
1068              
1069             while ($$text =~ /$use_rx/g)
1070             {
1071 0         0 my $use = $1;
1072              
1073 0         0 if ($use =~ /$identifier_rx/)
1074 0 0       0 {
1075 0         0 my $module = $1;
1076              
1077 0         0 # Assume lowercase modules are pragmas.
1078             next if (lc $module eq $module);
1079              
1080 0         0 push @imports, {use => $use, module => $module};
1081             } ## end if ($use =~ /$identifier_rx/...)
1082             } ## end while ($$text =~ /$use_rx/g...)
1083              
1084             return \@imports;
1085 0     0 0 0 } ## end sub get_imports
1086              
1087 0 0       0 =head2 format_range
1088 0 0       0  
1089             This formats a range of text in the document using perltidy.
1090 0         0  
1091 0         0 =cut
1092              
1093 0         0 {
1094             my ($class, %args) = @_;
1095 0         0  
1096             $args{formatting_options} = {} unless (ref $args{formatting_options} eq 'HASH');
1097 0         0 my $range = $args{range};
1098             my $text = $args{text};
1099 0 0       0  
1100             if (ref $text ne 'SCALAR')
1101 0         0 {
1102             return (0, {code => -32700, message => 'Could not get document text.'});
1103             }
1104 0 0       0  
1105             my $selection = '';
1106 0         0 my $whole_file = 0;
1107              
1108             if (ref $range eq 'HASH')
1109             {
1110 0         0 # if we've selected up until the first character of the next line,
1111             # just format up to the line before that
1112             $range->{end}{line}-- if ($range->{end}{character} == 0);
1113              
1114             my @lines = _split_lines($$text);
1115             @lines = @lines[$range->{start}{line} .. $range->{end}{line}];
1116              
1117             # ignore the column, and just format the entire line.
1118             # the text will likely get messed up if you don't include the entire line, anyway.
1119             $range->{start}{character} = 0;
1120             $range->{end}{character} = 0;
1121 0     0 1 0 $range->{end}{line}++;
1122             $selection = join '', @lines;
1123 0 0       0 } ## end if (ref $range eq 'HASH'...)
1124 0         0 else
1125 0         0 {
1126             $whole_file = 1;
1127 0 0       0 $selection = $$text;
1128             my $lines = () = $selection =~ m{($/)}g;
1129 0         0 $lines++;
1130              
1131             $range = {
1132 0         0 start => {
1133 0         0 line => 0,
1134             character => 0
1135 0 0       0 },
1136             end => {
1137             line => $lines,
1138             character => 0
1139 0 0       0 }
1140             };
1141 0         0 } ## end else [ if (ref $range eq 'HASH'...)]
1142 0         0  
1143             my $formatted = '';
1144             my $stderr = '';
1145             my $argv = '-se';
1146 0         0 if (length $args{formatting_options}{tabSize})
1147 0         0 {
1148 0         0 $argv .= $args{formatting_options}{insertSpaces} ? ' -i=' : ' -et=';
1149 0         0 $argv .= $args{formatting_options}{tabSize};
1150             }
1151             my ($perltidyrc) = glob $args{perltidyrc};
1152             undef $perltidyrc if (not length $perltidyrc or not -f $perltidyrc or not -r $perltidyrc);
1153 0         0 my $error = Perl::Tidy::perltidy(source => \$selection, destination => \$formatted, stderr => \$stderr, perltidyrc => $perltidyrc, argv => $argv);
1154 0         0  
1155 0         0 # get the number of lines in the formatted result - we need to modify the range if
1156 0         0 # any lines were added
1157             my $lines = () = $formatted =~ m{($/)}g;
1158 0         0 $lines++;
1159              
1160             # if the selection length has increased due to formatting, update the end.
1161             $range->{end}{line} = $lines if ($whole_file and $lines > $range->{end}{line});
1162              
1163             $formatted =~ s/\h+$//gm if ($args{formatting_options}{trimTrailingWhitespace});
1164              
1165             if ($args{formatting_options}{insertFinalNewline})
1166             {
1167             $formatted .= "\n" unless ($formatted =~ /\n$/);
1168             }
1169             if ($args{formatting_options}{trimFinalNewlines})
1170 0         0 {
1171 0         0 $formatted =~ s/\n+$/\n/;
1172 0         0 }
1173 0 0       0  
1174             $stderr =~ s/^<source_stream>:\s*//gm;
1175 0 0       0 $stderr =~ s/^Begin Error Output Stream.*$//m;
1176 0         0 $stderr =~ s/^.*To save a full \.LOG file.*$//m;
1177             $stderr =~ s/^\s*$//gm;
1178 0         0  
1179 0 0 0     0 return (1, undef) if ($error == 1 or length $stderr);
      0        
1180 0         0  
1181             return (
1182             1,
1183             [
1184 0         0 {
1185 0         0 range => $range,
1186             newText => $formatted
1187             }
1188 0 0 0     0 ]
1189             );
1190 0 0       0 } ## end sub format_range
1191              
1192 0 0       0 =head2 format
1193              
1194 0 0       0 This formats the entire document using perltidy.
1195              
1196 0 0       0 =cut
1197              
1198 0         0 {
1199             my ($class, %args) = @_;
1200              
1201 0         0 return $class->format_range(formatting_options => $args{formatting_options}, text => $args{text}, perltidyrc => $args{perltidyrc});
1202 0         0 }
1203 0         0  
1204 0         0 =head2 _ppi_location
1205              
1206 0 0 0     0 This converts an LSP 0-indexed location to a PPI 1-indexed location.
1207              
1208             =cut
1209 0         0  
1210             {
1211             my ($line_number, $column_number) = @_;
1212              
1213             return ++$line_number, ++$column_number;
1214             }
1215              
1216             =head2 text_from_uri
1217              
1218             This returns a SCALAR reference to the text of a particular URI.
1219              
1220             =cut
1221              
1222             {
1223             my (undef, $uri) = @_;
1224              
1225             if (ref $FILES{$uri} eq 'SCALAR')
1226             {
1227 0     0 1 0 return $FILES{$uri};
1228             }
1229 0         0 else
1230             {
1231             my $file = URI->new($uri);
1232             open my $fh, '<', $file->file or return;
1233             my $text = do { local $/; <$fh> };
1234             return \$text;
1235             } ## end else [ if (ref $FILES{$uri} eq...)]
1236             } ## end sub text_from_uri
1237              
1238             {
1239             my ($uri) = @_;
1240 27     27   36  
1241             return $VERSIONS{$uri};
1242 27         49 }
1243              
1244             =head2 _get_ppi_document
1245              
1246             This creates a L<PPI::Document> object for a document. It will
1247             return an L<PPI::Document> from memory if the file has not changed since it was last parsed.
1248              
1249             =cut
1250              
1251             {
1252             my ($self, %args) = @_;
1253 3     3 1 45  
1254             my $file;
1255 3 50       34  
1256             if ($args{text})
1257 3         17 {
1258             $file = $args{text};
1259             }
1260             elsif (length $args{uri})
1261 0         0 {
1262 0 0       0 if (ref $FILES{$args{uri}} eq 'SCALAR')
1263 0         0 {
  0         0  
  0         0  
1264 0         0 $file = $FILES{$args{uri}};
1265             }
1266             else
1267             {
1268             $file = URI->new($args{uri})->file;
1269             }
1270 12     12 0 161 } ## end elsif (length $args{uri})
1271              
1272 12         85 if (length $args{line})
1273             {
1274             my $fh;
1275             if (ref $file eq 'SCALAR')
1276             {
1277             my $line = $args{line};
1278             my $new_line = $/;
1279              
1280             my ($text) = $$file =~ /(?:[^$new_line]*$new_line){$line}([^$new_line]*)$new_line?/m;
1281              
1282             if (length $text)
1283             {
1284 32     32   81 $file = \$text;
1285             $self->{one_line} = 1;
1286 32         42 }
1287             } ## end if (ref $file eq 'SCALAR'...)
1288 32 50       88 elsif (open $fh, '<', $file)
    50          
1289             {
1290 0         0 my @text = <$fh>;
1291              
1292             if (length $text[$args{line}])
1293             {
1294 32 100       83 $file = \($text[$args{line}]);
1295             $self->{one_line} = 1;
1296 27         51 }
1297             } ## end elsif (open $fh, '<', $file...)
1298             } ## end if (length $args{line}...)
1299              
1300 5         14 my $document = PPI::Document->new($file, readonly => 1);
1301             return if (not blessed($document) or not $document->isa('PPI::Document'));
1302              
1303             $document->index_locations();
1304 32 100       714  
1305             return $document;
1306 28         34 } ## end sub _get_ppi_document
1307 28 100       124  
    50          
1308             =head2 _is_constant
1309 27         42  
1310 27         40 Determines if a PPI element is a constant.
1311              
1312 27         686 =cut
1313              
1314 27 50       85 {
1315             my ($element) = @_;
1316 27         40  
1317 27         54 return unless $element->isa('PPI::Token::Word');
1318             return unless ref $_->snext_sibling eq 'PPI::Token::Operator';
1319             return $_->snext_sibling->content eq '=>';
1320             } ## end sub _is_constant
1321              
1322 1         62 =head2 find_word_under_cursor
1323              
1324 1 50       6 Gets information about the current word under the cursor.
1325             Returns a four-element list:
1326 1         3  
1327 1         21 =over
1328              
1329             =item The range where the word is located
1330              
1331             =item A boolean indicating whether the word is before an arrow (->) or not.
1332 32         134  
1333 32 100 66     190814 =item The name of the package where the word is located
1334              
1335 30         98 =item The word under the cursor to be used as a filter for searching
1336              
1337 30         40244 =back
1338              
1339             =cut
1340              
1341             {
1342             my ($self, $line, $character) = @_;
1343              
1344             my @elements = $self->find_elements_at_location($line, $character);
1345             @elements = map { $_->tokens } @elements;
1346             @elements =
1347             sort { (abs $character - $a->lsp_column_number) <=> (abs $character - $b->lsp_column_number) } @elements;
1348 0     0   0 my @in_range = grep { $_->lsp_column_number <= $character and $_->lsp_column_number + length($_->content) >= $character } @elements;
1349             my $element = first
1350 0 0       0 {
1351 0 0       0 $_->type eq 'PPI::Token::Word'
1352 0         0 or $_->type eq 'PPI::Token::Label'
1353             or $_->type eq 'PPI::Token::Symbol'
1354             or $_->type eq 'PPI::Token::Magic'
1355             or $_->type eq 'PPI::Token::Operator'
1356             or $_->type eq 'PPI::Token::Quote::Double'
1357             or $_->type eq 'PPI::Token::Quote::Interpolate'
1358             or $_->type eq 'PPI::Token::QuoteLike::Regexp'
1359             or $_->type eq 'PPI::Token::QuoteLike::Command'
1360             or $_->element->isa('PPI::Token::Regexp')
1361             } @in_range;
1362             my $closest_operator = first { $_->type eq 'PPI::Token::Operator' } @elements;
1363              
1364             if (blessed($element) and $element->isa('PLS::Parser::Element') and $element->type eq 'PPI::Token::Operator')
1365             {
1366             return $element->range(), 0, '', '-' if ((not blessed($element->element->previous_sibling) or $element->element->previous_sibling->isa('PPI::Token::Whitespace')) and $element->content eq '-');
1367             undef $element;
1368             }
1369              
1370             if (
1371             blessed($element)
1372             and $element->isa('PLS::Parser::Element')
1373             and ( $element->type eq 'PPI::Token::Quote::Double'
1374             or $element->type eq 'PPI::Token::Quote::Interpolate'
1375             or $element->type eq 'PPI::Token::QuoteLike::Regexp'
1376 27     27 1 9246 or $element->type eq 'PPI::Token::QuoteLike::Command'
1377             or $element->element->isa('PPI::Token::Regexp'))
1378 27         65 )
1379 27         39 {
  101         167  
1380             my $string_start = $character - $element->range->{start}{character};
1381 27         71 my $string_end = $character - $element->range->{end}{character};
  458         658  
1382 27 100       60  
  205         748  
1383             return if ($string_start <= 0);
1384              
1385 49 100 100 49   103 my $string = $element->name;
      100        
      100        
      100        
      66        
      100        
      100        
      100        
1386              
1387             if ($string =~ /^"/)
1388             {
1389             $string = substr $string, 1, -1;
1390             }
1391             elsif ($string =~ /^(q[qrx]|[ysm]|tr)(\S)/ or $string =~ m{^()(/)})
1392             {
1393             my $operator = $1 // '';
1394             my $delimiter = $2;
1395 27         210 my $end_delimiter = $delimiter;
1396 27     134   166 $end_delimiter = '}' if ($delimiter eq '{');
  134         206  
1397             $end_delimiter = ')' if ($delimiter eq '(');
1398 27 100 66     162 $end_delimiter = '>' if ($delimiter eq '>');
      100        
1399             $end_delimiter = ']' if ($delimiter eq ']');
1400 4 50 33     10  
      33        
1401 4         70 if ($string =~ /\Q$end_delimiter\E$/)
1402             {
1403             $string = substr $string, length($operator) + 1, -1;
1404 27 100 66     110 }
      100        
      100        
1405             else
1406             {
1407             $string = substr $string, length($operator) + 1;
1408             }
1409             } ## end elsif ($string =~ /^(q[qrx]|[ysm]|tr)(\S)/...)
1410              
1411             state $var_rx = qr/((?&PerlVariable)|[\$\@\%])$PPR::GRAMMAR$/;
1412              
1413             if ($string =~ /$var_rx/)
1414 5         12 {
1415 5         14 return {
1416             start => {
1417 5 50       15 line => $line,
1418             character => $character - length $1
1419 5         9 },
1420             end => {
1421 5 50 33     52 line => $line,
    50          
1422             character => $character
1423 0         0 }
1424             },
1425             0, '', $1;
1426             } ## end if ($string =~ /$var_rx/...)
1427 0   0     0  
1428 0         0 undef $element;
1429 0         0 } ## end if (blessed($element) ...)
1430 0 0       0  
1431 0 0       0 if (not blessed($element) or not $element->isa('PLS::Parser::Element'))
1432 0 0       0 {
1433 0 0       0 # Let's see if PPI thinks that we're typing the start of a Regexp operator.
1434             my $regexp = first { $_->element->isa('PPI::Token::Regexp') } @elements;
1435 0 0       0 if (
1436             blessed($regexp)
1437 0         0 and ( $regexp->type eq 'PPI::Token::Regexp::Match' and $regexp->content eq 'm'
1438             or $regexp->type eq 'PPI::Token::Regexp::Substitute' and $regexp->content eq 's'
1439             or $regexp->type eq 'PPI::Token::Regexp::Transliterate' and ($regexp->content eq 'tr' or $regexp->content eq 'y'))
1440             )
1441 0         0 {
1442             $element = $regexp;
1443             } ## end if (blessed($regexp) and...)
1444             } ## end if (not blessed($element...))
1445 5         26169  
1446             if (not blessed($element) or not $element->isa('PLS::Parser::Element'))
1447 5 50       119 {
1448             # Let's see if PPI thinks that we're typing the start of a quote operator.
1449             my $literal = first { $_->type eq 'PPI::Token::Quote::Literal' } @elements;
1450 0         0 my $interpolate = first { $_->type eq 'PPI::Token::Quote::Interpolate' } @elements;
1451             my $qr = first { $_->type eq 'PPI::Token::QuoteLike::Regexp' } @elements;
1452             my $qw = first { $_->type eq 'PPI::Token::QuoteLike::Words' } @elements;
1453             my $qx = first { $_->type eq 'PPI::Token::QuoteLike::Command' } @elements;
1454              
1455             if (blessed($literal) and $literal->element->content eq 'q')
1456             {
1457             $element = $literal;
1458             }
1459             elsif (blessed($interpolate) and $interpolate->element->content eq 'qq')
1460             {
1461             $element = $interpolate;
1462 5         13 }
1463             elsif (blessed($qr) and $qr->element->content eq 'qr')
1464             {
1465 27 100 66     96 $element = $qr;
1466             }
1467             elsif (blessed($qw) and $qw->element->content eq 'qw')
1468 12     61   44 {
  61         95  
1469 12 50 33     47 $element = $qw;
      66        
1470             }
1471             elsif (blessed($qx) and $qx->element->content eq 'qx')
1472             {
1473             $element = $qx;
1474             }
1475             } ## end if (not blessed($element...))
1476 2         13  
1477             if (not blessed($element) or not $element->isa('PLS::Parser::Element'))
1478             {
1479             my $cast = first { $_->type eq 'PPI::Token::Cast' } @elements;
1480 27 100 66     89  
1481             # A cast probably means only a sigil was typed.
1482             if (blessed($cast) and $cast->isa('PLS::Parser::Element'))
1483 10     57   27 {
  57         90  
1484 10     57   32 return $cast->range, 0, '', $cast->name;
  57         87  
1485 10     57   31 }
  57         86  
1486 10     57   30 } ## end if (not blessed($element...))
  57         85  
1487 10     57   39  
  57         85  
1488             if (
1489 10 100 66     86 (
    100 66        
    100 66        
    100 66        
    100 66        
1490             not blessed($element)
1491 1         5 or not $element->isa('PLS::Parser::Element')
1492             )
1493             and blessed($closest_operator)
1494             and $closest_operator->isa('PLS::Parser::Element')
1495 1         6 and $closest_operator->name eq '->'
1496             and $closest_operator->lsp_column_number + length($closest_operator->content) == $character
1497             )
1498             {
1499 1         7 my $range = $closest_operator->range;
1500             $range->{start}{character} += length $closest_operator->content;
1501             $range->{end}{character} = $range->{start}{character};
1502              
1503 1         7 # If there is a word before the arrow AND it is not after another arrow, use it as the package name.
1504             # Otherwise, there is no package name, but there is an arrow and a blank filter.
1505             if (
1506             blessed($closest_operator->previous_sibling)
1507 1         6 and $closest_operator->previous_sibling->isa('PLS::Parser::Element')
1508             and $closest_operator->previous_sibling->type eq 'PPI::Token::Word'
1509             and ( not blessed($closest_operator->previous_sibling->element->previous_sibling)
1510             or not $closest_operator->previous_sibling->element->previous_sibling->isa('PPI::Token::Operator')
1511 27 100 66     99 or not $closest_operator->previous_sibling->element->previous_sibling eq '->')
1512             )
1513 5     42   15 {
  42         65  
1514             return $range, 1, $closest_operator->previous_sibling->name, '';
1515             } ## end if (blessed($closest_operator...))
1516 5 100 66     27 else
1517             {
1518 1         4 return $range, 1, '', '';
1519             }
1520             } ## end if ((not blessed($element...)))
1521              
1522 26 50 66     125 return if (not blessed($element) or not $element->isa('PLS::Parser::Element'));
      66        
      66        
      33        
      33        
1523              
1524             if ($element->type eq 'PPI::Token::Magic')
1525             {
1526             my $range = $element->range;
1527             $range->{end}{character}--;
1528             return $range, 0, '', '$';
1529             } ## end if ($element->type eq ...)
1530              
1531             # If we're typing right before a sigil, return the previous element.
1532             if ($element->type eq 'PPI::Token::Symbol' and $element->lsp_column_number == $character and blessed($element->previous_sibling) and $element->previous_sibling->isa('PLS::Parser::Element'))
1533 4         26 {
1534 4         13 $element = $element->previous_sibling;
1535 4         15 }
1536              
1537             # Short-circuit if this is a HASH reference subscript.
1538             my $parent = $element->parent;
1539 4 100 33     11 $parent = $parent->parent if (blessed($parent) and ref $parent eq 'PLS::Parser::Element');
      66        
      33        
      100        
1540             return if ($element->type eq 'PPI::Token::Word' and blessed($parent) and $parent->isa('PLS::Parser::Element') and $parent->type eq 'PPI::Structure::Subscript');
1541              
1542             # if the cursor is on the word after an arrow, back up to the arrow so we can use any package information before it.
1543             if ( $element->type eq 'PPI::Token::Word'
1544             and blessed($element->previous_sibling)
1545             and $element->previous_sibling->isa('PLS::Parser::Element')
1546             and $element->previous_sibling->name eq '->')
1547             {
1548 2         35 $closest_operator = $element->previous_sibling;
1549             } ## end if ($element->type eq ...)
1550              
1551             if ( blessed($closest_operator)
1552 2         79 and $closest_operator->isa('PLS::Parser::Element')
1553             and $closest_operator->name eq '->'
1554             and $element->type eq 'PPI::Token::Word'
1555             and $element->parent->element == $closest_operator->parent->element)
1556 22 50 33     81 {
1557             # default to inserting after the arrow
1558 22 100       49 my $arrow_range = $closest_operator->range;
1559             my $range = {
1560 4         11 start => $arrow_range->{end},
1561 4         7 end => $arrow_range->{end}
1562 4         40 };
1563              
1564             my $filter = '';
1565              
1566 18 50 100     31 # if the next element is a word, it is likely the start of a method name,
      66        
      66        
1567             # so we want to return it as a filter. we also want the range to be that
1568 1         5 # of the next element so that we replace the word when it is selected.
1569             if ( blessed($closest_operator->next_sibling)
1570             and $closest_operator->next_sibling->isa('PLS::Parser::Element')
1571             and $closest_operator->next_sibling->type eq 'PPI::Token::Word'
1572 18         39 and $closest_operator->ppi_line_number == $closest_operator->next_sibling->ppi_line_number)
1573 18 50 33     77 {
1574 18 50 66     34 $filter = $closest_operator->next_sibling->name;
      66        
      33        
1575             $range = $closest_operator->next_sibling->range;
1576             } ## end if (blessed($closest_operator...))
1577 18 50 100     32  
      66        
      66        
1578             # if the previous element is a word, it's possibly a class name,
1579             # so we return that to use for searching for that class's methods.
1580             my $package = '';
1581              
1582 4         21 if (
1583             blessed($closest_operator->previous_sibling)
1584             and $closest_operator->previous_sibling->isa('PLS::Parser::Element')
1585 18 100 66     135 and $closest_operator->previous_sibling->type eq 'PPI::Token::Word'
      66        
      33        
      66        
1586             and ( not blessed($closest_operator->previous_sibling->previous_sibling)
1587             or not $closest_operator->previous_sibling->previous_sibling->isa('PLS::Parser::Element')
1588             or $closest_operator->previous_sibling->previous_sibling->name ne '->')
1589             )
1590             {
1591             $package = $closest_operator->previous_sibling->name;
1592 4         26 } ## end if (blessed($closest_operator...))
1593              
1594             # the 1 indicates that the current token is an arrow, due to the special logic needed.
1595             return $range, 1, $package, $filter;
1596 4         14 } ## end if (blessed($closest_operator...))
1597              
1598 4         7 # This handles the case for when there is an arrow after a variable name
1599             # but the user has not yet started typing the method name.
1600             if ( blessed($closest_operator)
1601             and $closest_operator->isa('PLS::Parser::Element')
1602             and $closest_operator->name eq '->'
1603 4 50 33     11 and blessed($closest_operator->previous_sibling)
      33        
      33        
1604             and $closest_operator->previous_sibling->isa('PLS::Parser::Element')
1605             and $closest_operator->previous_sibling->element == $element->element)
1606             {
1607             my $arrow_range = $closest_operator->range;
1608 4         51 my $range = {
1609 4         18 start => $arrow_range->{end},
1610             end => $arrow_range->{end}
1611             };
1612              
1613             return $range, 1, '', '';
1614 4         9 } ## end if (blessed($closest_operator...))
1615              
1616 4 100 33     19 # something like "Package::Name:", we just want Package::Name.
      66        
      66        
      66        
1617             if (
1618             $element->name eq ':'
1619             and blessed($element->previous_sibling)
1620             and $element->previous_sibling->isa('PLS::Parser::Element')
1621             and ( $element->previous_sibling->type eq 'PPI::Token::Word'
1622             or $element->previous_sibling->type eq 'PPI::Token::Label')
1623              
1624             # Check that there isn't another arrow before the previous word - in this case the previous word is likely NOT a package name.
1625 2         35 and ( not blessed($element->previous_sibling->previous_sibling)
1626             or not $element->previous_sibling->previous_sibling->isa('PLS::Parser::Element')
1627             or $element->previous_sibling->previous_sibling->name ne '->')
1628             )
1629 4         56 {
1630             $element = $element->previous_sibling;
1631             } ## end if ($element->name eq ...)
1632              
1633             my $range = $element->range;
1634 14 50 66     47  
      66        
      33        
      33        
      33        
1635             # look at labels as well, because a label looks like a package name before the second colon.
1636             my $package = '';
1637              
1638             if ( $element->type eq 'PPI::Token::Word'
1639             or $element->type eq 'PPI::Token::Label'
1640             or $element->element->isa('PPI::Token::Quote')
1641 0         0 or $element->element->isa('PPI::Token::QuoteLike')
1642             or $element->element->isa('PPI::Token::Regexp'))
1643             {
1644             $package = $element->name;
1645 0         0 } ## end if ($element->type eq ...)
1646              
1647 0         0 my $name = $element->name;
1648             $name =~ s/:?:$//;
1649              
1650             return $range, 0, $package, $name;
1651 14 0 33     32 } ## end sub find_word_under_cursor
      33        
      0        
      0        
      0        
      0        
1652              
1653             =head2 get_list_index
1654              
1655             Gets the index within a list where a cursor is.
1656              
1657             This is useful for determining which function parameter the cursor is on
1658             within a function call.
1659              
1660             =cut
1661              
1662             {
1663             my ($self, $list, $line, $character) = @_;
1664 0         0  
1665             return 0 if (not blessed($list) or not $list->isa('PLS::Parser::Element') or $list->type ne 'PPI::Structure::List');
1666              
1667 14         67 my $find = PPI::Find->new(sub { $_[0]->isa('PPI::Statement::Expression') });
1668             my $expr;
1669             $expr = $find->match() if $find->start($list->element);
1670 14         24  
1671             return 0 if (not blessed($expr) or not $expr->isa('PPI::Statement::Expression'));
1672 14 100 100     23  
      100        
      100        
      100        
1673             my @commas = grep { $_->isa('PPI::Token::Operator') and $_ eq ',' } $expr->schildren;
1674              
1675             return 0 unless (scalar @commas);
1676              
1677             my $param_index = -1;
1678 12         22  
1679             foreach my $index (reverse 0 .. $#commas)
1680             {
1681 14         51 my $param = $commas[$index];
1682 14         65  
1683             if ($param->column_number <= $character)
1684 14         89 {
1685             $param_index = $index;
1686             last;
1687             }
1688             } ## end foreach my $index (reverse ...)
1689              
1690             return $param_index + 1;
1691             } ## end sub get_list_index
1692              
1693             =head2 sort_imports
1694              
1695             This sorts the imports within a file. The order is:
1696              
1697             =over
1698 0     0 1    
1699             =item C<use strict> and C<use warnings>
1700 0 0 0        
      0        
1701             =item C<use parent> and C<use base>
1702 0     0      
  0            
1703 0           =item Other pragmas (excluding C<use constant>)
1704 0 0          
1705             =item Core and external imports
1706 0 0 0        
1707             =item Internal imports (from the current project)
1708 0 0          
  0            
1709             =item Constants (C<use constant>)
1710 0 0          
1711             =back
1712 0            
1713             =cut
1714 0            
1715             {
1716 0           my ($self) = @_;
1717              
1718 0 0         my $doc = $self->{document}->clone();
1719             my @installed = ExtUtils::Installed->new->modules;
1720 0            
1721 0           # Just strict and warnings - I like them to be first and in their own group
1722             my @special_pragmas;
1723              
1724             # parent and base - I like them to be after strict and warnings and in their own group.
1725 0           my @isa_pragmas;
1726              
1727             # The rest of the pragmas
1728             my @pragmas;
1729              
1730             # Group of any modules that are installed (either core or external)
1731             my @installed_modules;
1732              
1733             # Group of modules that are part of this project,
1734             # though it gets tricky if this project is also installed
1735             my @internal_modules;
1736              
1737             # Put constant pragmas at the very end of all imports
1738             my @constant_pragmas;
1739              
1740             my $insert_after;
1741              
1742             foreach my $child ($doc->children)
1743             {
1744             my $seqno;
1745             next unless ($seqno = ($child->isa('PPI::Statement::Include') .. (not $child->isa('PPI::Statement::Include') and not $child->isa('PPI::Token::Whitespace'))));
1746             last if ($seqno =~ /E0/);
1747             $insert_after = $child->previous_sibling if ($seqno eq '1');
1748              
1749             if ($child->isa('PPI::Token::Whitespace'))
1750             {
1751             $child->delete;
1752 0     0 1   next;
1753             }
1754 0            
1755 0           if ($child->pragma eq 'strict' or $child->pragma eq 'warnings')
1756             {
1757             push @special_pragmas, $child;
1758 0           }
1759             elsif ($child->pragma eq 'parent' or $child->pragma eq 'base')
1760             {
1761             push @isa_pragmas, $child;
1762             }
1763             elsif ($child->pragma eq 'constant')
1764 0           {
1765             push @constant_pragmas, $child;
1766             }
1767 0           elsif (length $child->pragma)
1768             {
1769             push @pragmas, $child;
1770             }
1771 0           else
1772             {
1773             if (Module::CoreList::is_core($child->module) or any { $child->module =~ /^\Q$_\E/ } @installed)
1774 0           {
1775             push @installed_modules, $child;
1776 0           }
1777             else
1778 0           {
1779             push @internal_modules, $child;
1780 0           }
1781 0 0 0       } ## end else [ if ($child->pragma eq ...)]
1782 0 0          
1783 0 0         $child->remove;
1784             } ## end foreach my $child ($doc->children...)
1785 0 0          
1786             @special_pragmas = _pad_imports(sort _sort_imports @special_pragmas) if (scalar @special_pragmas);
1787 0           @isa_pragmas = _pad_imports(sort _sort_imports @isa_pragmas) if (scalar @isa_pragmas);
1788 0           @pragmas = _pad_imports(sort _sort_imports @pragmas) if (scalar @pragmas);
1789             @installed_modules = _pad_imports(sort _sort_imports @installed_modules) if (scalar @installed_modules);
1790             @internal_modules = _pad_imports(sort _sort_imports @internal_modules) if (scalar @internal_modules);
1791 0 0 0       @constant_pragmas = _pad_imports(sort _sort_imports @constant_pragmas) if (scalar @constant_pragmas);
    0 0        
    0          
    0          
1792              
1793 0           # There doesn't seem to be a better way to do this other than to use this private method.
1794             $insert_after->__insert_after(@special_pragmas, @isa_pragmas, @pragmas, @installed_modules, @internal_modules, @constant_pragmas);
1795              
1796             open my $fh, '<', $self->get_full_text();
1797 0            
1798             my $lines;
1799              
1800             while (my $line = <$fh>)
1801 0           {
1802             $lines = $.;
1803             }
1804              
1805 0           return \($doc->serialize), $lines;
1806             } ## end sub sort_imports
1807              
1808             =head2 _sort_imports
1809 0 0 0 0      
  0            
1810             Determines the sorting of two imports within a block of imports.
1811 0            
1812             =cut
1813              
1814             {
1815 0           return $b->type cmp $a->type || $a->module cmp $b->module;
1816             }
1817              
1818             =head2 _pad_imports
1819 0            
1820             Adds newlines to pad the various import sections from each other and from
1821             the rest of the document.
1822 0 0          
1823 0 0         =cut
1824 0 0          
1825 0 0         {
1826 0 0         my @imports = @_;
1827 0 0          
1828             # Newlines between the imports
1829             @imports = map { $_, PPI::Token::Whitespace->new("\n") } @imports;
1830 0            
1831             # An extra newline at the end of the section
1832 0           push @imports, PPI::Token::Whitespace->new("\n");
1833              
1834 0           return @imports;
1835             } ## end sub _pad_imports
1836 0            
1837             =head2 _split_lines
1838 0            
1839             Splits a document into lines using C<$/> as the separator.
1840              
1841 0           =cut
1842              
1843             {
1844             my ($text) = @_;
1845              
1846             my $sep = $/;
1847             return split /(?<=$sep)/, $text;
1848             } ## end sub _split_lines
1849              
1850             1;