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   25366 use vars qw($VERSION);
  1         3  
  1         68  
4             $VERSION = '1.0.5';
5 1     1   6 use base qw(Tk::Derived Tk::TextUndo);
  1         2  
  1         804  
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, '', 'insertTab' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
138             $w->bind($class, '', 'insertTabChar' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
139             $w->bind($class, '', 'doShiftBackSpace' ); #DOESN'T SEEM TO WORK?!?!?!
140             $w->bind($class, '', 'doShiftDelete' );
141             $w->bind($class, '', 'deleteToEndofLine' );
142             $w->bind($class, '', 'deleteToEndofLine' ); #DOESN'T SEEM TO WORK?!?!?!
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/)
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             foreach my $d (@INC) {
429             my @fl = <$d/Tk/TextHighlight/*.pm>;
430             foreach my $file (@fl) {
431             my ($name, $path, $suffix) = fileparse($file, "\.pm");
432             if (($name ne 'None') and ($name ne 'Template')) {
433             #avoid duplicates
434             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
435             }
436             }
437             }
438             return sort @ml;
439             }
440              
441             sub highlightPurge {
442             my ($cw, $line) = @_;
443             $cw->configure('-colored' => $line);
444             my $cli = $cw->cget('-colorinf');
445             if (@$cli) { splice(@$cli, $line) };
446             $cw->highlightVisual;
447             }
448              
449             sub highlightVisual {
450             my $cw = shift;
451             return if ($blockHighlight);
452             my $end = $cw->visualend;
453             my $col = $cw->cget('-colored');
454             if ($col < $end) {
455             $col = $cw->highlight($col, $end);
456             $cw->configure(-colored => $col);
457             };
458             $cw->matchCheck;
459             }
460              
461             sub insert {
462             my $cw = shift;
463             my $pos = shift;
464             $pos = $cw->index($pos);
465             my $begin = $cw->linenumber("$pos - 1 chars");
466             $cw->SUPER::insert($pos, @_);
467             $cw->highlightCheck($begin, $cw->linenumber("insert lineend"));
468             }
469              
470             sub Insert {
471             my $cw = shift;
472             $cw->SUPER::Insert(@_);
473             $cw->see('insert');
474             }
475              
476             sub InsertKeypress {
477             my ($cw,$char) = @_;
478             if ($char ne '') {
479             my $index = $cw->index('insert');
480             my $line = $cw->linenumber($index);
481             if ($char =~ /^\S$/ and !$cw->OverstrikeMode and !$cw->tagRanges('sel')) {
482             my $undo_item = $cw->getUndoAtIndex(-1);
483             if (defined($undo_item) &&
484             ($undo_item->[0] eq 'delete') &&
485             ($undo_item->[2] == $index)
486             ) {
487             $cw->Tk::Text::insert($index,$char);
488             $undo_item->[2] = $cw->index('insert');
489             $cw->highlightCheck($line, $line);
490             $cw->see('insert'); #ADDED 20060703 TO ALLOW USER TO SEE WHAT HE'S TYPING PAST END OF LINE (THIS IS BROKEN IN TEXTUNDO TOO).
491             return;
492             }
493             }
494             $cw->addGlobStart;
495             $cw->Tk::Text::InsertKeypress($char);
496             $cw->addGlobEnd;
497             }
498             }
499              
500             sub linenumber {
501             my ($cw, $index) = @_;
502             if (not defined($index)) { $index = 'insert'; }
503             my $id = $cw->index($index);
504             my ($line, $pos ) = split(/\./, $id);
505             return $line;
506             }
507              
508             sub Load {
509             my $cw = shift;
510             my @r = $cw->SUPER::Load(@_);
511             $cw->highlightVisual;
512             return @r;
513             }
514              
515             sub matchCheck {
516             my $cw = shift;
517             my $c = $cw->get('insert', 'insert + 1 chars');
518             my $p = $cw->index('match');
519             if ($p ne '0.0') {
520             $cw->tagRemove('Match', $p, "$p + 1 chars");
521             $cw->markSet('match', '0.0');
522             $cw->markUnset('MyMatch');
523             }
524             if ($c) {
525             my $v = $cw->cget('-match');
526             my $p = index($v, $c);
527             if ($p ne -1) { #a character in '-match' has been detected.
528             my $count = 0;
529             my $found = 0;
530             if ($p % 2) {
531             my $m = substr($v, $p - 1, 1);
532             $cw->matchFind('-backwards', $c, $m,
533             $cw->index('insert'),
534             # $cw->index('@0,0'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
535             $cw->index('0.0'),
536             );
537             } else {
538             my $m = substr($v, $p + 1, 1);
539             # print "searching -forwards, $c, $m\n";
540             $cw->matchFind('-forwards', $c, $m,
541             $cw->index('insert + 1 chars'),
542             # $cw->index($cw->visualend . '.0 lineend'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
543             $cw->index('end'),
544             );
545             }
546             }
547             }
548             $cw->updateCall;
549             }
550              
551             sub matchFind {
552             my ($cw, $dir, $char, $ochar, $start, $stop) = @_;
553             #first of all remove a previous match highlight;
554             my $pattern = "\\$char|\\$ochar";
555             my $found = 0;
556             my $count = 0;
557             while ((not $found) and (my $i = $cw->search(
558             $dir, '-regexp', '-nocase', '--', $pattern, $start, $stop
559             ))) {
560             my $k = $cw->get($i, "$i + 1 chars");
561             # print "found $k at $i and count is $count\n";
562             if ($k eq $ochar) {
563             if ($count > 0) {
564             # print "decrementing count\n";
565             $count--;
566             if ($dir eq '-forwards') {
567             $start = $cw->index("$i + 1 chars");
568             } else {
569             $start = $i;
570             }
571             } else {
572             # print "Found !!!\n";
573             $cw->markSet('match', $i);
574             $cw->tagAdd('Match', $i, "$i + 1 chars");
575             $cw->markSet('MyMatch', $i);
576             $cw->tagRaise('Match');
577             $found = 1;
578             }
579             } elsif ($k eq $char) {
580             # print "incrementing count\n";
581             $count++;
582             if ($dir eq '-forwards') {
583             $start = $cw->index("$i + 1 chars");
584             } else {
585             $start = $i;
586             }
587             } elsif ($i eq $start) {
588             $found = 1;
589             }
590             }
591             }
592              
593             sub matchoptions {
594             my $cw = shift;
595             if (my $o = shift) {
596             my @op = ();
597             if (ref($o)) {
598             @op = @$o;
599             } else {
600             @op = split(/\s+/, $o);
601             }
602             $cw->tagConfigure('Match', @op);
603             }
604             }
605              
606              
607             sub PostPopupMenu {
608             my $cw = shift;
609             my @r;
610             if (not $cw->cget('-disablemenu')) {
611             @r = $cw->SUPER::PostPopupMenu(@_);
612             }
613             }
614              
615             sub rulesConfigure {
616             my $cw = shift;
617             if (my $plug = $cw->Subwidget('formatter')) {
618             my $rules = $plug->rules;
619             my @r = @$rules;
620             foreach my $k (@r) {
621             $cw->tagConfigure(@$k);
622             };
623             $cw->configure(-colored => 1, -colorinf => [[ $plug->stateGet]]);
624             }
625             }
626              
627             sub setRule #ADDED 20060530 JWT TO PERMIT CHANGING INDIVIDUAL RULES.
628             {
629             my $cw = shift;
630             my @rule = @_;
631              
632             if (my $plug = $cw->Subwidget('formatter'))
633             {
634             my $rules = $plug->rules;
635             my @r = @$rules;
636             for (my $k=0;$k<=$#r;$k++)
637             {
638             if ($rule[0] eq $r[$k]->[0])
639             {
640             @{$r[$k]} = @rule;
641             }
642             };
643             $cw->configure(-rules => \@r);
644             }
645             }
646              
647             sub rulesDelete {
648             my $cw = shift;
649             if (my $plug = $cw->Subwidget('formatter')) {
650             my $rules = $plug->rules;
651             foreach my $r (@$rules) {
652             $cw->tagDelete($r->[0]);
653             }
654             }
655             }
656              
657              
658             sub rulesEdit {
659             my $cw = shift;
660             require Tk::RulesEditor;
661             $cw->RulesEditor(
662             -class => 'Toplevel',
663             );
664             }
665              
666             sub rulesFetch {
667             my $cw = shift;
668             my $dir = $cw->cget('-rulesdir');
669             my $syntax = $cw->cget('-syntax');
670             $cw->configure(-rules => undef);
671             # print "rulesFetch called\n";
672             my $result = 0;
673             if ($dir and (-e "$dir/$syntax.rules")) {
674             my $file = "$dir/$syntax.rules";
675             # print "getting $file\n";
676             if (my $rl = retrieve("$dir/$syntax.rules")) {
677             # print "configuring\n";
678             $cw->configure(-rules => $rl);
679             $result = 1;
680             }
681             }
682             return $result;
683             }
684              
685             sub rulesSave {
686             my $cw = shift;
687             my $dir = $cw->cget('-rulesdir');
688             # print "rulesSave called\n";
689             if ($dir) {
690             my $syntax = $cw->cget('-syntax');
691             my $file = "$dir/$syntax.rules";
692             store($cw->cget('-rules'), $file);
693             }
694             }
695              
696             sub scan {
697             my $cw = shift;
698             my @r = $cw->SUPER::scan(@_);
699             $cw->highlightVisual;
700             return @r;
701             }
702              
703             sub selectionModify {
704             my ($cw, $char, $mode) = @_;
705             my @ranges = $cw->tagRanges('sel');
706             if (@ranges eq 2) {
707             my $start = $cw->index($ranges[0]);
708             my $end = $cw->index($ranges[1]);
709             # print "doing from $start to $end\n";
710             while ($cw->compare($start, "<", $end)) {
711             # print "going to do something\n";
712             if ($mode) {
713             if ($cw->get("$start linestart", "$start linestart + 1 chars") eq $char) {
714             $cw->delete("$start linestart", "$start linestart + 1 chars");
715             }
716             } else {
717             $cw->insert("$start linestart", $char)
718             }
719             $start = $cw->index("$start + 1 lines");
720             }
721             $cw->tagAdd('sel', @ranges);
722             }
723             }
724              
725             sub selectionComment {
726             my $cw = shift;
727             $cw->selectionModify($cw->cget('-commentchar'), 0);
728             }
729              
730             sub selectionIndent {
731             my $cw = shift;
732             $cw->selectionModify($cw->cget('-indentchar'), 0);
733             }
734              
735             sub selectionUnComment {
736             my $cw = shift;
737             $cw->selectionModify($cw->cget('-commentchar'), 1);
738             }
739              
740             sub selectionUnIndent {
741             my $cw = shift;
742             $cw->selectionModify($cw->cget('-indentchar'), 1);
743             }
744              
745             sub syntax {
746             my $cw = shift;
747             if (@_) {
748             my $name = shift;
749             my $fm;
750             eval ("require Tk::TextHighlight::$name; \$fm = new Tk::TextHighlight::$name(\$cw);");
751             $cw->Advertise('formatter', $fm);
752             $cw->configure('-langname' => $name);
753             }
754             return $cw->cget('-langname');
755             }
756              
757             sub yview {
758             my $cw = shift;
759             my @r = ();
760             if (@_) {
761             @r = $cw->SUPER::yview(@_);
762             if ($_[1] > 0) { #ONLY RE-HIGHLIGHT IF SCROLLING DOWN (PREV. LINES ALREADY HIGHLIGHTED)!
763             my ($p) = caller;
764             $nodoEvent = 1 if ($p =~ /scroll/io); #THIS PREVENTS REPEATING (RUN-AWAY) SCROLLING!
765             $cw->highlightVisual;
766             }
767             } else {
768             @r = $cw->SUPER::yview;
769             }
770             return @r;
771             }
772              
773             sub see {
774             my $cw = shift;
775             my @r = $cw->SUPER::see(@_);
776             $cw->highlightVisual;
777             return @r
778             }
779              
780             sub updateCall {
781             my $cw = shift;
782             my $call = $cw->cget('-updatecall');
783             &$call;
784             $nodoEvent = 0;
785             }
786              
787             sub ViewMenuItems {
788             my $cw = shift;
789             my $s;
790             tie $s,'Tk::Configure',$cw,'-syntax';
791             my @stx = ('None', $cw->highlightPlugList);
792             my @rad = (['command' => 'Reset', -command => sub {
793             $cw->configure('-rules' => undef);
794             $cw->highlightPlug;
795             }]);
796             foreach my $n (@stx) {
797             push(@rad, [
798             'radiobutton' => $n,
799             -variable => \$s,
800             -value => $n,
801             -command => sub {
802             $cw->configure('-rules' => undef);
803             $cw->highlightPlug;
804             }
805             ]);
806             }
807             my $dir = $cw->cget('-rulesdir');
808             my $syntax = $cw->cget('-syntax');
809             my $menuExt = \@{$cw->SUPER::ViewMenuItems};
810             unless ($cw->cget('-noRulesMenu'))
811             {
812             push (@{$menuExt},
813             ['cascade'=>'Syntax',
814             -menuitems => [@rad],
815             ]) unless ($cw->cget('-noSyntaxMenu'));
816             push (@{$menuExt},
817             ['command'=>'Rules Editor',
818             -command => sub { $cw->rulesEdit },
819             ]) unless ($cw->cget('-noRulesEditMenu'));
820             push (@{$menuExt},
821             ['command'=>'Save Rules',
822             -command => sub { $cw->rulesSave },
823             ]) if (!$cw->cget('-noSaveRulesMenu') && $dir
824             && (-w $dir));
825             }
826             return $menuExt;
827             }
828              
829             sub visualend {
830             my $cw = shift;
831             my $end = $cw->linenumber('end - 1 chars');
832             my ($first, $last) = $cw->Tk::Text::yview;
833             my $vend = int($last * $end) + 2;
834             if ($vend > $end) {
835             $vend = $end;
836             }
837             return $vend;
838             }
839              
840             sub fetchKateInfo #FETCH LISTS OF KATE LANGUAGES AND FILE EXTENSION PATTERNS W/O KATE:
841             {
842             #IT IS NECESSARY TO FETCH THIS INFORMATION W/O USING KATE METHODS SINCE WE MAY NOT
843             #HAVE CREATED A KATE OBJECT WHEN THIS IS NEEDED!
844             #We return 3 hash-references: 1st can be passed to addkate2viewmenu() to add the
845             #Kate languages to the Syntax.View menu. the keys are "Kate::language" and the
846             #values are what's needed to instantiate Kate for that language. the 2nd is
847             #a list of file-extension pattern suitable for matching against file-names and
848             #the values are the reccomended Kate language for that file-extension.
849              
850             my $cw = shift;
851              
852             my (%sectionHash, %extHash, %syntaxHash);
853              
854             foreach my $i (@INC)
855             {
856             if (-e "$i/Syntax/Highlight/Engine/Kate.pm"
857             && open KATE, "$i/Syntax/Highlight/Engine/Kate.pm")
858             {
859             my $inExtensions = 0;
860             my $inSyntaxes = 0;
861             my $inSections = 0;
862             while ()
863             {
864             chomp;
865             $inExtensions = 1 if (/\$self\-\>\{\'extensions\'\}\s*\=\s*\{/o);
866             $inSections = 1 if (/\$self\-\>\{\'sections\'\}\s*\=\s*\{/o);
867             $inSyntaxes = 1 if (/\$self\-\>\{\'syntaxes\'\}\s*\=\s*\{/o);
868             if ($inSections)
869             {
870             if (/\'([^\']+)\'\s*\=\>\s*\[/o)
871             {
872             $inSections = $1;
873             @{$sectionHash{$inSections}} = ();
874             }
875             elsif (/\'([^\']+)\'\s*\,/o)
876             {
877             push (@{$sectionHash{$inSections}}, $1);
878             }
879             elsif (/\}\;/o)
880             {
881             $inSections = 0;
882             }
883             }
884             elsif ($inExtensions)
885             {
886             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/)
887             {
888             my $one = '^'.$1.'$';
889             my $two = $2;
890             $one =~ s/\./\\\./o;
891             $one =~ s/\*/\.\*/go;
892             $extHash{$one} = "Kate::$two";
893             }
894             elsif (/\}\;/o)
895             {
896             $inExtensions = 0;
897             }
898             }
899             elsif ($inSyntaxes)
900             {
901             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/)
902             {
903             $syntaxHash{$1} = $2;
904             }
905             elsif (/\}\;/o)
906             {
907             $inSyntaxes = 0;
908             close KATE;
909             last;
910             }
911             }
912             }
913             close KATE;
914             last;
915             }
916             }
917             return (\%sectionHash, \%extHash, \%syntaxHash);
918             }
919              
920             sub addKate2ViewMenu #ADD ALL KATE-LANGUAGES AS OPTIONS TO THE "View" MENU:
921             {
922             my $cw = shift;
923             my $sectionHash = shift;
924              
925             return undef if ($cw->cget('-noRulesMenu') || $cw->cget('-noSyntaxMenu'));
926              
927             my $ViewSyntaxMenu = $cw->menu->entrycget('View','-menu')->entrycget('Syntax','-menu');
928             my $lastMenuIndex = $ViewSyntaxMenu->index('end');
929              
930             #WE MUST FETCH THE VARIABLE REFERENCE USED BY THE "View" MENU RADIO-BUTTONS SO
931             #THAT OUR NEW RADIO BUTTONS SHARE SAME VARIABLE (OTHERWISE, WILL HAVE >1 LIT AT
932             #SAME TIME!
933              
934             my $var;
935             my $kateIndx = 'end';
936             foreach my $i (0..$lastMenuIndex)
937             {
938             if ($ViewSyntaxMenu->type($i) =~ /radiobutton/)
939             {
940             $var = $ViewSyntaxMenu->entrycget($i, '-variable');
941             tie $$var,'Tk::Configure',$cw,'-syntax';
942             if ($ViewSyntaxMenu->entrycget($i, '-label') eq 'Kate')
943             {
944             $ViewSyntaxMenu->delete($i); #REMOVE THE "Kate" ENTRY, SINCE WE'RE ADDING KATE STUFF SEPARATELY!
945             $kateIndx = $i; #SAVE IT'S MENU-LOCATION SO WE CAN INSERT THE KATE MENU TREE THERE.
946             last;
947             }
948             }
949             }
950              
951             #NOW ADD OUR "KATE" RADIO-BUTTONS!
952              
953             my ($nextMenu, $menuTitle);
954             foreach my $sect (sort keys %{$sectionHash})
955             {
956             $nextMenu = $ViewSyntaxMenu->Menu;
957             foreach my $lang (@{$sectionHash->{$sect}})
958             {
959             $menuTitle = "Kate::$lang";
960             $nextMenu->radiobutton( -label => $menuTitle,
961             -variable => $var,
962             -value => $menuTitle,
963             -command => sub
964             {
965             $cw->configure('-rules' => undef);
966             $cw->highlightPlug;
967             }
968             );
969             }
970             $ViewSyntaxMenu->insert($kateIndx, 'cascade', -label => "Kate: $sect...",
971             -menu => $nextMenu);
972             ++$kateIndx if ($kateIndx =~ /^\d/o);
973             }
974             }
975              
976             sub insertTab
977             {
978             my ($w) = @_;
979             # $w->Insert("\t");
980             $w->Insert($w->cget('-indentchar'));
981             $w->focus;
982             $w->break
983             }
984              
985             sub insertTabChar
986             {
987             my ($w) = @_;
988             $w->Insert("\t");
989             $w->focus;
990             $w->break
991             }
992              
993             1;
994              
995             __END__