1
package Padre::Document::Perl::Lexer;
13
my $css = class_to_css($class);
15
keyword => 4, # dark green
28
'Comment' => 2, # it's good, it's green
32
'Word' => 0, # stay the black
61
my $doc = Padre::Current->document;
62
my $editor = $doc->editor;
65
# my $dp = Padre->ide->wx->main_window->{debugpane};
68
# start and end position for styling, as sent from Wx::STC
69
# the algorithm used by Wx::STC to determine what needs styling
70
# is not precise enough for our need, but is a good starting point
71
my ($start_pos, $end_pos) = @_;
74
$text, # the text that we will send to PPI for parsing
75
$start_line, # number of first line of text to parse and style
76
$end_line, # number of last line of text to parse and style
77
$styling_start_pos, # number of first character to parse and style
78
$styling_end_pos, # number of last character to parse and style
79
$line_count, # number of lines within the document
80
$last_char, # index of the last character in the file
83
# convert start and end position to start of first line and end of last line
84
# rather than starting to parse and style from the position sent by Wx::STC,
85
# we will shift the start and end position to the start of the first line and
86
# end of the last line respectively
87
$start_line = $editor->LineFromPosition($start_pos);
88
$end_line = $editor->LineFromPosition($end_pos);
89
$styling_start_pos = $editor->PositionFromLine($start_line);
90
$styling_end_pos = $editor->GetLineEndPosition($end_line);
91
$line_count = $editor->GetLineCount();
92
$last_char = $editor->GetLineEndPosition($line_count-1);
94
# basically we let PPI start parsing the text within the start and end
95
# positions we just determined, unless there is a chance that our start
96
# ro end position is within some multiline token - a quotelike expression
99
# this check is not necessary if we are on the first line of text
100
if ($start_line > 0) {
102
# get first char on the preceding line, but skip newline symbols
103
my $previous_char = $styling_start_pos-1;
104
while ($editor->GetCharAt($previous_char) == 10 or $editor->GetCharAt($previous_char) == 13) {
106
last if $previous_char <= 1;
110
if ($previous_char > 0) {
112
# get the start position of the previous token
113
# NOTE TO SELF: why did I have to decrement $previous_char again?
114
my $previous_style = $editor->GetStyleAt($previous_char--);
116
my $start_of_previous_token = $previous_char;
118
while ($editor->GetStyleAt($start_of_previous_token) == $previous_style) {
119
$start_of_previous_token--;
120
last if $start_of_previous_token <= 0;
122
$start_of_previous_token++;
124
# get the text of the previous token
125
my $prev_token_text = $editor->GetTextRange($start_of_previous_token, $styling_start_pos-1);
126
my $prev_ppi_doc = PPI::Document->new( \$prev_token_text );
129
# check if the previous token is a quotelike
130
my @tokens = $prev_ppi_doc->tokens;
131
my $prev_token = $tokens[-1];
133
if ( $prev_token->isa("PPI::Token::Quote")
134
or $prev_token->isa("PPI::Token::QuoteLike")
135
or $prev_token->isa("PPI::Token::Regexp")
137
# check if the quotelike token is complete
138
if ( !Text::Balanced::extract_quotelike($prev_token->content) ) {
139
# if the token beore the text we are to parse and style
140
# is an unfinished quotelike expression, include it
141
# in the text to parse and style
142
$styling_start_pos = $start_of_previous_token;
144
} elsif ( $prev_token->isa("PPI::Token::Pod") ) {
146
$styling_start_pos = $start_of_previous_token;
152
# ditto for the token after
153
if ($styling_end_pos < $last_char) {
154
my $next_char = $styling_end_pos + 1;
155
while ($editor->GetCharAt($next_char) == 10 or $editor->GetCharAt($next_char) == 13) {
157
last if $next_char >= $last_char;
161
if ($next_char < $last_char) {
163
my $next_style = $editor->GetStyleAt($next_char);
165
my $end_of_next_token = $next_char;
167
while ($editor->GetStyleAt($end_of_next_token) == $next_style) {
168
$end_of_next_token++;
169
last if $end_of_next_token == $last_char;
171
$end_of_next_token--;
174
my $next_token_text = $editor->GetTextRange($styling_end_pos + 1, $end_of_next_token);
175
my $next_ppi_doc = PPI::Document->new( \$next_token_text );
178
my @tokens = $next_ppi_doc->tokens;
179
my $next_token = $tokens[0];
181
if ( $next_token->isa("PPI::Token::Quote")
182
or $next_token->isa("PPI::Token::QuoteLike")
183
or $next_token->isa("PPI::Token::Regexp")
184
or $next_token->isa("PPI::Token::Pod")
186
$styling_end_pos = $end_of_next_token;
192
# check if we have to style it all
193
if ($end_pos and $doc->{_is_colorized}) {
194
$text = $editor->GetTextRange($styling_start_pos, $styling_end_pos);
195
clear_style($styling_start_pos, $styling_end_pos);
205
# now that we have determined the proper starting position,
206
# feed the text to PPI
207
my $ppi_doc = PPI::Document->new( \$text );
210
# $dp->AppendText($text);
211
# $dp->AppendText("\n" . '='x10 . "\n");
212
# my $dumper = PPI::Dumper->new($ppi_doc);
213
# $dp->AppendText($dumper->string);
216
my @tokens = $ppi_doc->tokens;
217
$ppi_doc->index_locations;
219
my (@prepared_extra_tokens, @prepared_tokens);
221
# check to see if the last token is quotelike or pod
222
my $last_token = $tokens[-1];
223
if ( $last_token->isa("PPI::Token::Quote")
224
or $last_token->isa("PPI::Token::QuoteLike")
225
or $last_token->isa("PPI::Token::Regexp") ) {
226
if ( !Text::Balanced::extract_quotelike($last_token->content) ) {
228
# get the position at which this token starts
229
my ($row, $rowchar, $col) = @{ $last_token->location };
230
my $new_start_pos = ($editor->PositionFromLine($start_line+$row-1)+ $rowchar-1);
232
# get the line at which it ends
233
my $token_end_line = ($editor->LineFromPosition($new_start_pos + $last_token->length));
235
# get the next up to 50 lines
236
my $new_end_pos = $editor->GetLineEndPosition($token_end_line + 50);
238
if ($new_end_pos > $new_start_pos) {
239
my $extra_text = $editor->GetTextRange($new_start_pos, $new_end_pos);
240
clear_style($new_start_pos, $new_end_pos);
242
# parse from start of this token
243
my $extra_ppi_doc = PPI::Document->new( \$extra_text );
244
my $dumper = PPI::Dumper->new($extra_ppi_doc);
248
my @extra_tokens = $extra_ppi_doc->tokens;
249
$extra_ppi_doc->index_locations;
251
@prepared_extra_tokens = prepare_tokens($new_start_pos, @extra_tokens);
253
# remove the last token since it is included in the extra tokens
258
} elsif ($last_token->isa("PPI::Token::Pod")) {
259
# get the position at which this token starts
260
my ($row, $rowchar, $col) = @{ $last_token->location };
261
my $token_start_line = $start_line+$row-1;
262
my $new_start_pos = ($editor->PositionFromLine($token_start_line)+ $rowchar-1);
264
# get the line at which it ends
265
my $token_end_line = ($editor->LineFromPosition($new_start_pos + $last_token->length));
267
# if we are in the first line of pod, start searching for the next line;
268
# otherwise start searching from the last line of the pod token
269
my $start_search_for_pod_end = $token_end_line;
270
$start_search_for_pod_end++ if $token_end_line == $token_start_line;
272
my $pod_end = $start_search_for_pod_end;
274
while (my $pod_last_line = $editor->GetLine($pod_end)) {
275
last if $pod_last_line =~ /^=cut\s/;
279
my $extra_text = $editor->GetTextRange($new_start_pos, $editor->GetLineEndPosition($pod_end));
280
clear_style($new_start_pos, $editor->GetLineEndPosition($pod_end));
282
# parse from start of this token
283
my $extra_ppi_doc = PPI::Document->new( \$extra_text );
285
my @extra_tokens = $extra_ppi_doc->tokens;
286
$extra_ppi_doc->index_locations;
288
@prepared_extra_tokens = prepare_tokens($new_start_pos, $extra_tokens[0]);
292
@prepared_tokens = prepare_tokens($styling_start_pos, @tokens);
294
do_styling(@prepared_tokens, @prepared_extra_tokens);
299
my ($offset, @tokens) = @_;
301
my $doc = Padre::Current->document;
302
my $editor = $doc->editor;
306
my $start_line = $editor->LineFromPosition($offset);
307
my $offset_from_start_line = ($offset - $editor->PositionFromLine($start_line));
309
foreach my $t (@tokens) {
310
my ($row, $rowchar, $col) = @{ $t->location };
312
if ($row == 1) {$rowchar += $offset_from_start_line;}
314
my $start = ($editor->PositionFromLine($start_line+$row-1) + $rowchar -1);
315
my $content = $t->content;
316
my $new_lines = ($content =~ s/\n/\n/gs);
319
length => ($t->length + $new_lines),
320
color => class_to_color($t),
323
push @prepared_tokens, \%token;
326
return @prepared_tokens;
330
my ($styling_start_pos, $styling_end_pos) = @_;
332
my $doc = Padre::Current->document;
333
my $editor = $doc->editor;
335
for my $i ( 0..31 ) {
336
$editor->StartStyling($styling_start_pos, $i);
337
$editor->SetStyling($styling_end_pos-$styling_start_pos, 0);
341
sub do_full_styling {
342
my $doc = Padre::Current->document;
343
my $editor = $doc->editor;
346
my $text = $doc->text_get;
348
my $ppi_doc = PPI::Document->new( \$text );
349
my @tokens = $ppi_doc->tokens;
350
$ppi_doc->index_locations;
351
my @prepared_tokens = prepare_tokens(1, @tokens);
352
do_styling(@prepared_tokens);
353
$doc->{_is_colorized} = 1;
357
my $doc = Padre::Current->document;
358
my $editor = $doc->editor;
361
$editor->StartStyling($t->{start}, $t->{color});
362
$editor->SetStyling($t->{length}, $t->{color});
369
if ( $Token->isa('PPI::Token::Word') ) {
370
# There are some words we can be very confident are
371
# being used as keywords
372
unless ( $Token->snext_sibling and $Token->snext_sibling->content eq '=>' ) {
373
if ( $Token->content =~ /^(?:sub|return)$/ ) {
375
} elsif ( $Token->content =~ /^(?:undef|shift|defined|bless)$/ ) {
380
if ( $Token->previous_sibling and $Token->previous_sibling->content eq '->' ) {
381
if ( $Token->content =~ /^(?:new)$/ ) {
386
if ( $Token->parent->isa('PPI::Statement::Include') ) {
387
if ( $Token->content =~ /^(?:use|no)$/ ) {
390
if ( $Token->content eq $Token->parent->pragma ) {
393
} elsif ( $Token->parent->isa('PPI::Statement::Variable') ) {
394
if ( $Token->content =~ /^(?:my|local|our)$/ ) {
397
} elsif ( $Token->parent->isa('PPI::Statement::Compond') ) {
398
if ( $Token->content =~ /^(?:if|else|elsif|unless|for|foreach|while|my)$/ ) {
401
} elsif ( $Token->parent->isa('PPI::Statement::Package') ) {
402
if ( $Token->content eq 'package' ) {
405
} elsif ( $Token->parent->isa('PPI::Statement::Scheduled') ) {
411
my $css = ref $Token;
418
# Copyright 2008-2009 The Padre development team as listed in Padre.pm.
420
# This program is free software; you can redistribute it and/or
421
# modify it under the same terms as Perl 5 itself.