File Coverage

blib/lib/PLS/Parser/Document.pm
Criterion Covered Total %
statement 270 777 34.7
branch 99 434 22.8
condition 141 364 38.7
subroutine 49 93 52.6
pod 29 32 90.6
total 588 1700 34.5


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