File Coverage

blib/lib/PLS/Parser/Document.pm
Criterion Covered Total %
statement 275 795 34.5
branch 99 438 22.6
condition 141 364 38.7
subroutine 50 94 53.1
pod 29 32 90.6
total 594 1723 34.4


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