| Syntax-Highlight-Engine-Kate documentation | Contained in the Syntax-Highlight-Engine-Kate distribution. |
Syntax::Highlight::Engine::Kate::Template - a template for syntax highlighting plugins
Syntax::Highlight::Engine::Kate::Template is a framework to assist authors of plugin modules. All methods to provide highlighting to the Syntax::Highlight::Engine::Kate module are there, Just no syntax definitions and callbacks. An instance of Syntax::Highlight::Engine::Kate::Template should never be created, it's meant to be sub classed only.
Sets and returns a reference to the attributes hash.
Sets and returns the basecontext instance variable. This is the context that is used when highlighting starts.
Puts $cap in the first element of the stack, the current context. Used when the context is dynamic.
Returns the $num'th element that was captured in the current context.
If $mode is specified, $string should only be one character long and numeric. capturedParse will return the Nth captured element of the current context.
If $mode is not specified, all occurences of %[1-9] will be replaced by the captured element of the current context.
returns the column position in the line that is currently highlighted.
Sets and returns a reference to the contextdata hash.
returns the value of several context options. $item can be callback, attribute, lineending, linebeginning, fallthrough.
Called by the plugins after a test succeeds. if $context has following values:
#pop returns to the previous context, removes to top item in the stack. Can
also be specified as #pop#pop etc.
#stay does nothing.
##.... Switches to the plugin specified in .... and assumes it's basecontext.
.... Swtiches to the context specified in ....
Sets and returns a string that is a regular expression for detecting deliminators.
Returns a reference to the Syntax::Highlight::Engine::Kate module that created this plugin.
returns true if the current line did not contain a non-spatial character so far and the first character in $string is also a spatial character.
sets and returns the instance variable format_table. See also the option format_table
highlights $text. It does so by selecting the proper callback from the commands hash and invoke it. It will do so untill $text has been reduced to an empty string. returns a paired list of snippets of text and the attribute with which they should be highlighted.
highlights $text and reformats it using the format_table and substitutions
Includes the plugin for $language in the highlighting.
Includes the plugin for $language in the highlighting.
Sets and returns the keywordscase instance variable.
return the last character that was processed.
returns true if the last character processed was a deliminator.
returns the string of text in the current line that has been processed so far,
returns true if processing is currently at the beginning of a line.
Adds a list to the 'lists' hash.
sets and returns the instance variable 'lists'.
sets and returns the instance variable 'out'.
Called by every one of the test methods below. If the test matches, it will do a couple of subtests. If $column is a defined numerical value it will test if the process is at the requested column. If $firnonspace is true, it will test this also. Ig it is not a look ahead and all tests are passed, $match is then parsed and removed from $$text.
Returns a reference to a plugin object for the specified language. Creating an instance if needed.
Resets the highlight engine to a fresh state, does not change the syntx.
Contains the current snippet of text that will have one attribute. The moment the attribute changes it will be parsed.
appends $string to the current snippet.
Sets and returns the used attribute.
Forces the current snippet to be parsed.
If attribute is defined and differs from the current attribute it does a snippetForce and sets the current attribute to $attribute. Then it does a snippetAppend of $text
sets and returns the instance variable 'stack', a reference to an array
retrieves the element that is on top of the stack, decrements stacksize by 1.
puts $tagname on top of the stack, increments stacksize by 1
Retrieves the element that is on top of the stack.
Compares two lists, \@state and the stack. returns true if they match.
Returns a list containing the entire stack.
Accepts @list as the current stack.
sets and returns a reference to the substitutions hash.
The methods below all return a boolean value.
All the people who wrote Kate and the syntax highlight xml files.
This module is written and maintained by:
Hans Jeuken < haje at toneel dot demon dot nl >
Copyright (c) 2006 by Hans Jeuken, all rights reserved.
You may freely distribute and/or modify this module under same terms as Perl itself
Synax::Highlight::Engine::Kate http:://www.kate-editor.org
| Syntax-Highlight-Engine-Kate documentation | Contained in the Syntax-Highlight-Engine-Kate distribution. |
# Copyright (c) 2006 Hans Jeuken. All rights reserved. # This program is free software; you can redistribute it and/or # modify it under the same terms as Perl itself. package Syntax::Highlight::Engine::Kate::Template; our $VERSION = '0.06'; use strict; use Carp qw(cluck); use Data::Dumper; #my $regchars = '\\^.$|()[]*+?'; sub new { my $proto = shift; my $class = ref($proto) || $proto; my %args = (@_); my $debug = delete $args{'debug'}; unless (defined($debug)) { $debug = 0 }; my $substitutions = delete $args{'substitutions'}; unless (defined($substitutions)) { $substitutions = {} }; my $formattable = delete $args{'format_table'}; unless (defined($formattable)) { $formattable = {} }; my $engine = delete $args{'engine'}; my $self = {}; $self->{'attributes'} = {}, $self->{'captured'} = []; $self->{'contextdata'} = {}; $self->{'basecontext'} = ''; $self->{'debug'} = $debug; $self->{'deliminators'} = ''; $self->{'engine'} = ''; $self->{'format_table'} = $formattable; $self->{'keywordcase'} = 1; $self->{'lastchar'} = ''; $self->{'linesegment'} = ''; $self->{'lists'} = {}; $self->{'linestart'} = 1; $self->{'out'} = []; $self->{'plugins'} = {}; $self->{'snippet'} = ''; $self->{'snippetattribute'} = ''; $self->{'stack'} = []; $self->{'substitutions'} = $substitutions; bless ($self, $class); unless (defined $engine) { $engine = $self }; $self->engine($engine); $self->initialize; return $self; } sub attributes { my $self = shift; if (@_) { $self->{'attributes'} = shift; }; return $self->{'attributes'}; } sub basecontext { my $self = shift; if (@_) { $self->{'basecontext'} = shift; }; return $self->{'basecontext'}; } sub captured { my ($self, $c) = @_; if (defined($c)) { my $t = $self->engine->stackTop; my $n = 0; my @o = (); while (defined($c->[$n])) { push @o, $c->[$n]; $n ++; } if (@o) { $t->[2] = \@o; } }; } sub capturedGet { my ($self, $num) = @_; my $s = $self->engine->stack; if (defined($s->[1])) { my $c = $s->[1]->[2]; $num --; if (defined($c)) { if (defined($c->[$num])) { my $r = $c->[$num]; return $r; } else { warn "capture number $num not defined"; } } else { warn "dynamic substitution is called for but nothing to substitute\n"; return undef; } } else { warn "no parent context to take captures from"; } } #sub captured { # my $self = shift; # if (@_) { # $self->{'captured'} = shift; ## print Dumper($self->{'captured'}); # }; # return $self->{'captured'} ## my ($self, $c) = @_; ## if (defined($c)) { ## my $t = $self->engine->stackTop; ## my $n = 0; ## my @o = (); ## while (defined($c->[$n])) { ## push @o, $c->[$n]; ## $n ++; ## } ## if (@o) { ## $t->[2] = \@o; ## } ## }; #} # #sub capturedGet { # my ($self, $num) = @_; # my $s = $self->captured; # if (defined $s) { # $num --; # if (defined($s->[$num])) { # return $s->[$num]; # } else { # $self->logwarning("capture number $num not defined"); # } # } else { # $self->logwarning("dynamic substitution is called for but nothing to substitute"); # return undef; # } #} sub capturedParse { my ($self, $string, $mode) = @_; my $s = ''; if (defined($mode)) { if ($string =~ s/^(\d)//) { $s = $self->capturedGet($1); if ($string ne '') { $self->logwarning("character class is longer then 1 character, ignoring the rest"); } } } else { while ($string ne '') { if ($string =~ s/^([^\%]*)\%(\d)//) { my $r = $self->capturedGet($2); if ($r ne '') { $s = $s . $1 . $r } else { $s = $s . $1 . '%' . $2; $self->logwarning("target is an empty string"); } } else { $string =~ s/^(.)//; $s = "$s$1"; } } } return $s; } sub column { my $self = shift; return length($self->linesegment); } sub contextdata { my $self = shift; if (@_) { $self->{'contextdata'} = shift; }; return $self->{'contextdata'}; } sub contextInfo { my ($self, $context, $item) = @_; if (exists $self->contextdata->{$context}) { my $c = $self->contextdata->{$context}; if (exists $c->{$item}) { return $c->{$item} } else { return undef; } } else { $self->logwarning("undefined context '$context'"); return undef; } } sub contextParse { my ($self, $plug, $context) = @_; if ($context =~ /^#pop/i) { while ($context =~ s/#pop//i) { $self->stackPull; } } elsif ($context =~ /^#stay/i) { #don't do anything } elsif ($context =~ /^##(.+)/) { my $new = $self->pluginGet($1); $self->stackPush([$new, $new->basecontext]); } else { $self->stackPush([$plug, $context]); } } sub debug { my $self = shift; if (@_) { $self->{'debug'} = shift; }; return $self->{'debug'}; } sub debugTest { my $self = shift; if (@_) { $self->{'debugtest'} = shift; }; return $self->{'debugtest'}; } sub deliminators { my $self = shift; if (@_) { $self->{'deliminators'} = shift; }; return $self->{'deliminators'}; } sub engine { my $self = shift; if (@_) { $self->{'engine'} = shift; }; return $self->{'engine'}; } sub firstnonspace { my ($self, $string) = @_; my $line = $self->linesegment; if (($line =~ /^\s*$/) and ($string =~ /^[^\s]/)) { return 1 } return '' } sub formatTable { my $self = shift; if (@_) { $self->{'format_table'} = shift; }; return $self->{'format_table'}; } sub highlight { my ($self, $text) = @_; $self->snippet(''); my $out = $self->out; @$out = (); while ($text ne '') { my $top = $self->stackTop; if (defined($top)) { my ($plug, $context) = @$top; if ($text =~ s/^(\n)//) { $self->snippetForce; my $e = $plug->contextInfo($context, 'lineending'); if (defined($e)) { $self->contextParse($plug, $e) } my $attr = $plug->attributes->{$plug->contextInfo($context, 'attribute')}; $self->snippetParse($1, $attr); $self->snippetForce; $self->linesegment(''); my $b = $plug->contextInfo($context, 'linebeginning'); if (defined($b)) { $self->contextParse($plug, $b) } } else { my $sub = $plug->contextInfo($context, 'callback'); my $result = &$sub($plug, \$text); unless($result) { my $f = $plug->contextInfo($context, 'fallthrough'); if (defined($f)) { $self->contextParse($plug, $f); } else { $text =~ s/^(.)//; my $attr = $plug->attributes->{$plug->contextInfo($context, 'attribute')}; $self->snippetParse($1, $attr); } } } } else { push @$out, length($text), 'Normal'; $text = ''; } } $self->snippetForce; return @$out; } sub highlightText { my ($self, $text) = @_; my $res = ''; my @hl = $self->highlight($text); while (@hl) { my $f = shift @hl; my $t = shift @hl; unless (defined($t)) { $t = 'Normal' } my $s = $self->substitutions; my $rr = ''; while ($f ne '') { my $k = substr($f , 0, 1); $f = substr($f, 1, length($f) -1); if (exists $s->{$k}) { $rr = $rr . $s->{$k} } else { $rr = $rr . $k; } } my $rt = $self->formatTable; if (exists $rt->{$t}) { my $o = $rt->{$t}; $res = $res . $o->[0] . $rr . $o->[1]; } else { $res = $res . $rr; $self->logwarning("undefined format tag '$t'"); } } return $res; } sub includePlugin { my ($self, $language, $text) = @_; my $eng = $self->engine; my $plug = $eng->pluginGet($language); if (defined($plug)) { my $context = $plug->basecontext; my $call = $plug->contextInfo($context, 'callback'); if (defined($call)) { return &$call($plug, $text); } else { $self->logwarning("cannot find callback for context '$context'"); } } return 0; } sub includeRules { my ($self, $context, $text) = @_; my $call = $self->contextInfo($context, 'callback'); if (defined($call)) { return &$call($self, $text); } else { $self->logwarning("cannot find callback for context '$context'"); } return 0; } sub initialize { my $self = shift; if ($self->engine eq $self) { $self->stack([[$self, $self->basecontext]]); } } sub keywordscase { my $self = shift; if (@_) { $self->{'keywordcase'} = shift; } return $self->{'keywordscase'} } sub languagePlug { my ($cw, $name) = @_; my %numb = ( '1' => 'One', '2' => 'Two', '3' => 'Three', '4' => 'Four', '5' => 'Five', '6' => 'Six', '7' => 'Seven', '8' => 'Eight', '9' => 'Nine', '0' => 'Zero', ); if ($name =~ s/^(\d)//) { $name = $numb{$1} . $name; } $name =~ s/\.//; $name =~ s/\+/plus/g; $name =~ s/\-/minus/g; $name =~ s/#/dash/g; $name =~ s/[^0-9a-zA-Z]/_/g; $name =~ s/__/_/g; $name =~ s/_$//; $name = ucfirst($name); return $name; } sub lastchar { my $self = shift; my $l = $self->linesegment; if ($l eq '') { return "\n" } #last character was a newline return substr($l, length($l) - 1, 1); } sub lastcharDeliminator { my $self = shift; my $deliminators = '\s|\~|\!|\%|\^|\&|\*|\+|\(|\)|-|=|\{|\}|\[|\]|:|;|<|>|,|\\|\||\.|\?|\/'; if ($self->linestart or ($self->lastchar =~ /$deliminators/)) { return 1; } return ''; } sub linesegment { my $self = shift; if (@_) { $self->{'linesegment'} = shift; }; return $self->{'linesegment'}; } sub linestart { my $self = shift; if ($self->linesegment eq '') { return 1 } return ''; } sub lists { my $self = shift; if (@_) { $self->{'lists'} = shift; } return $self->{'lists'} } sub out { my $self = shift; if (@_) { $self->{'out'} = shift; } return $self->{'out'}; } sub listAdd { my $self = shift; my $listname = shift; my $lst = $self->lists; if (@_) { my @l = reverse sort @_; $lst->{$listname} = \@l; } else { $lst->{$listname} = []; } } sub logwarning { my ($self, $warning) = @_; my $top = $self->engine->stackTop; if (defined $top) { my $lang = $top->[0]->language; my $context = $top->[1]; $warning = "$warning\n Language => $lang, Context => $context\n"; } else { $warning = "$warning\n STACK IS EMPTY: PANIC\n" } cluck($warning); } sub parseResult { my ($self, $text, $string, $lahead, $column, $fnspace, $context, $attr) = @_; my $eng = $self->engine; if ($fnspace) { unless ($eng->firstnonspace($$text)) { return '' } } if (defined($column)) { if ($column ne $eng->column) { return ''; } } unless ($lahead) { $$text = substr($$text, length($string)); my $r; unless (defined($attr)) { my $t = $eng->stackTop; my ($plug, $ctext) = @$t; $r = $plug->attributes->{$plug->contextInfo($ctext, 'attribute')}; } else { $r = $self->attributes->{$attr}; } $eng->snippetParse($string, $r); } $eng->contextParse($self, $context); return 1 } sub pluginGet { my ($self, $language) = @_; my $plugs = $self->{'plugins'}; unless (exists($plugs->{$language})) { my $modname = 'Syntax::Highlight::Engine::Kate::' . $self->languagePlug($language); unless (defined($modname)) { $self->logwarning("no valid module found for language '$language'"); return undef; } my $plug; eval "use $modname; \$plug = new $modname(engine => \$self);"; if (defined($plug)) { $plugs->{$language} = $plug; } else { $self->logwarning("cannot create plugin for language '$language'\n$@"); } } if (exists($plugs->{$language})) { return $plugs->{$language}; } return undef; } sub reset { my $self = shift; $self->stack([[$self, $self->basecontext]]); $self->out([]); $self->snippet(''); } sub snippet { my $self = shift; if (@_) { $self->{'snippet'} = shift; } return $self->{'snippet'}; } sub snippetAppend { my ($self, $ch) = @_; return if not defined $ch; $self->{'snippet'} = $self->{'snippet'} . $ch; if ($ch ne '') { $self->linesegment($self->linesegment . $ch); } return; } sub snippetAttribute { my $self = shift; if (@_) { $self->{'snippetattribute'} = shift; } return $self->{'snippetattribute'}; } sub snippetForce { my $self = shift; my $parse = $self->snippet; if ($parse ne '') { my $out = $self->{'out'}; push(@$out, $parse, $self->snippetAttribute); $self->snippet(''); } } sub snippetParse { my $self = shift; my $snip = shift; my $attr = shift; if ((defined $attr) and ($attr ne $self->snippetAttribute)) { $self->snippetForce; $self->snippetAttribute($attr); } $self->snippetAppend($snip); } sub stack { my $self = shift; if (@_) { $self->{'stack'} = shift; } return $self->{'stack'}; } sub stackPush { my ($self, $val) = @_; my $stack = $self->stack; unshift(@$stack, $val); } sub stackPull { my ($self, $val) = @_; my $stack = $self->stack; return shift(@$stack); } sub stackTop { my $self = shift; return $self->stack->[0]; } sub stateCompare { my ($self, $state) = @_; my $h = [ $self->stateGet ]; my $equal = 0; if (Dumper($h) eq Dumper($state)) { $equal = 1 }; return $equal; } sub stateGet { my $self = shift; my $s = $self->stack; return @$s; } sub stateSet { my $self = shift; my $s = $self->stack; @$s = (@_); } sub substitutions { my $self = shift; if (@_) { $self->{'substitutions'} = shift; } return $self->{'substitutions'}; } sub testAnyChar { my $self = shift; my $text = shift; my $string = shift; my $insensitive = shift; my $test = substr($$text, 0, 1); my $bck = $test; if ($insensitive) { $string = lc($string); $test = lc($test); } if (index($string, $test) > -1) { return $self->parseResult($text, $bck, @_); } return '' } sub testDetectChar { my $self = shift; my $text = shift; my $char = shift; my $insensitive = shift; my $dyn = shift; if ($dyn) { $char = $self->capturedParse($char, 1); } my $test = substr($$text, 0, 1); my $bck = $test; if ($insensitive) { $char = lc($char); $test = lc($test); } if ($char eq $test) { return $self->parseResult($text, $bck, @_); } return '' } sub testDetect2Chars { my $self = shift; my $text = shift; my $char = shift; my $char1 = shift; my $insensitive = shift; my $dyn = shift; if ($dyn) { $char = $self->capturedParse($char, 1); $char1 = $self->capturedParse($char1, 1); } my $string = $char . $char1; my $test = substr($$text, 0, 2); my $bck = $test; if ($insensitive) { $string = lc($string); $test = lc($test); } if ($string eq $test) { return $self->parseResult($text, $bck, @_); } return '' } sub testDetectIdentifier { my $self = shift; my $text = shift; if ($$text =~ /^([a-zA-Z_][a-zA-Z0-9_]+)/) { return $self->parseResult($text, $1, @_); } return '' } sub testDetectSpaces { my $self = shift; my $text = shift; if ($$text =~ /^([\\040|\\t]+)/) { return $self->parseResult($text, $1, @_); } return '' } sub testFloat { my $self = shift; my $text = shift; if ($self->engine->lastcharDeliminator) { if ($$text =~ /^((?=\.?\d)\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?)/) { return $self->parseResult($text, $1, @_); } } return '' } sub testHlCChar { my $self = shift; my $text = shift; if ($$text =~ /^('.')/) { return $self->parseResult($text, $1, @_); } return '' } sub testHlCHex { my $self = shift; my $text = shift; if ($self->engine->lastcharDeliminator) { if ($$text =~ /^(0x[0-9a-fA-F]+)/) { return $self->parseResult($text, $1, @_); } } return '' } sub testHlCOct { my $self = shift; my $text = shift; if ($self->engine->lastcharDeliminator) { if ($$text =~ /^(0[0-7]+)/) { return $self->parseResult($text, $1, @_); } } return '' } sub testHlCStringChar { my $self = shift; my $text = shift; if ($$text =~ /^(\\[a|b|e|f|n|r|t|v|'|"|\?])/) { return $self->parseResult($text, $1, @_); } if ($$text =~ /^(\\x[0-9a-fA-F][0-9a-fA-F]?)/) { return $self->parseResult($text, $1, @_); } if ($$text =~ /^(\\[0-7][0-7]?[0-7]?)/) { return $self->parseResult($text, $1, @_); } return '' } sub testInt { my $self = shift; my $text = shift; if ($self->engine->lastcharDeliminator) { if ($$text =~ /^([+-]?\d+)/) { return $self->parseResult($text, $1, @_); } } return '' } sub testKeyword { my $self = shift; my $text = shift; my $list = shift; my $eng = $self->engine; my $deliminators = $self->deliminators; if (($eng->lastcharDeliminator) and ($$text =~ /^([^$deliminators]+)/)) { my $match = $1; my $l = $self->lists->{$list}; if (defined($l)) { my @list = @$l; my @rl = (); unless ($self->keywordscase) { @rl = grep { (lc($match) eq lc($_)) } @list; } else { @rl = grep { ($match eq $_) } @list; } if (@rl) { return $self->parseResult($text, $match, @_); } } else { $self->logwarning("list '$list' is not defined, failing test"); } } return '' } sub testLineContinue { my $self = shift; my $text = shift; my $lahead = shift; if ($lahead) { if ($$text =~ /^\\\n/) { $self->parseResult($text, "\\", $lahead, @_); return 1; } } else { if ($$text =~ s/^(\\)(\n)/$2/) { return $self->parseResult($text, "\\", $lahead, @_); } } return '' } sub testRangeDetect { my $self = shift; my $text = shift; my $char = shift; my $char1 = shift; my $insensitive = shift; my $string = "$char\[^$char1\]+$char1"; return $self->testRegExpr($text, $string, $insensitive, 0, @_); } sub testRegExpr { my $self = shift; my $text = shift; my $reg = shift; my $insensitive = shift; my $dynamic = shift; if ($dynamic) { $reg = $self->capturedParse($reg); } my $eng = $self->engine; if ($reg =~ s/^\^//) { unless ($eng->linestart) { return ''; } } elsif ($reg =~ s/^\\(b)//i) { my $lastchar = $self->engine->lastchar; if ($1 eq 'b') { if ($lastchar =~ /\w/) { return '' } } else { if ($lastchar =~ /\W/) { return '' } } } # $reg = "^($reg)"; $reg = "^$reg"; my $pos; # my @cap = (); my $sample = $$text; if ($insensitive) { if ($sample =~ /$reg/ig) { $pos = pos($sample); # @cap = ($1, $2, $3, $4, $5, $6, $7, $8, $9); # my @cap = (); if ($#-) { no strict 'refs'; my @cap = map {$$_} 1 .. $#-; $self->captured(\@cap) } # my $r = 1; # my $c = 1; # my @cap = (); # while ($r) { # eval "if (defined\$$c) { push \@cap, \$$c } else { \$r = 0 }"; # $c ++; # } # if (@cap) { $self->captured(\@cap) }; } } else { if ($sample =~ /$reg/g) { $pos = pos($sample); # @cap = ($1, $2, $3, $4, $5, $6, $7, $8, $9); # my @cap = (); if ($#-) { no strict 'refs'; my @cap = map {$$_} 1 .. $#-; $self->captured(\@cap); } # my $r = 1; # my $c = 1; # my @cap = (); # while ($r) { # eval "if (defined\$$c) { push \@cap, \$$c } else { \$r = 0 }"; # $c ++; # } # if (@cap) { $self->captured(\@cap) }; } } if (defined($pos) and ($pos > 0)) { my $string = substr($$text, 0, $pos); return $self->parseResult($text, $string, @_); } return '' } sub testStringDetect { my $self = shift; my $text = shift; my $string = shift; my $insensitive = shift; my $dynamic = shift; if ($dynamic) { $string = $self->capturedParse($string); } my $test = substr($$text, 0, length($string)); my $bck = $test; if ($insensitive) { $string = lc($string); $test = lc($test); } if ($string eq $test) { return $self->parseResult($text, $bck, @_); } return '' } 1; __END__ =cut