1 # Copyright (c) 2006 Hans Jeuken. All rights reserved.
2 # This program is free software; you can redistribute it and/or
3 # modify it under the same terms as Perl itself.
5 package Syntax::Highlight::Engine::Kate::Template;
13 #my $regchars = '\\^.$|()[]*+?';
17 my $class = ref($proto) || $proto;
20 my $debug = delete $args{'debug'};
21 unless (defined($debug)) { $debug = 0 };
22 my $substitutions = delete $args{'substitutions'};
23 unless (defined($substitutions)) { $substitutions = {} };
24 my $formattable = delete $args{'format_table'};
25 unless (defined($formattable)) { $formattable = {} };
26 my $engine = delete $args{'engine'};
29 $self->{'attributes'} = {},
30 $self->{'captured'} = [];
31 $self->{'contextdata'} = {};
32 $self->{'basecontext'} = '';
33 $self->{'debug'} = $debug;
34 $self->{'deliminators'} = '';
35 $self->{'engine'} = '';
36 $self->{'format_table'} = $formattable;
37 $self->{'keywordcase'} = 1;
38 $self->{'lastchar'} = '';
39 $self->{'linesegment'} = '';
40 $self->{'lists'} = {};
41 $self->{'linestart'} = 1;
43 $self->{'plugins'} = {};
44 $self->{'snippet'} = '';
45 $self->{'snippetattribute'} = '';
46 $self->{'stack'} = [];
47 $self->{'substitutions'} = $substitutions;
48 bless ($self, $class);
49 unless (defined $engine) { $engine = $self };
50 $self->engine($engine);
57 if (@_) { $self->{'attributes'} = shift; };
58 return $self->{'attributes'};
63 if (@_) { $self->{'basecontext'} = shift; };
64 return $self->{'basecontext'};
70 my $t = $self->engine->stackTop;
73 while (defined($c->[$n])) {
84 my ($self, $num) = @_;
85 my $s = $self->engine->stack;
86 if (defined($s->[1])) {
90 if (defined($c->[$num])) {
94 warn "capture number $num not defined";
97 warn "dynamic substitution is called for but nothing to substitute\n";
101 warn "no parent context to take captures from";
108 # $self->{'captured'} = shift;
109 ## print Dumper($self->{'captured'});
111 # return $self->{'captured'}
112 ## my ($self, $c) = @_;
113 ## if (defined($c)) {
114 ## my $t = $self->engine->stackTop;
117 ## while (defined($c->[$n])) {
118 ## push @o, $c->[$n];
128 # my ($self, $num) = @_;
129 # my $s = $self->captured;
132 # if (defined($s->[$num])) {
135 # $self->logwarning("capture number $num not defined");
138 # $self->logwarning("dynamic substitution is called for but nothing to substitute");
144 my ($self, $string, $mode) = @_;
146 if (defined($mode)) {
147 if ($string =~ s/^(\d)//) {
148 $s = $self->capturedGet($1);
150 $self->logwarning("character class is longer then 1 character, ignoring the rest");
154 while ($string ne '') {
155 if ($string =~ s/^([^\%]*)\%(\d)//) {
156 my $r = $self->capturedGet($2);
160 $s = $s . $1 . '%' . $2;
161 $self->logwarning("target is an empty string");
174 return length($self->linesegment);
179 if (@_) { $self->{'contextdata'} = shift; };
180 return $self->{'contextdata'};
184 my ($self, $context, $item) = @_;
185 if (exists $self->contextdata->{$context}) {
186 my $c = $self->contextdata->{$context};
187 if (exists $c->{$item}) {
193 $self->logwarning("undefined context '$context'");
199 my ($self, $plug, $context) = @_;
200 if ($context =~ /^#pop/i) {
201 while ($context =~ s/#pop//i) {
204 } elsif ($context =~ /^#stay/i) {
206 } elsif ($context =~ /^##(.+)/) {
207 my $new = $self->pluginGet($1);
208 $self->stackPush([$new, $new->basecontext]);
210 $self->stackPush([$plug, $context]);
216 if (@_) { $self->{'debug'} = shift; };
217 return $self->{'debug'};
222 if (@_) { $self->{'debugtest'} = shift; };
223 return $self->{'debugtest'};
228 if (@_) { $self->{'deliminators'} = shift; };
229 return $self->{'deliminators'};
234 if (@_) { $self->{'engine'} = shift; };
235 return $self->{'engine'};
240 my ($self, $string) = @_;
241 my $line = $self->linesegment;
242 if (($line =~ /^\s*$/) and ($string =~ /^[^\s]/)) {
250 if (@_) { $self->{'format_table'} = shift; };
251 return $self->{'format_table'};
255 my ($self, $text) = @_;
257 my $out = $self->out;
259 while ($text ne '') {
260 my $top = $self->stackTop;
262 my ($plug, $context) = @$top;
263 if ($text =~ s/^(\n)//) {
265 my $e = $plug->contextInfo($context, 'lineending');
267 $self->contextParse($plug, $e)
269 my $attr = $plug->attributes->{$plug->contextInfo($context, 'attribute')};
270 $self->snippetParse($1, $attr);
272 $self->linesegment('');
273 my $b = $plug->contextInfo($context, 'linebeginning');
275 $self->contextParse($plug, $b)
278 my $sub = $plug->contextInfo($context, 'callback');
279 my $result = &$sub($plug, \$text);
281 my $f = $plug->contextInfo($context, 'fallthrough');
283 $self->contextParse($plug, $f);
286 my $attr = $plug->attributes->{$plug->contextInfo($context, 'attribute')};
287 $self->snippetParse($1, $attr);
292 push @$out, length($text), 'Normal';
301 my ($self, $text) = @_;
303 my @hl = $self->highlight($text);
307 unless (defined($t)) { $t = 'Normal' }
308 my $s = $self->substitutions;
311 my $k = substr($f , 0, 1);
312 $f = substr($f, 1, length($f) -1);
313 if (exists $s->{$k}) {
319 my $rt = $self->formatTable;
320 if (exists $rt->{$t}) {
322 $res = $res . $o->[0] . $rr . $o->[1];
325 $self->logwarning("undefined format tag '$t'");
332 my ($self, $language, $text) = @_;
333 my $eng = $self->engine;
334 my $plug = $eng->pluginGet($language);
335 if (defined($plug)) {
336 my $context = $plug->basecontext;
337 my $call = $plug->contextInfo($context, 'callback');
338 if (defined($call)) {
339 return &$call($plug, $text);
341 $self->logwarning("cannot find callback for context '$context'");
348 my ($self, $context, $text) = @_;
349 my $call = $self->contextInfo($context, 'callback');
350 if (defined($call)) {
351 return &$call($self, $text);
353 $self->logwarning("cannot find callback for context '$context'");
360 if ($self->engine eq $self) {
361 $self->stack([[$self, $self->basecontext]]);
367 if (@_) { $self->{'keywordcase'} = shift; }
368 return $self->{'keywordscase'}
372 my ($cw, $name) = @_;
385 if ($name =~ s/^(\d)//) {
386 $name = $numb{$1} . $name;
389 $name =~ s/\+/plus/g;
390 $name =~ s/\-/minus/g;
392 $name =~ s/[^0-9a-zA-Z]/_/g;
395 $name = ucfirst($name);
401 my $l = $self->linesegment;
402 if ($l eq '') { return "\n" } #last character was a newline
403 return substr($l, length($l) - 1, 1);
406 sub lastcharDeliminator {
408 my $deliminators = '\s|\~|\!|\%|\^|\&|\*|\+|\(|\)|-|=|\{|\}|\[|\]|:|;|<|>|,|\\|\||\.|\?|\/';
409 if ($self->linestart or ($self->lastchar =~ /$deliminators/)) {
417 if (@_) { $self->{'linesegment'} = shift; };
418 return $self->{'linesegment'};
423 if ($self->linesegment eq '') {
431 if (@_) { $self->{'lists'} = shift; }
432 return $self->{'lists'}
437 if (@_) { $self->{'out'} = shift; }
438 return $self->{'out'};
443 my $listname = shift;
444 my $lst = $self->lists;
446 my @l = reverse sort @_;
447 $lst->{$listname} = \@l;
449 $lst->{$listname} = [];
454 my ($self, $warning) = @_;
455 my $top = $self->engine->stackTop;
457 my $lang = $top->[0]->language;
458 my $context = $top->[1];
459 $warning = "$warning\n Language => $lang, Context => $context\n";
461 $warning = "$warning\n STACK IS EMPTY: PANIC\n"
467 my ($self, $text, $string, $lahead, $column, $fnspace, $context, $attr) = @_;
468 my $eng = $self->engine;
470 unless ($eng->firstnonspace($$text)) {
474 if (defined($column)) {
475 if ($column ne $eng->column) {
480 $$text = substr($$text, length($string));
482 unless (defined($attr)) {
483 my $t = $eng->stackTop;
484 my ($plug, $ctext) = @$t;
485 $r = $plug->attributes->{$plug->contextInfo($ctext, 'attribute')};
487 $r = $self->attributes->{$attr};
489 $eng->snippetParse($string, $r);
491 $eng->contextParse($self, $context);
496 my ($self, $language) = @_;
497 my $plugs = $self->{'plugins'};
498 unless (exists($plugs->{$language})) {
499 my $modname = 'Syntax::Highlight::Engine::Kate::' . $self->languagePlug($language);
500 unless (defined($modname)) {
501 $self->logwarning("no valid module found for language '$language'");
505 eval "use $modname; \$plug = new $modname(engine => \$self);";
506 if (defined($plug)) {
507 $plugs->{$language} = $plug;
509 $self->logwarning("cannot create plugin for language '$language'\n$@");
512 if (exists($plugs->{$language})) {
513 return $plugs->{$language};
520 $self->stack([[$self, $self->basecontext]]);
527 if (@_) { $self->{'snippet'} = shift; }
528 return $self->{'snippet'};
532 my ($self, $ch) = @_;
534 return if not defined $ch;
535 $self->{'snippet'} = $self->{'snippet'} . $ch;
537 $self->linesegment($self->linesegment . $ch);
542 sub snippetAttribute {
544 if (@_) { $self->{'snippetattribute'} = shift; }
545 return $self->{'snippetattribute'};
550 my $parse = $self->snippet;
552 my $out = $self->{'out'};
553 push(@$out, $parse, $self->snippetAttribute);
562 if ((defined $attr) and ($attr ne $self->snippetAttribute)) {
564 $self->snippetAttribute($attr);
566 $self->snippetAppend($snip);
571 if (@_) { $self->{'stack'} = shift; }
572 return $self->{'stack'};
576 my ($self, $val) = @_;
577 my $stack = $self->stack;
578 unshift(@$stack, $val);
582 my ($self, $val) = @_;
583 my $stack = $self->stack;
584 return shift(@$stack);
589 return $self->stack->[0];
593 my ($self, $state) = @_;
594 my $h = [ $self->stateGet ];
596 if (Dumper($h) eq Dumper($state)) { $equal = 1 };
602 my $s = $self->stack;
608 my $s = $self->stack;
614 if (@_) { $self->{'substitutions'} = shift; }
615 return $self->{'substitutions'};
622 my $insensitive = shift;
623 my $test = substr($$text, 0, 1);
626 $string = lc($string);
629 if (index($string, $test) > -1) {
630 return $self->parseResult($text, $bck, @_);
639 my $insensitive = shift;
642 $char = $self->capturedParse($char, 1);
644 my $test = substr($$text, 0, 1);
650 if ($char eq $test) {
651 return $self->parseResult($text, $bck, @_);
656 sub testDetect2Chars {
661 my $insensitive = shift;
664 $char = $self->capturedParse($char, 1);
665 $char1 = $self->capturedParse($char1, 1);
667 my $string = $char . $char1;
668 my $test = substr($$text, 0, 2);
671 $string = lc($string);
674 if ($string eq $test) {
675 return $self->parseResult($text, $bck, @_);
680 sub testDetectIdentifier {
683 if ($$text =~ /^([a-zA-Z_][a-zA-Z0-9_]+)/) {
684 return $self->parseResult($text, $1, @_);
689 sub testDetectSpaces {
692 if ($$text =~ /^([\\040|\\t]+)/) {
693 return $self->parseResult($text, $1, @_);
701 if ($self->engine->lastcharDeliminator) {
702 if ($$text =~ /^((?=\.?\d)\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?)/) {
703 return $self->parseResult($text, $1, @_);
712 if ($$text =~ /^('.')/) {
713 return $self->parseResult($text, $1, @_);
721 if ($self->engine->lastcharDeliminator) {
722 if ($$text =~ /^(0x[0-9a-fA-F]+)/) {
723 return $self->parseResult($text, $1, @_);
732 if ($self->engine->lastcharDeliminator) {
733 if ($$text =~ /^(0[0-7]+)/) {
734 return $self->parseResult($text, $1, @_);
740 sub testHlCStringChar {
743 if ($$text =~ /^(\\[a|b|e|f|n|r|t|v|'|"|\?])/) {
744 return $self->parseResult($text, $1, @_);
746 if ($$text =~ /^(\\x[0-9a-fA-F][0-9a-fA-F]?)/) {
747 return $self->parseResult($text, $1, @_);
749 if ($$text =~ /^(\\[0-7][0-7]?[0-7]?)/) {
750 return $self->parseResult($text, $1, @_);
758 if ($self->engine->lastcharDeliminator) {
759 if ($$text =~ /^([+-]?\d+)/) {
760 return $self->parseResult($text, $1, @_);
770 my $eng = $self->engine;
771 my $deliminators = $self->deliminators;
772 if (($eng->lastcharDeliminator) and ($$text =~ /^([^$deliminators]+)/)) {
774 my $l = $self->lists->{$list};
778 unless ($self->keywordscase) {
779 @rl = grep { (lc($match) eq lc($_)) } @list;
781 @rl = grep { ($match eq $_) } @list;
784 return $self->parseResult($text, $match, @_);
787 $self->logwarning("list '$list' is not defined, failing test");
793 sub testLineContinue {
798 if ($$text =~ /^\\\n/) {
799 $self->parseResult($text, "\\", $lahead, @_);
803 if ($$text =~ s/^(\\)(\n)/$2/) {
804 return $self->parseResult($text, "\\", $lahead, @_);
810 sub testRangeDetect {
815 my $insensitive = shift;
816 my $string = "$char\[^$char1\]+$char1";
817 return $self->testRegExpr($text, $string, $insensitive, 0, @_);
824 my $insensitive = shift;
827 $reg = $self->capturedParse($reg);
829 my $eng = $self->engine;
830 if ($reg =~ s/^\^//) {
831 unless ($eng->linestart) {
834 } elsif ($reg =~ s/^\\(b)//i) {
835 my $lastchar = $self->engine->lastchar;
837 if ($lastchar =~ /\w/) { return '' }
839 if ($lastchar =~ /\W/) { return '' }
848 if ($sample =~ /$reg/ig) {
850 # @cap = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
854 my @cap = map {$$_} 1 .. $#-;
855 $self->captured(\@cap)
861 # eval "if (defined\$$c) { push \@cap, \$$c } else { \$r = 0 }";
864 # if (@cap) { $self->captured(\@cap) };
867 if ($sample =~ /$reg/g) {
869 # @cap = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
873 my @cap = map {$$_} 1 .. $#-;
874 $self->captured(\@cap);
880 # eval "if (defined\$$c) { push \@cap, \$$c } else { \$r = 0 }";
883 # if (@cap) { $self->captured(\@cap) };
886 if (defined($pos) and ($pos > 0)) {
887 my $string = substr($$text, 0, $pos);
888 return $self->parseResult($text, $string, @_);
893 sub testStringDetect {
897 my $insensitive = shift;
900 $string = $self->capturedParse($string);
902 my $test = substr($$text, 0, length($string));
905 $string = lc($string);
908 if ($string eq $test) {
909 return $self->parseResult($text, $bck, @_);
923 Syntax::Highlight::Engine::Kate::Template - a template for syntax highlighting plugins
927 Syntax::Highlight::Engine::Kate::Template is a framework to assist authors of plugin modules.
928 All methods to provide highlighting to the Syntax::Highlight::Engine::Kate module are there, Just
929 no syntax definitions and callbacks. An instance of Syntax::Highlight::Engine::Kate::Template
930 should never be created, it's meant to be sub classed only.
936 =item B<attributes>(I<?$attributesref?>);
938 Sets and returns a reference to the attributes hash.
940 =item B<basecontext>(I<?$context?>);
942 Sets and returns the basecontext instance variable. This is the context that is used when highlighting starts.
944 =item B<captured>(I<$cap>);
946 Puts $cap in the first element of the stack, the current context. Used when the context is dynamic.
948 =item B<capturedGet>(I<$num>);
950 Returns the $num'th element that was captured in the current context.
952 =item B<capturedParse>(I<$string>, I<$mode>);
954 If B<$mode> is specified, B<$string> should only be one character long and numeric.
955 B<capturedParse> will return the Nth captured element of the current context.
957 If B<$mode> is not specified, all occurences of %[1-9] will be replaced by the captured
958 element of the current context.
962 returns the column position in the line that is currently highlighted.
964 =item B<contextdata>(I<\%data>);
966 Sets and returns a reference to the contextdata hash.
968 =item B<contextInfo>(I<$context>, I<$item>);
970 returns the value of several context options. B<$item> can be B<callback>, B<attribute>, B<lineending>,
971 B<linebeginning>, B<fallthrough>.
973 =item B<contextParse>(I<$plugin>, I<$context>);
975 Called by the plugins after a test succeeds. if B<$context> has following values:
977 #pop returns to the previous context, removes to top item in the stack. Can
978 also be specified as #pop#pop etc.
980 ##.... Switches to the plugin specified in .... and assumes it's basecontext.
981 .... Swtiches to the context specified in ....
983 =item B<deliminators>(I<?$delim?>);
985 Sets and returns a string that is a regular expression for detecting deliminators.
989 Returns a reference to the Syntax::Highlight::Engine::Kate module that created this plugin.
991 =item B<firstnonspace>(I<$string>);
993 returns true if the current line did not contain a non-spatial character so far and the first
994 character in B<$string> is also a spatial character.
998 sets and returns the instance variable B<format_table>. See also the option B<format_table>
1000 =item B<highlight>(I<$text>);
1002 highlights I<$text>. It does so by selecting the proper callback
1003 from the B<commands> hash and invoke it. It will do so untill
1004 $text has been reduced to an empty string. returns a paired list
1005 of snippets of text and the attribute with which they should be
1008 =item B<highlightText>(I<$text>);
1010 highlights I<$text> and reformats it using the B<format_table> and B<substitutions>
1012 =item B<includePlugin>(I<$language>, I<\$text>);
1014 Includes the plugin for B<$language> in the highlighting.
1016 =item B<includeRules>(I<$language>, I<\$text>);
1018 Includes the plugin for B<$language> in the highlighting.
1020 =item B<keywordscase>
1022 Sets and returns the keywordscase instance variable.
1026 return the last character that was processed.
1028 =item B<lastcharDeliminator>
1030 returns true if the last character processed was a deliminator.
1032 =item B<linesegment>
1034 returns the string of text in the current line that has been processed so far,
1038 returns true if processing is currently at the beginning of a line.
1040 =item B<listAdd>(I<'listname'>, I<$item1>, I<$item2> ...);
1042 Adds a list to the 'lists' hash.
1044 =item B<lists>(I<?\%lists?>);
1046 sets and returns the instance variable 'lists'.
1048 =item B<out>(I<?\@highlightedlist?>);
1050 sets and returns the instance variable 'out'.
1052 =item B<parseResult>(I<\$text>, I<$match>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1054 Called by every one of the test methods below. If the test matches, it will do a couple of subtests.
1055 If B<$column> is a defined numerical value it will test if the process is at the requested column.
1056 If B<$firnonspace> is true, it will test this also.
1057 Ig it is not a look ahead and all tests are passed, B<$match> is then parsed and removed from B<$$text>.
1059 =item B<pluginGet>(I<$language>);
1061 Returns a reference to a plugin object for the specified language. Creating an
1066 Resets the highlight engine to a fresh state, does not change the syntx.
1070 Contains the current snippet of text that will have one attribute. The moment the attribute
1071 changes it will be parsed.
1073 =item B<snippetAppend>(I<$string>)
1075 appends I<$string> to the current snippet.
1077 =item B<snippetAttribute>(I<$attribute>)
1079 Sets and returns the used attribute.
1081 =item B<snippetForce>
1083 Forces the current snippet to be parsed.
1085 =item B<snippetParse>(I<$text>, I<?$attribute?>)
1087 If attribute is defined and differs from the current attribute it does a snippetForce and
1088 sets the current attribute to B<$attribute>. Then it does a snippetAppend of B<$text>
1092 sets and returns the instance variable 'stack', a reference to an array
1096 retrieves the element that is on top of the stack, decrements stacksize by 1.
1098 =item B<stackPush>(I<$tagname>);
1100 puts I<$tagname> on top of the stack, increments stacksize by 1
1104 Retrieves the element that is on top of the stack.
1106 =item B<stateCompare>(I<\@state>)
1108 Compares two lists, \@state and the stack. returns true if they
1113 Returns a list containing the entire stack.
1115 =item B<stateSet>(I<@list>)
1117 Accepts I<@list> as the current stack.
1119 =item B<substitutions>
1121 sets and returns a reference to the substitutions hash.
1125 The methods below all return a boolean value.
1129 =item B<testAnyChar>(I<\$text>, I<$string>, I<$insensitive>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1131 =item B<testDetectChar>(I<\$text>, I<$char>, I<$insensitive>, I<$dynamic>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1133 =item B<testDetect2Chars>(I<\$text>, I<$char1>, I<$char2>, I<$insensitive>, I<$dynamic>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1135 =item B<testDetectIdentifier>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1137 =item B<testDetectSpaces>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1139 =item B<testFloat>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1141 =item B<testHlCChar>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1143 =item B<testHlCHex>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1145 =item B<testHlCOct>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1147 =item B<testHlCStringChar>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1149 =item B<testInt>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1151 =item B<testKeyword>(I<\$text>, I<$list>, I<$insensitive>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1153 =item B<testLineContinue>(I<\$text>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1155 =item B<testRangeDetect>(I<\$text>, I<$char1>, I<$char2>, I<$insensitive>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1157 =item B<testRegExpr>(I<\$text>, I<$reg>, I<$insensitive>, I<$dynamic>, I<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1159 =item B<testStringDetect>(I<\$text>, I<$string>, I<$insensitive>, I<$dynamic>, II<$lookahaed>, I<$column>, I<$firstnonspace>, I<$context>, I<$attribute>);
1163 =head1 ACKNOWLEDGEMENTS
1165 All the people who wrote Kate and the syntax highlight xml files.
1167 =head1 AUTHOR AND COPYRIGHT
1169 This module is written and maintained by:
1171 Hans Jeuken < haje at toneel dot demon dot nl >
1173 Copyright (c) 2006 by Hans Jeuken, all rights reserved.
1175 You may freely distribute and/or modify this module under same terms as
1180 Synax::Highlight::Engine::Kate http:://www.kate-editor.org