File Coverage

blib/lib/Tk/TextHighlight.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             package Tk::TextHighlight;
2              
3 1     1   21504 use vars qw($VERSION);
  1         2  
  1         114  
4             $VERSION = '1.1.0';
5 1     1   6 use base qw(Tk::Derived Tk::TextUndo);
  1         2  
  1         736  
6             use strict;
7             use Storable;
8             use File::Basename;
9              
10             my $blockHighlight = 0; #USED TO PREVENT RECURSIVE CALLS TO RE-HIGHLIGHT!
11             my $nodoEvent = 0; #USED TO PREVENT REPEATING (RUN-AWAY) SCROLLING!
12             Construct Tk::Widget 'TextHighlight';
13              
14             sub Populate {
15             my ($cw,$args) = @_;
16             $cw->SUPER::Populate($args);
17             $cw->ConfigSpecs(
18             -autoindent => [qw/PASSIVE autoindent Autoindent/, 0],
19             -match => [qw/PASSIVE match Match/, '[]{}()'],
20             -matchoptions => [qw/METHOD matchoptions Matchoptions/,
21             [-background => 'red', -foreground => 'yellow']],
22             -indentchar => [qw/PASSIVE indentchar Indentchar/, "\t"],
23             -disablemenu => [qw/PASSIVE disablemenu Disablemenu/, 0],
24             -commentchar => [qw/PASSIVE commentchar Commentchar/, "#"],
25             -colorinf => [qw/PASSIVE undef undef/, []],
26             -colored => [qw/PASSIVE undef undef/, 0],
27             -syntax => [qw/PASSIVE syntax Syntax/, 'None'],
28             -rules => [qw/PASSIVE undef undef/, undef],
29             -rulesdir => [qw/PASSIVE rulesdir Rulesdir/, ''],
30             -updatecall => [qw/PASSIVE undef undef/, sub {}],
31             -noRulesMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
32             -noSyntaxMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
33             -noRulesEditMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
34             -noSaveRulesMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FOR BACKWARD COMPATABILITY.
35             -noPlugInit => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FOR BACKWARD COMPATABILITY.
36             -highlightInBackground => [qw/PASSIVE undef undef/, 0], #JWT: SELF-EXPLANATORY.
37             DEFAULT => [ 'SELF' ],
38             );
39             $cw->bind('', sub { $cw->highlightVisual });
40             $cw->bind('', sub { $cw->doAutoIndent(0) });
41             $cw->bind('', sub { $cw->doAutoIndent(1) });
42             $cw->markSet('match', '0.0');
43             $cw->bind('', \&jumpToMatchingChar);
44             $cw->bind('', \&doShiftBackSpace); #DOESN'T SEEM TO WORK?!?!?!
45             $cw->bind('', \&deleteToEndofLine); #DOESN'T SEEM TO WORK?!?!?!
46             $cw->bind('', \&doShiftInsert); #DOESN'T SEEM TO WORK?!?!?!
47             }
48              
49             sub configure #ADDED 20081027 TO RE-CHECK RULE COLORS WHEN BACKGROUND CHANGES
50             {
51             my $cw = shift;
52             my $plug = $cw->Subwidget('formatter');
53             if ($plug)
54             {
55             for (my $i=0;$i<$#{_};$i++)
56             {
57             if ($_[$i] =~ /\-(?:bg|background)/o)
58             {
59             my $oldBg = $cw->cget($_[$i]);
60             unless ($_[$i+1] eq $oldBg)
61             {
62             #IF CHANGING BACKGROUND, MUST RESET RULE COLORS TO PREVENT
63             #COLOR CONTRAST ILLEGABILITIES!
64             $cw->SUPER::configure($_[$i] => $_[$i+1]);
65             $cw->configure('-rules' => undef);
66             $cw->highlightPlug;
67             last;
68             }
69             }
70             }
71             }
72             $cw->SUPER::configure(@_);
73             }
74              
75             sub jumpToMatchingChar #ADDED 20060630 JWT TO CAUSE ^p TO WORK LIKE VI & SUPERTEXT - JUMP TO MATCHING CHARACTER!
76             {
77             my $cw = shift;
78             $cw->markSet('insert', $cw->index('insert'));
79             my $pm = -1;
80             eval { $pm = $cw->index('MyMatch'); };
81             if ($pm >= 0)
82             {
83             my $prevMatch = $cw->index('insert');
84             $prevMatch .= '.0' unless ($prevMatch =~ /\./o);
85             $cw->markSet('insert', $cw->index('MyMatch'));
86             $cw->see('insert');
87             $cw->markSet('MyMatch', $prevMatch);
88             }
89             }
90              
91             sub doShiftBackSpace
92             {
93             my $cw = shift;
94             my $curPos = $cw->index('insert');
95             my $leftPos = $cw->index('insert linestart');
96             $cw->delete($leftPos, $curPos) unless ($curPos <= $leftPos);
97             }
98              
99             sub deleteToEndofLine
100             {
101             my ($cw) = @_;
102             if ($cw->compare('insert','==','insert lineend'))
103             {
104             $cw->delete('insert')
105             }
106             else
107             {
108             $cw->delete('insert','insert lineend')
109             }
110             }
111              
112             sub doShiftDelete
113             {
114             my $cw = shift;
115             (my $curPos = $cw->index('insert')) =~ s/\..*$//o;
116             my $startPos = ($curPos > 1) ? $cw->index('insert - 1 line lineend')
117             : $cw->index('1.0');
118             my $endPos = $cw->index('insert lineend');
119             $cw->delete($startPos, $endPos); # unless ($startPos <= $endPos);
120             }
121              
122             sub doShiftInsert
123             {
124             my $cw = shift;
125             my $insPos = $cw->index('insert lineend');
126             $cw->insert($insPos, "\n");
127             }
128              
129             sub ClassInit #JWT: ADDED FOR VI-LIKE Control-P JUMP TO MATCHING BRACKET FEATURE.
130             {
131             my ($class,$w) = @_;
132            
133             $class->SUPER::ClassInit($w);
134              
135             # reset default Tk::Text binds
136             $w->bind($class, '', sub {} );
137             $w->bind($class, '', 'insertTabChar' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
138             $w->bind($class, '', 'doShiftBackSpace' ); #DOESN'T SEEM TO WORK?!?!?!
139             $w->bind($class, '', 'doShiftDelete' );
140             $w->bind($class, '', 'deleteToEndofLine' );
141             $w->bind($class, '', 'deleteToEndofLine' ); #DOESN'T SEEM TO WORK?!?!?!
142             $w->bind($class, '', 'insertTab' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
143             $w->bind($class, '', 'doShiftBackSpace' );
144             return $class;
145             }
146              
147             sub clipboardCopy {
148             my $cw = shift;
149             my @ranges = $cw->tagRanges('sel');
150             if (@ranges) {
151             $cw->SUPER::clipboardCopy(@_);
152             }
153             }
154              
155             sub clipboardCut {
156             my $cw = shift;
157             my @ranges = $cw->tagRanges('sel');
158             if (@ranges) {
159             $cw->SUPER::clipboardCut(@_);
160             }
161             }
162              
163             sub clipboardPaste {
164             my $cw = shift;
165             my @ranges = $cw->tagRanges('sel');
166             if (@ranges) {
167             $cw->tagRemove('sel', '1.0', 'end');
168             return;
169             }
170             $cw->SUPER::clipboardPaste(@_);
171             }
172              
173             sub delete {
174             my $cw = shift;
175             my $begin = $_[0];
176             if (defined($begin)) {
177             $begin = $cw->linenumber($begin);
178             } else {
179             $begin = $cw->linenumber('insert');
180             };
181             my $end = $_[1];
182             if (defined($end)) {
183             $end = $cw->linenumber($end);
184             } else {
185             $end = $begin;
186             };
187             $cw->SUPER::delete(@_);
188             $cw->highlightCheck($begin, $end);
189             }
190              
191             sub doAutoIndent {
192             my $cw = shift;
193             my $doAutoIndent = shift;
194             return unless ($doAutoIndent);
195              
196             if ($cw->cget('-autoindent')) {
197             my $i = $cw->index('insert linestart');
198             if ($cw->compare($i, ">", '0.0')) {
199             my $s = $cw->get("$i - 1 lines", "$i - 1 lines lineend");
200             # if ($s =~ /\S/) #JWT: UNCOMMENT TO CAUSE SUBSEQUENT BLANK LINES TO NOT BE AUTOINDENTED.
201             # {
202             #$s =~ /^(\s+)/; #CHGD. TO NEXT 20060701 JWT TO FIX "e" BEING INSERTED INTO LINE WHEN AUTOINDENT ON?!
203             $s =~ /^(\s*)/o;
204             if ($1) {
205             $cw->insert('insert', $1);
206             }
207             $cw->insert('insert', $cw->cget('-indentchar'))
208             if ($s =~ /[\{\[\(]\s*$/o); #ADDED 20060701 JWT - ADD AN INDENTION IF JUST OPENED A BLOCK!
209             # }
210             }
211             }
212             }
213              
214             sub EditMenuItems {
215             my $cw = shift;
216             return [
217             @{$cw->SUPER::EditMenuItems},
218             "-",
219             ["command"=>'Comment', -command => [$cw => 'selectionComment']],
220             ["command"=>'Uncomment', -command => [$cw => 'selectionUnComment']],
221             "-",
222             ["command"=>'Indent', -command => [$cw => 'selectionIndent']],
223             ["command"=>'Unindent', -command => [$cw => 'selectionUnIndent']],
224             ];
225             }
226              
227             sub EmptyDocument {
228             my $cw = shift;
229             my @r = $cw->SUPER::EmptyDocument(@_);
230             $cw->highlightPurge(1);
231             return @r
232             }
233              
234             sub highlight {
235             my ($cw, $begin, $end) = @_;
236             # return $begin if ($blockHighlight); #PREVENT RECURSIVE CALLING WHILST ALREADY REHIGHLIGHTING!
237             $blockHighlight = 1;
238             if (not defined($end)) { $end = $begin + 1};
239             #save selection and cursor position
240             my @sel = $cw->tagRanges('sel');
241             # my $cursor = $cw->index('insert');
242             #go over the source code line by line.
243             while ($begin < $end) {
244             $cw->highlightLine($begin);
245             $begin++; #move on to next line.
246             };
247             #restore original cursor and selection
248             # $cw->markSet('insert', $cursor);
249             #1 if ($sel[0]) {
250             #1 $cw->tagRaise('sel'); #JWT:REMOVED 20060703 SO THAT HIGHLIGHTING STAYS ON SELECTED STUFF AFTER SELECTION MOVES OVER UNTAGGED TEXT.
251             #1 };
252             $blockHighlight = 0;
253             return $begin;
254             }
255              
256             sub highlightCheck {
257             my ($cw, $begin, $end) = @_;
258             my $col = $cw->cget('-colored');
259             my $cli = $cw->cget('-colorinf');
260             if ($begin <= $col) {
261             #The operation occurred in an area that was highlighted already
262             if ($begin < $end) {
263             #it was a multiline operation, so highlighting is not reliable anymore
264             #restart hightlighting from the beginning of the operation.
265             $cw->highlightPurge($begin);
266             } else {
267             #just re-highlight the modified line.
268             my $hlt = $cw->highlightPlug;
269             my $i = $cli->[$begin];
270             $cw->highlight($begin);
271             if (($col < $cw->linenumber('end')) and (not $hlt->stateCompare($i))) {
272             #the proces ended inside a multiline token. try to fix it.
273             $cw->highlightPurge($begin);
274             }
275             };
276             $cw->matchCheck;
277             } else {
278             $cw->highlightVisual;
279             }
280             }
281              
282             sub highlightLine {
283             my ($cw, $num) = @_;
284             my $hlt = $cw->highlightPlug;
285             my $cli = $cw->cget('-colorinf');
286             my $k = $cli->[$num - 1];
287             $hlt->stateSet(@$k);
288             # remove all existing tags in this line
289             my $begin = "$num.0"; my $end = $cw->index("$num.0 lineend");
290             my $rl = $hlt->rules;
291             foreach my $tn (@$rl) {
292             $cw->tagRemove($tn->[0], $begin, $end);
293             }
294             my $txt = $cw->get($begin, $end); #get the text to be highlighted
295             my @v;
296             if ($txt) { #if the line is not empty
297             my $pos = 0;
298             my $start = 0;
299             my @h = $hlt->highlight("$txt\n"); #JWT: ADDED "\n" TO MAKE KATE WORK!
300             while (@h ne 0) {
301             $start = $pos;
302             $pos += shift @h;
303             my $tag = shift @h;
304             $cw->tagAdd($tag, "$num.$start", "$num.$pos");
305             };
306             $cw->DoOneEvent(2) unless ($nodoEvent
307             || !$cw->cget('-highlightInBackground')); #DON'T PREVENT USER-INTERACTION WHILE RE-HILIGHTING!
308             };
309             $cli->[$num] = [ $hlt->stateGet ];
310             }
311              
312             sub highlightPlug {
313             my $cw = shift;
314             my $plug = $cw->Subwidget('formatter');
315             my $syntax = $cw->cget('-syntax');
316             $syntax =~ s/\:\:.*$//o;
317             my $rules = $cw->cget('-rules');
318             if (not defined($plug)) {
319             $plug = $cw->highlightPlugInit;
320             } elsif (ref($syntax)) {
321             if ($syntax ne $plug) {
322             $plug = $cw->highlightPlugInit;
323             }
324             } elsif ($syntax ne $plug->syntax) {
325             $cw->rulesDelete;
326             $plug = $cw->highlightPlugInit;
327             $cw->highlightPurge(1);
328             } elsif (defined($rules)) {
329             # if ($rules ne $plug->rules) { #JWT: CHGD TO NEXT TO PREVENT INFINITE RECURSION WHEN "None" HIGHLIGHTER IS USED!
330             if ($#{$rules} >= 0 && $rules ne $plug->rules) {
331             $cw->rulesDelete;
332             $plug->rules($rules);
333             $cw->rulesConfigure;
334             $cw->highlightPurge(1);
335             }
336             } else {
337             $cw->rulesDelete;
338             $cw->highlightPlugInit;
339             $cw->highlightPurge(1);
340             }
341             return $plug
342             }
343              
344             sub highlightPlugInit {
345             my $cw = shift;
346             my $syntax = $cw->cget('-syntax');
347             if (not defined($cw->cget('-rules'))) { $cw->rulesFetch };
348             my $plug;
349             my $lang = '';
350             if (ref($syntax)) {
351             $plug = $syntax;
352             } else {
353             $lang = $1 if ($syntax =~ s/\:\:(.*)$//o);
354             my @opt = ();
355             if (my $rules = $cw->cget('-rules')) {
356             push(@opt, $rules);
357             }
358             my $evalStr = "require Tk::TextHighlight::$syntax; \$plug = new Tk::TextHighlight::$syntax("
359             .($lang ? "'$lang', " : '') . "\@opt);";
360             eval $evalStr;
361             #JWT: ADDED UNLESS 20060703 TO PROPERLY INITIALIZE RULES FROM PLUGIN, IF NO .rules FILE DEFINED.
362             unless ($@ || !defined($plug) || !defined($plug->rules)
363             || $cw->cget('-noPlugInit'))
364             {
365             my $rules = $plug->rules;
366             $cw->configure(-rules => \@$rules);
367             }
368             }
369             $cw->Advertise('formatter', $plug);
370             $cw->rulesConfigure;
371             my $bg = $cw->cget(-background);
372             my ($red, $green, $blue) = $cw->rgb($bg); #JWT: NEXT 11 ADDED 20070802 TO PREVENT INVISIBLE TEXT!
373             my @rgb = sort {$b <=> $a} ($red, $green, $blue);
374             my $max = $rgb[0]+$rgb[1]; #TOTAL BRIGHTEST 2.
375             my $daytime = 1;
376             my $currentrules = $plug->rules;
377             if ($max <= 52500) { #IF BG COLOR IS DARK ENOUGH, FORCE RULES WITH NORMAL BLACK-
378             $daytime = 0; #FOREGROUND TO WHITE TO AVOID COLOR CONTRAST ILLEGABILITIES.
379             #print "-NIGHT 65!\n";
380             for (my $k=0;$k<=$#{$currentrules};$k++)
381             {
382             if ($currentrules->[$k]->[2] eq 'black')
383             {
384             $cw->setRule($currentrules->[$k]->[0],$currentrules->[$k]->[1],'white');
385             }
386             };
387             }
388             for (my $k=0;$k<=$#{$currentrules};$k++)
389             {
390             if (defined($currentrules->[$k]->[2]) and $currentrules->[$k]->[2] eq $bg)
391             {
392             #RULE FOREGROUND COLOR == BACKGROUND, CHANGE TO BLACK OR WHITE TO KEEP READABLE!
393             $cw->setRule($currentrules->[$k]->[0],$currentrules->[$k]->[1],($daytime ? 'black' : 'white'));
394             }
395             };
396             $cw->update;
397             unless ($cw->cget('-noSyntaxMenu')) #JWT: ADDED TO ENSURE VIEW RADIO-BUTTON PROPERLY INITIALIZED/SET.
398             {
399             my @kateMenus;
400             my $ViewSyntaxMenu = $cw->menu->entrycget('View','-menu')->entrycget('Syntax','-menu');
401             my $lastMenuIndex = $ViewSyntaxMenu->index('end');
402              
403             #WE MUST FETCH THE VARIABLE REFERENCE USED BY THE "View" MENU RADIO-BUTTONS SO
404             #THAT OUR NEW RADIO BUTTONS SHARE SAME VARIABLE (OTHERWISE, WILL HAVE >1 LIT AT
405             #SAME TIME!
406              
407             my $var;
408             foreach my $i (0..$lastMenuIndex)
409             {
410             if ($ViewSyntaxMenu->type($i) =~ /radiobutton/o)
411             {
412             $var = $ViewSyntaxMenu->entrycget($i, '-variable');
413             tie $$var,'Tk::Configure',$cw,'-syntax';
414             unless (ref($syntax))
415             {
416             $$var = $lang ? ($syntax.'::'.$lang) : $syntax;
417             }
418             last;
419             }
420             }
421             }
422             return $plug;
423             }
424              
425             sub highlightPlugList {
426             my $cw = shift;
427             my @ml = ();
428             my $haveKate = 0;
429             foreach my $d (@INC) {
430             my @fl = <$d/Tk/TextHighlight/*.pm>;
431             foreach my $file (@fl) {
432             my ($name, $path, $suffix) = fileparse($file, "\.pm");
433             if ($name eq 'Kate') { #JWT:ADDED THIS PART OF CONDITIONAL 20160118:
434             eval 'use Syntax::Highlight::Engine::Kate; $haveKate = 1; 1' unless ($haveKate);
435             if ($haveKate) {
436             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
437             }
438             } elsif (($name ne 'None') and ($name ne 'Template')) {
439             #avoid duplicates
440             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
441             }
442             }
443             }
444             return sort @ml;
445             }
446              
447             sub highlightPurge {
448             my ($cw, $line) = @_;
449             $cw->configure('-colored' => $line);
450             my $cli = $cw->cget('-colorinf');
451             if (@$cli) { splice(@$cli, $line) };
452             $cw->highlightVisual;
453             }
454              
455             sub highlightVisual {
456             my $cw = shift;
457             return if ($blockHighlight);
458             my $end = $cw->visualend;
459             my $col = $cw->cget('-colored');
460             if ($col < $end) {
461             $col = $cw->highlight($col, $end);
462             $cw->configure(-colored => $col);
463             };
464             $cw->matchCheck;
465             }
466              
467             sub insert {
468             my $cw = shift;
469             my $pos = shift;
470             $pos = $cw->index($pos);
471             my $begin = $cw->linenumber("$pos - 1 chars");
472             $cw->SUPER::insert($pos, @_);
473             $cw->highlightCheck($begin, $cw->linenumber("insert lineend"));
474             }
475              
476             sub Insert {
477             my $cw = shift;
478             $cw->SUPER::Insert(@_);
479             $cw->see('insert');
480             }
481              
482             sub InsertKeypress {
483             my ($cw,$char) = @_;
484             if ($char ne '') {
485             my $index = $cw->index('insert');
486             my $line = $cw->linenumber($index);
487             if ($char =~ /^\S$/o and !$cw->OverstrikeMode and !$cw->tagRanges('sel')) {
488             my $undo_item = $cw->getUndoAtIndex(-1);
489             if (defined($undo_item) &&
490             ($undo_item->[0] eq 'delete') &&
491             ($undo_item->[2] == $index)
492             ) {
493             $cw->Tk::Text::insert($index,$char);
494             $undo_item->[2] = $cw->index('insert');
495             $cw->highlightCheck($line, $line);
496             $cw->see('insert'); #ADDED 20060703 TO ALLOW USER TO SEE WHAT HE'S TYPING PAST END OF LINE (THIS IS BROKEN IN TEXTUNDO TOO).
497             return;
498             }
499             }
500             $cw->addGlobStart;
501             $cw->Tk::Text::InsertKeypress($char);
502             $cw->addGlobEnd;
503             }
504             }
505              
506             sub linenumber {
507             my ($cw, $index) = @_;
508             if (not defined($index)) { $index = 'insert'; }
509             my $id = $cw->index($index);
510             my ($line, $pos ) = split(/\./o, $id);
511             return $line;
512             }
513              
514             sub Load {
515             my $cw = shift;
516             my @r = $cw->SUPER::Load(@_);
517             $cw->highlightVisual;
518             return @r;
519             }
520              
521             sub matchCheck {
522             my $cw = shift;
523             my $c = $cw->get('insert', 'insert + 1 chars');
524             my $p = $cw->index('match');
525             if ($p ne '0.0') {
526             $cw->tagRemove('Match', $p, "$p + 1 chars");
527             $cw->markSet('match', '0.0');
528             $cw->markUnset('MyMatch');
529             }
530             if ($c) {
531             my $v = $cw->cget('-match');
532             my $p = index($v, $c);
533             if ($p ne -1) { #a character in '-match' has been detected.
534             my $count = 0;
535             my $found = 0;
536             if ($p % 2) {
537             my $m = substr($v, $p - 1, 1);
538             $cw->matchFind('-backwards', $c, $m,
539             $cw->index('insert'),
540             # $cw->index('@0,0'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
541             $cw->index('0.0'),
542             );
543             } else {
544             my $m = substr($v, $p + 1, 1);
545             # print "searching -forwards, $c, $m\n";
546             $cw->matchFind('-forwards', $c, $m,
547             $cw->index('insert + 1 chars'),
548             # $cw->index($cw->visualend . '.0 lineend'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
549             $cw->index('end'),
550             );
551             }
552             }
553             }
554             $cw->updateCall;
555             }
556              
557             sub matchFind {
558             my ($cw, $dir, $char, $ochar, $start, $stop) = @_;
559             #first of all remove a previous match highlight;
560             my $pattern = "\\$char|\\$ochar";
561             my $found = 0;
562             my $count = 0;
563             while ((not $found) and (my $i = $cw->search(
564             $dir, '-regexp', '-nocase', '--', $pattern, $start, $stop
565             ))) {
566             my $k = $cw->get($i, "$i + 1 chars");
567             # print "found $k at $i and count is $count\n";
568             if ($k eq $ochar) {
569             if ($count > 0) {
570             # print "decrementing count\n";
571             $count--;
572             if ($dir eq '-forwards') {
573             $start = $cw->index("$i + 1 chars");
574             } else {
575             $start = $i;
576             }
577             } else {
578             # print "Found !!!\n";
579             $cw->markSet('match', $i);
580             $cw->tagAdd('Match', $i, "$i + 1 chars");
581             $cw->markSet('MyMatch', $i);
582             $cw->tagRaise('Match');
583             $found = 1;
584             }
585             } elsif ($k eq $char) {
586             # print "incrementing count\n";
587             $count++;
588             if ($dir eq '-forwards') {
589             $start = $cw->index("$i + 1 chars");
590             } else {
591             $start = $i;
592             }
593             } elsif ($i eq $start) {
594             $found = 1;
595             }
596             }
597             }
598              
599             sub matchoptions {
600             my $cw = shift;
601             if (my $o = shift) {
602             my @op = ();
603             if (ref($o)) {
604             @op = @$o;
605             } else {
606             @op = split(/\s+/o, $o);
607             }
608             $cw->tagConfigure('Match', @op);
609             }
610             }
611              
612              
613             sub PostPopupMenu {
614             my $cw = shift;
615             my @r;
616             if (not $cw->cget('-disablemenu')) {
617             @r = $cw->SUPER::PostPopupMenu(@_);
618             }
619             }
620              
621             sub rulesConfigure {
622             my $cw = shift;
623             if (my $plug = $cw->Subwidget('formatter')) {
624             my $rules = $plug->rules;
625             my @r = @$rules;
626             foreach my $k (@r) {
627             $cw->tagConfigure(@$k);
628             };
629             $cw->configure(-colored => 1, -colorinf => [[ $plug->stateGet]]);
630             }
631             }
632              
633             sub setRule #ADDED 20060530 JWT TO PERMIT CHANGING INDIVIDUAL RULES.
634             {
635             my $cw = shift;
636             my @rule = @_;
637              
638             if (my $plug = $cw->Subwidget('formatter'))
639             {
640             my $rules = $plug->rules;
641             my @r = @$rules;
642             for (my $k=0;$k<=$#r;$k++)
643             {
644             if ($rule[0] eq $r[$k]->[0])
645             {
646             @{$r[$k]} = @rule;
647             }
648             };
649             $cw->configure(-rules => \@r);
650             }
651             }
652              
653             sub rulesDelete {
654             my $cw = shift;
655             if (my $plug = $cw->Subwidget('formatter')) {
656             my $rules = $plug->rules;
657             foreach my $r (@$rules) {
658             $cw->tagDelete($r->[0]);
659             }
660             }
661             }
662              
663              
664             sub rulesEdit {
665             my $cw = shift;
666             require Tk::RulesEditor;
667             $cw->RulesEditor(
668             -class => 'Toplevel',
669             );
670             }
671              
672             sub rulesFetch {
673             my $cw = shift;
674             my $dir = $cw->cget('-rulesdir');
675             my $syntax = $cw->cget('-syntax');
676             $cw->configure(-rules => undef);
677             # print "rulesFetch called\n";
678             my $result = 0;
679             if ($dir and (-e "$dir/$syntax.rules")) {
680             my $file = "$dir/$syntax.rules";
681             # print "getting $file\n";
682             if (my $rl = retrieve("$dir/$syntax.rules")) {
683             # print "configuring\n";
684             $cw->configure(-rules => $rl);
685             $result = 1;
686             }
687             }
688             return $result;
689             }
690              
691             sub rulesSave {
692             my $cw = shift;
693             my $dir = $cw->cget('-rulesdir');
694             # print "rulesSave called\n";
695             if ($dir) {
696             my $syntax = $cw->cget('-syntax');
697             my $file = "$dir/$syntax.rules";
698             store($cw->cget('-rules'), $file);
699             }
700             }
701              
702             sub scan {
703             my $cw = shift;
704             my @r = $cw->SUPER::scan(@_);
705             $cw->highlightVisual;
706             return @r;
707             }
708              
709             sub selectionModify {
710             my ($cw, $char, $mode) = @_;
711             my @ranges = $cw->tagRanges('sel');
712             if (@ranges eq 2) {
713             my $start = $cw->index($ranges[0]);
714             my $end = $cw->index($ranges[1]);
715             # print "doing from $start to $end\n";
716             while ($cw->compare($start, "<", $end)) {
717             # print "going to do something\n";
718             if ($mode) {
719             if ($cw->get("$start linestart", "$start linestart + 1 chars") eq $char) {
720             $cw->delete("$start linestart", "$start linestart + 1 chars");
721             }
722             } else {
723             $cw->insert("$start linestart", $char)
724             }
725             $start = $cw->index("$start + 1 lines");
726             }
727             $cw->tagAdd('sel', @ranges);
728             }
729             }
730              
731             sub selectionComment {
732             my $cw = shift;
733             $cw->selectionModify($cw->cget('-commentchar'), 0);
734             }
735              
736             sub selectionIndent {
737             my $cw = shift;
738             $cw->selectionModify($cw->cget('-indentchar'), 0);
739             }
740              
741             sub selectionUnComment {
742             my $cw = shift;
743             $cw->selectionModify($cw->cget('-commentchar'), 1);
744             }
745              
746             sub selectionUnIndent {
747             my $cw = shift;
748             $cw->selectionModify($cw->cget('-indentchar'), 1);
749             }
750              
751             sub syntax {
752             my $cw = shift;
753             if (@_) {
754             my $name = shift;
755             my $fm;
756             eval ("require Tk::TextHighlight::$name; \$fm = new Tk::TextHighlight::$name(\$cw);");
757             $cw->Advertise('formatter', $fm);
758             $cw->configure('-langname' => $name);
759             }
760             return $cw->cget('-langname');
761             }
762              
763             sub yview {
764             my $cw = shift;
765             my @r = ();
766             if (@_) {
767             @r = $cw->SUPER::yview(@_);
768             if ($_[1] > 0) { #ONLY RE-HIGHLIGHT IF SCROLLING DOWN (PREV. LINES ALREADY HIGHLIGHTED)!
769             my ($p) = caller;
770             $nodoEvent = 1 if ($p =~ /scroll/io); #THIS PREVENTS REPEATING (RUN-AWAY) SCROLLING!
771             $cw->highlightVisual;
772             }
773             } else {
774             @r = $cw->SUPER::yview;
775             }
776             return @r;
777             }
778              
779             sub see {
780             my $cw = shift;
781             my @r = $cw->SUPER::see(@_);
782             $cw->highlightVisual;
783             return @r
784             }
785              
786             sub updateCall {
787             my $cw = shift;
788             my $call = $cw->cget('-updatecall');
789             &$call;
790             $nodoEvent = 0;
791             }
792              
793             sub ViewMenuItems {
794             my $cw = shift;
795             my $s;
796             tie $s,'Tk::Configure',$cw,'-syntax';
797             my @stx = ('None', $cw->highlightPlugList);
798             my @rad = (['command' => 'Reset', -command => sub {
799             $cw->configure('-rules' => undef);
800             $cw->highlightPlug;
801             }]);
802             foreach my $n (@stx) {
803             push(@rad, [
804             'radiobutton' => $n,
805             -variable => \$s,
806             -value => $n,
807             -command => sub {
808             $cw->configure('-rules' => undef);
809             $cw->highlightPlug;
810             }
811             ]);
812             }
813             my $dir = $cw->cget('-rulesdir');
814             my $syntax = $cw->cget('-syntax');
815             my $menuExt = \@{$cw->SUPER::ViewMenuItems};
816             unless ($cw->cget('-noRulesMenu'))
817             {
818             push (@{$menuExt},
819             ['cascade'=>'Syntax',
820             -menuitems => [@rad],
821             ]) unless ($cw->cget('-noSyntaxMenu'));
822             push (@{$menuExt},
823             ['command'=>'Rules Editor',
824             -command => sub { $cw->rulesEdit },
825             ]) unless ($cw->cget('-noRulesEditMenu'));
826             push (@{$menuExt},
827             ['command'=>'Save Rules',
828             -command => sub { $cw->rulesSave },
829             ]) if (!$cw->cget('-noSaveRulesMenu') && $dir
830             && (-w $dir));
831             }
832             return $menuExt;
833             }
834              
835             sub visualend {
836             my $cw = shift;
837             my $end = $cw->linenumber('end - 1 chars');
838             my ($first, $last) = $cw->Tk::Text::yview;
839             my $vend = int($last * $end) + 2;
840             if ($vend > $end) {
841             $vend = $end;
842             }
843             return $vend;
844             }
845              
846             sub fetchKateInfo #FETCH LISTS OF KATE LANGUAGES AND FILE EXTENSION PATTERNS W/O KATE:
847             {
848             #IT IS NECESSARY TO FETCH THIS INFORMATION W/O USING KATE METHODS SINCE WE MAY NOT
849             #HAVE CREATED A KATE OBJECT WHEN THIS IS NEEDED!
850             #We return 3 hash-references: 1st can be passed to addkate2viewmenu() to add the
851             #Kate languages to the Syntax.View menu. the keys are "Kate::language" and the
852             #values are what's needed to instantiate Kate for that language. the 2nd is
853             #a list of file-extension pattern suitable for matching against file-names and
854             #the values are the reccomended Kate language for that file-extension.
855              
856             my $cw = shift;
857              
858             my (%sectionHash, %extHash, %syntaxHash);
859              
860             foreach my $i (@INC)
861             {
862             if (-e "$i/Syntax/Highlight/Engine/Kate.pm"
863             && open KATE, "$i/Syntax/Highlight/Engine/Kate.pm")
864             {
865             my $inExtensions = 0;
866             my $inSyntaxes = 0;
867             my $inSections = 0;
868             while ()
869             {
870             chomp;
871             $inExtensions = 1 if (/\$self\-\>\{\'extensions\'\}\s*\=\s*\{/o);
872             $inSections = 1 if (/\$self\-\>\{\'sections\'\}\s*\=\s*\{/o);
873             $inSyntaxes = 1 if (/\$self\-\>\{\'syntaxes\'\}\s*\=\s*\{/o);
874             if ($inSections)
875             {
876             if (/\'([^\']+)\'\s*\=\>\s*\[/o)
877             {
878             $inSections = $1;
879             @{$sectionHash{$inSections}} = ();
880             }
881             elsif (/\'([^\']+)\'\s*\,/o)
882             {
883             push (@{$sectionHash{$inSections}}, $1);
884             }
885             elsif (/\}\;/o)
886             {
887             $inSections = 0;
888             }
889             }
890             elsif ($inExtensions)
891             {
892             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/o)
893             {
894             my $one = '^'.$1.'$';
895             my $two = $2;
896             $one =~ s/\./\\\./o;
897             $one =~ s/\*/\.\*/go;
898             $extHash{$one} = "Kate::$two";
899             }
900             elsif (/\}\;/o)
901             {
902             $inExtensions = 0;
903             }
904             }
905             elsif ($inSyntaxes)
906             {
907             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/o)
908             {
909             $syntaxHash{$1} = $2;
910             }
911             elsif (/\}\;/o)
912             {
913             $inSyntaxes = 0;
914             close KATE;
915             last;
916             }
917             }
918             }
919             close KATE;
920             last;
921             }
922             }
923             return (\%sectionHash, \%extHash, \%syntaxHash);
924             }
925              
926             sub addKate2ViewMenu #ADD ALL KATE-LANGUAGES AS OPTIONS TO THE "View" MENU:
927             {
928             my $cw = shift;
929             my $sectionHash = shift;
930              
931             return undef if ($cw->cget('-noRulesMenu') || $cw->cget('-noSyntaxMenu'));
932              
933             my $ViewSyntaxMenu = $cw->menu->entrycget('View','-menu')->entrycget('Syntax','-menu');
934             my $lastMenuIndex = $ViewSyntaxMenu->index('end');
935              
936             #WE MUST FETCH THE VARIABLE REFERENCE USED BY THE "View" MENU RADIO-BUTTONS SO
937             #THAT OUR NEW RADIO BUTTONS SHARE SAME VARIABLE (OTHERWISE, WILL HAVE >1 LIT AT
938             #SAME TIME!
939              
940             my $var;
941             my $kateIndx = 'end';
942             foreach my $i (0..$lastMenuIndex)
943             {
944             if ($ViewSyntaxMenu->type($i) =~ /radiobutton/o)
945             {
946             $var = $ViewSyntaxMenu->entrycget($i, '-variable');
947             tie $$var,'Tk::Configure',$cw,'-syntax';
948             if ($ViewSyntaxMenu->entrycget($i, '-label') eq 'Kate')
949             {
950             $ViewSyntaxMenu->delete($i); #REMOVE THE "Kate" ENTRY, SINCE WE'RE ADDING KATE STUFF SEPARATELY!
951             #UNCOMMENT TO INSERT KATE MENUS IN ALPHABETICAL ORDER IN VIEW MENU: $kateIndx = $i; #SAVE IT'S MENU-LOCATION SO WE CAN INSERT THE KATE MENU TREE THERE.
952             last;
953             }
954             }
955             }
956              
957             #NOW ADD OUR "KATE" RADIO-BUTTONS!
958              
959             my ($nextMenu, $menuTitle);
960             foreach my $sect (sort keys %{$sectionHash})
961             {
962             $nextMenu = $ViewSyntaxMenu->Menu;
963             foreach my $lang (@{$sectionHash->{$sect}})
964             {
965             $menuTitle = "Kate::$lang";
966             $nextMenu->radiobutton( -label => $menuTitle,
967             -variable => $var,
968             -value => $menuTitle,
969             -command => sub
970             {
971             $cw->configure('-rules' => undef);
972             $cw->highlightPlug;
973             }
974             );
975             }
976             $ViewSyntaxMenu->insert($kateIndx, 'cascade', -label => "Kate: $sect...",
977             -menu => $nextMenu);
978             ++$kateIndx if ($kateIndx =~ /^\d/o);
979             }
980             }
981              
982             sub insertTab
983             {
984             my ($w) = @_;
985             # $w->Insert("\t");
986             $w->Insert($w->cget('-indentchar'));
987             $w->focus;
988             $w->break
989             }
990              
991             sub insertTabChar
992             {
993             my ($w) = @_;
994             $w->Insert("\t");
995             $w->focus;
996             $w->break
997             }
998              
999             1;
1000              
1001             __END__