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             =head1 NAME
2              
3             Tk::TextHighlight - a TextUndo/SuperText widget with syntax highlighting capabilities, can also use Kate languages.
4              
5             Tk::ROTextHighlight - a Read-only version of this widget.
6              
7             =head1 SYNOPSIS
8              
9             =over 4
10              
11             use Tk;
12             my $haveKateInstalled = 0;
13             eval "use Syntax::Highlight::Engine::Kate; \$haveKateInstalled = 1; 1";
14              
15             require Tk::TextHighlight;
16              
17             my $m = new MainWindow;
18              
19             my $e = $m->Scrolled("TextHighlight",
20             -syntax => "Perl",
21             -scrollbars => "se",
22             )->pack(-expand => 1, -fill => "both");
23              
24             if ($haveKateInstalled) {
25             my ($sections, $kateExtensions) = $e->fetchKateInfo;
26             $e->addKate2ViewMenu($sections);
27             }
28             $m->configure(-menu => $e->menu);
29             $m->MainLoop;
30              
31             =back
32              
33             =head1 DESCRIPTION
34              
35             Tk::TextHighlight inherits Tk::TextUndo and all its options and methods. Besides
36             syntax highlighting, methods are provided for commenting and uncommenting
37             as well as indenting and unindenting a selected area, matching pairs of braces, brackets and
38             brackets and curlies and automatic indenting of new lines. The included companion
39             module B provides all the same functionality in a "readonly"
40             widget for text viewers, etc. B also supports highlighting of all
41             the lauguages of the B, if that module is installed.
42              
43             If you want your widget to be read-only, then B, which
44             is based on B instead of B.
45              
46             Syntax highlighting is done through a plugin approach. Adding languages
47             is a matter of writing plugin modules. Theoretically this is not limited to programming languages.
48             The plugin approach could also provide the possibility for grammar or spell checking in spoken
49             languages.
50              
51             Currently there is support for B, B, B, B, B, and B.
52              
53             =head1 OPTIONS
54              
55             =over 4
56              
57             =item Name: B
58              
59             =item Class: B
60              
61             =item Switch: B<-autoindent>
62              
63             Boolean, when you press the enter button, should the next line begin at the
64             same position as the current line or not. By default B.
65              
66             =item Name: B
67              
68             =item Class: B
69              
70             =item Switch: B<-commentchar>
71              
72             By default "#".
73              
74             =item Name: B
75              
76             =item Class: B
77              
78             =item Switch: B<-disablemenu>
79              
80             Boolean, by default 0. In case you don't want the menu under the
81             right mouse button to pop up.
82              
83             =item Name: B
84              
85             =item Class: B
86              
87             =item Switch: B<-highlightInBackground>
88              
89             Whether or not to do highlighting in background freeing up the mouse and
90             keyboard for most events (experimental). May be 1 or 0. Default 0 (Do not
91             highlight in background - block input until highlighting completed).
92              
93             =item Name: B
94              
95             =item Class: B
96              
97             =item Switch: B<-indentchar>
98              
99             String to be inserted when the [Tab] key is pressed or when indenting.
100             Default "\t".
101              
102             =item Name: B
103              
104             =item Class: B
105              
106             =item Switch: B<-match>
107              
108             string of pairs for brace/bracket/curlie etc matching. If this description
109             doesn't make anything clear, don't worry, the default setting will:
110              
111             '[]{}()'
112              
113             if you don't want matching to be available, simply set it to ''.
114              
115             =item Name: B
116              
117             =item Class: B
118              
119             =item Switch: B<-matchoptions>
120              
121             Options list for the tag 'Match'. By default:
122              
123             [-background => 'red', -foreground => 'yellow']
124              
125             You can also specify this option as a space separated string. Might come in
126             handy for your Xresource files.
127              
128             "-background red -foreground yellow"
129              
130             =item Name: not available
131              
132             =item Class: not available
133              
134             =item Switch B<-rules>
135              
136             Specify the color and font options for highlighting. You specify a list
137             looking a bit like this.
138              
139             [
140             ['Tagname1', @options1],
141             ['Tagname2', @options2],
142             ]
143              
144             The names of the tags are depending on the syntax that is highlighted.
145             See the language modules for more information about this data structure.
146              
147             =item Name: rulesdir
148              
149             =item Class: Rulesdir
150              
151             =item Switch B<-rulesdir>
152              
153             Specify the directory where this widget stores its coloring defenitions.
154             Files in this directory are stored as "HTML.rules", "Perl.rules" etc.
155             By default it is set to '', which means that when you switch syntax
156             the highlighting rules are not loaded or stored. The hard coded defaults
157             in the language modules will be used.
158              
159             =item Name: B
160              
161             =item Class: B
162              
163             =item Switch: B<-syntax>
164              
165              
166             Specifies the language for highlighting. At this moment the possible
167             values are B, B, B, B B, and B.
168             By default B
169              
170             If B is installed, you may specify any language
171             that the B syntax highlight engine supports.
172              
173             Alternatively it is possible to specify a reference to your independent plugin.
174              
175             =item Name: Not available
176              
177             =item Class: Not available
178              
179             =item Switch: B<-updatecall>
180              
181             Here you can specify a callback that will be executed whenever the insert
182             cursor has moved or text has been modified, so your application can keep
183             track of position etc. Don't make this callback to heavy, the widget will
184             get sluggish quickly.
185              
186             =item Name: Not available
187              
188             =item Class: Not available
189              
190             =item Switch: B<-noPlugInit>
191              
192             Disables TextHighlight feature of initializing default rules when no B<.rules> file present.
193              
194             =item Name: Not available
195              
196             =item Class: Not available
197              
198             =item Switch: B<-noSyntaxMenu>
199              
200             Don't show the B submenu option in the B submenu of the right-click menu.
201              
202             =item Name: Not available
203              
204             =item Class: Not available
205              
206             =item Switch: B<-noSaveRulesMenu>
207              
208             Don't show the B submenu option in the B submenu of the right-click menu.
209              
210             =item Name: Not available
211              
212             =item Class: Not available
213              
214             =item Switch: B<-noRulesEditMenu>
215              
216             Don't show the B option in the B submenu of the right-click menu.
217              
218             =item Name: Not available
219              
220             =item Class: Not available
221              
222             =item Switch: B<-noRulesMenu>
223              
224             Don't show any of the TextHighlight menu items (combines B<-noSyntaxMenu>,
225             B<-noRulesEditMenu>, and B<-noSaveRulesMenu> options.
226              
227             =back
228              
229             There are some undocumented options. They are used internally.
230             It is propably best to leave them alone.
231              
232             =cut
233              
234             =head1 METHODS
235              
236             =over 4
237              
238             =item B
239              
240             Checks the indention of the previous line and indents
241             the line where the cursor is equally deep.
242              
243             =item B(I<$begin>, I<$end>);
244              
245             Does syntax highlighting on the section of text indicated by $begin and $end.
246             $begin and $end are linenumbers not indexes!
247              
248             =item B>(I<$begin>, I<$end>);
249              
250             An insert or delete has taken place affecting the section of text between $begin and $end.
251             B is being called after and insert or delete operation. $begin and $end (again
252             linenumbers, not indexes) indicate the section of text affected. B checks what
253             needs to be highlighted again and does the highlighting.
254              
255             =item B(I<$line>);
256              
257             Does syntax highlighting on linenumber $line.
258              
259             =item B
260              
261             Checks wether the appropriate highlight plugin has been loaded. If none or the wrong
262             one is loaded, it loads the correct plugin. It returns a reference to the plugin loaded.
263             It also checks wether the rules have changed. If so, it restarts highlighting
264             from the beginning of the text.
265              
266             =item B
267              
268             Loads and initalizes a highlighting plugin. First it checks the value of the B<-syntax> option
269             to see which plugin should be loaded. Then it checks wether a set of rules is defined to this plugin
270             in the B<-rules> option. If not, it tries to obtain a set of rules from disk using B.
271             If this fails as well it will use the hardcoded rules from the syntax plugin.
272              
273             =item B(I<$line>);
274              
275             Tells the widget that the text from linenumber $line to the end of the text is not to be considered
276             highlighted any more.
277              
278             =item B
279              
280             Calls B to see what part of the text is visible on the display, and adjusts highlighting
281             accordingly.
282              
283             =item B(I<$index>);
284              
285             Returns the linenumber part of an index. You may also specify indexes like 'end' or 'insert' etc.
286              
287             =item B
288              
289             Checks wether the character that is just before the 'insert'-mark should be matched, and if so
290             should it match forwards or backwards. It then calls B.
291              
292             =item B(I<$direction>, I<$char>, I<$match>, I<$start>, I<$stop>);
293              
294             Matches $char to $match, skipping nested $char/$match pairs, and displays the match found (if any).
295              
296             =item B
297              
298             Pops up a window that enables the user to set the color and font options
299             for the current syntax.
300              
301             =item B
302              
303             Checks wether the file
304              
305             $text->cget('-rulesdir') . '/' . $text->cget('-syntax') . '.rules'
306              
307             exists, and if so attempts to load this as a set of rules.
308              
309             =item B
310              
311             Saves the currently loaded rules as
312              
313             $text->cget('-rulesdir') . '/' . $text->cget('-syntax') . '.rules'
314              
315             =item B
316              
317             Comment currently selected text.
318              
319             =item B
320              
321             Indent currently selected text.
322              
323             =item B
324              
325             Used by the other B methods to do the actual work.
326              
327             =item B
328              
329             Uncomment currently selected text.
330              
331             =item B
332              
333             Unindent currently selected text.
334              
335             =item B
336              
337             Allows altering of individual rules by the programmer.
338              
339             =item B
340              
341             Fetches 3 hashrefs containing information about the installed Kate highlight
342             engine (if installed). The three hashrefs contain in order: The first can be
343             passed to the B method to add the B languages to the
344             Syntax.View menu. the keys are "Kate::language" and the values are what's
345             needed to instantiate Kate for that language. the 2nd is a list of file-
346             extension pattern suitable for matching against file-names and the values are
347             the reccomended Kate language for that file-extension. It will return
348             B<(undef, undef, undef)> if B is not installed.
349              
350             =item B
351              
352             Inserts the list of B-supported languages to the widget's Syntax.View
353             right-mousebutton popup menu along with the basic TextHight-supported choices.
354             These choices can then be selected to change the current language-highlighting
355             used in the text in the widget. B<$sections> is a hash-ref normally returned
356             as the 1st item in the list returned by B. NOTE: No menu
357             items will be added if B is not installed or if B<-noRulesMenu> or
358             B<-noSyntaxMenu> is set!
359              
360             =back
361              
362             =head1 SYNTAX HIGHLIGHTING
363              
364             This section is a brief description of how the syntax highlighting process
365             works.
366              
367             B
368              
369             The highlighting plugin is only then initiated when it is needed. When some
370             highlighting needs to be done, the widget calls B to retrieve
371             a reference to the plugin.
372              
373             B checks wether a plugin is present. Next it will check whether
374             the B<-rules> option has been specified or wether the B<-rules> option has changed.
375             If no rules are specified in B<-rules>, it will look for a pathname
376             in the B<-rulesdir> option. If that is found it will try to load a file
377             called '*.rules', where * is the value of B<-syntax>.
378              
379             If no plugin is present, or the B<-syntax> option has changed value,
380             B loads the plugin. and constructs optionally giving it
381             a reference to the found rules as parameter. if no rules
382             are specified, the plugin will use its internal hardcoded defaults.
383              
384             B
385              
386             A set of rules is a list, containing lists of tagnames, followed by options.
387             If you want to see what they look like, you can have a look at the constructors
388             of each plugin module. Every plugin has a fixed set of tagnames it can handle.
389              
390             There are two ways to change the rules.
391              
392             You can invoke the B method, which is also available through the
393             B menu. The result is a popup in which you can specify color and font
394             options for each tagname. After pressing 'Ok', the edited rules will be applied.
395             If B<-rulesdir> is specified, the rules will be saved on disk as
396             I.
397              
398             You can also use B to specify a new set of rules. In this you have
399             ofcause more freedom to use all available tag options. For more details about
400             those there is a nice section about tag options in the Tk::Text documentation.
401             After the call to B it is wise to call B.
402              
403             B
404              
405             Syntax highlighting is done in a lazy manor. Only that piece of text is
406             highlighted that is needed to present the user a pretty picture. This is
407             done to minimize use of system resources. Highlighting is running on the
408             foreground. Jumping directly to the end of a long fresh loaded textfile may
409             very well take a couple of seconds.
410              
411             Highlighting is done on a line to line basis. At the end of each line the
412             highlighting status is saved in the list in B<-colorinf>, so when highlighting
413             the next line, the B method of B will know how to begin.
414              
415             The line that needs highlighting is offered to the B method of
416             the plugin. This method returns a list of offset and tagname pairs.
417             Take for example the following line of perl code.
418              
419             my $mother = 'older than i am';
420              
421             The B method of the Perl plugin will return the following list;
422              
423             (2 => 'Reserved', #'my' is a reserved word
424             1 => 'DEFAULT', #Space
425             7 => 'Variable', #$mother
426             1 => 'DEFAULT', #Space
427             1 => 'Operator', #'='
428             1 => 'DEFAULT', #Space
429             17 => 'String', #'older than i am'
430             1 => 'DEFAULT',) #;
431              
432             The B method of TextHighlight will then mark positions 0 to 2 as
433             'Reserved', positions 2 to 3 as 'DEFAULT', positions 3 to 10 as 'Variable',
434             etcetera.
435              
436             =cut
437              
438             =head1 WRITING PLUGINS
439              
440             After writing a couple of plugins myself i have come to a couple of guidelines
441             about how to set them up. If you are interested in adding support for your
442             own syntax highlighting problem or language this section is of interest to you.
443              
444             B
445              
446             If you choose to build a plugin completely from scratch, your module needs
447             to meet the following requirements.
448              
449             - If you want to write a formal addition to Tk::TextHighlight,
450             your plugin must be in the namespace
451             Tk::TextHighlight::YourSyntax.
452             - The constructor is called 'new', and it should accept
453             a reference a reference to a list of rules as parameters.
454             - The following methods will be called upon by Tk::TextHighlight:
455             highlight, stateCompare, rules, setSate,
456             getState, syntax.
457              
458             More information about those methods is available in the documentation of
459             Tk::TextHighlight::None and Tk::TextHighlight::Template. Good luck, you're on your own now.
460              
461             B
462              
463             For many highlighting problems Tk::TextHighlight::Template
464             provides a nice basis to start from. Your code
465             could look like this:
466              
467             package Tk::TextHighlight::MySyntax;
468            
469             use strict;
470             use base('Tk::TextHighlight::Template');
471            
472             sub new {
473             my ($proto, $wdg, $rules) = @_;
474             my $class = ref($proto) || $proto;
475              
476             Next, specify the set of hardcoded rules.
477              
478             if (not defined($rules)) {
479             $rules = [
480             ['Tagname1', -foreground => 'red'],
481             ['Tagname1', -foreground => 'red'],
482             ];
483             };
484              
485             Call the constructor of Tk::TextHighlight::Template and bless your
486             object.
487              
488             my $self = $class->SUPER::new($rules);
489              
490             So now we have the SUPER class avalable and we can start defining
491             a couple of things.
492              
493             You could add a couple of lists, usefull for keywords etc.
494              
495             $self->lists({
496             'Keywords' => ['foo', 'bar'],
497             'Operators' => ['and', 'or'],
498             });
499              
500             For every tag you have to define a corresponding callback like this.
501              
502             $self->callbacks({
503             'Tagname1' => \&Callback1,
504             'Tagname2' => \&Callback2,
505             });
506              
507             You have to define a default tagname like this:
508              
509             $self->stackPush('Tagname1');
510              
511             Perhaps do a couple of other things but in the end, wrap up the new method.
512              
513            
514             bless ($self, $class);
515             return $self;
516             }
517              
518             Then you need define the callbacks that are mentioned in the B
519             hash. When you just start writing your plugin i suggest you make them look
520             like this:
521              
522             sub callback1 {
523             my ($self $txt) = @_;
524             return $self->parserError($txt); #for debugging your later additions
525             }
526              
527             Later you add matching statements inside these callback methods. For instance,
528             if you want I to parse spaces it is going to look like this:
529              
530              
531             sub callback1 {
532             my ($self $txt) = @_;
533             if ($text =~ s/^(\s+)//) { #spaces
534             $self->snippetParse($1, 'Tagname1'); #the tagname here is optional
535             return $text;
536             }
537             return $self->parserError($txt); #for debugging your later additions
538             }
539              
540             If I is the callback that is called by default, you have to add
541             the mechanism for checking lists to it. Hnce, the code will look like this:
542              
543             sub callback1 {
544             my ($self $txt) = @_;
545             if ($text =~ s/^(\s+)//) { #spaces
546             $self->snippetParse($1, 'Tagname1'); #the tagname here is optional
547             return $text;
548             }
549             if ($text =~ s/^([^$separators]+)//) { #fetching a bare part
550             if ($self->tokenTest($1, 'Reserved')) {
551             $self->snippetParse($1, 'Reserved');
552             } elsif ($self->tokenTest($1, 'Keyword')) {
553             $self->snippetParse($1, 'Keyword');
554             } else { #unrecognized text
555             $self->snippetParse($1);
556             }
557             return $text
558             }
559             return $self->parserError($txt); #for debugging your later additions
560             }
561              
562             Have a look at the code of Tk::TextHighlight::Bash. Things should clear up.
563             And then, last but not least, you need a B method.
564              
565             B
566              
567             An example of this approach is the Perl syntax module.
568              
569             Also with this approach you will have to meet the minimum criteria
570             as set out in the B section.
571              
572             =cut
573              
574             =head1 CONTRIBUTIONS
575              
576             If you have written a plugin, i will be happy to include it in the next release
577             of Tk::TextHighlight. If you send it to me, please have it accompanied with the
578             sample of code that you used for testing.
579              
580             =head1 AUTHOR
581              
582             =over 4
583              
584             =item Jim Turner (turnerjw784 - att.symbol.thingy - yahoo.com).
585              
586             This is a derived work from Tk::CodeText, by Hans Jeuken (haje - att.symbol.thingy - toneel.demon.nl)
587              
588             Thanks go to Mr. Hans Jeuken for his great work in making this and the Kate
589             modules possible. He did the hard work!
590              
591             =back
592              
593             =cut
594              
595             =head1 BUGS
596              
597             Unknown. If you find any, please contact the author.
598              
599             =cut
600              
601             =head1 TODO
602              
603             =over 4
604              
605             =item Add additional language modules. I am going to need help on this one. We currently
606             support all the original B languages (included) plus all those supported by
607             B, if it's installed.
608              
609             =item The sample files in the test suite should be set up so that conformity
610             with the language specification can actually be verified.
611              
612             =back
613              
614             =cut
615              
616             =head1 SEE ALSO
617              
618             =over 4
619              
620             =item L, L, L, L, L,
621             L, L
622              
623             =back
624              
625             =cut
626              
627             package Tk::TextHighlight;
628              
629 1     1   21558 use vars qw($VERSION);
  1         3  
  1         66  
630             $VERSION = '1.1.1';
631 1     1   5 use base qw(Tk::Derived Tk::TextUndo);
  1         1  
  1         748  
632             use strict;
633             use Storable;
634             use File::Basename;
635              
636             my $blockHighlight = 0; #USED TO PREVENT RECURSIVE CALLS TO RE-HIGHLIGHT!
637             my $nodoEvent = 0; #USED TO PREVENT REPEATING (RUN-AWAY) SCROLLING!
638             Construct Tk::Widget 'TextHighlight';
639              
640             sub Populate {
641             my ($cw,$args) = @_;
642             $cw->SUPER::Populate($args);
643             $cw->ConfigSpecs(
644             -autoindent => [qw/PASSIVE autoindent Autoindent/, 0],
645             -match => [qw/PASSIVE match Match/, '[]{}()'],
646             -matchoptions => [qw/METHOD matchoptions Matchoptions/,
647             [-background => 'red', -foreground => 'yellow']],
648             -indentchar => [qw/PASSIVE indentchar Indentchar/, "\t"],
649             -disablemenu => [qw/PASSIVE disablemenu Disablemenu/, 0],
650             -commentchar => [qw/PASSIVE commentchar Commentchar/, "#"],
651             -colorinf => [qw/PASSIVE undef undef/, []],
652             -colored => [qw/PASSIVE undef undef/, 0],
653             -syntax => [qw/PASSIVE syntax Syntax/, 'None'],
654             -rules => [qw/PASSIVE undef undef/, undef],
655             -rulesdir => [qw/PASSIVE rulesdir Rulesdir/, ''],
656             -updatecall => [qw/PASSIVE undef undef/, sub {}],
657             -noRulesMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
658             -noSyntaxMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
659             -noRulesEditMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FEATURE.
660             -noSaveRulesMenu => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FOR BACKWARD COMPATABILITY.
661             -noPlugInit => [qw/PASSIVE undef undef/, 0], #JWT: ADDED FOR BACKWARD COMPATABILITY.
662             -highlightInBackground => [qw/PASSIVE undef undef/, 0], #JWT: SELF-EXPLANATORY.
663             DEFAULT => [ 'SELF' ],
664             );
665             $cw->bind('', sub { $cw->highlightVisual });
666             $cw->bind('', sub { $cw->doAutoIndent(0) });
667             $cw->bind('', sub { $cw->doAutoIndent(1) });
668             $cw->markSet('match', '0.0');
669             $cw->bind('', \&jumpToMatchingChar);
670             $cw->bind('', \&doShiftBackSpace); #DOESN'T SEEM TO WORK?!?!?!
671             $cw->bind('', \&deleteToEndofLine); #DOESN'T SEEM TO WORK?!?!?!
672             $cw->bind('', \&doShiftInsert); #DOESN'T SEEM TO WORK?!?!?!
673             }
674              
675             sub configure #ADDED 20081027 TO RE-CHECK RULE COLORS WHEN BACKGROUND CHANGES
676             {
677             my $cw = shift;
678             my $plug = $cw->Subwidget('formatter');
679             if ($plug)
680             {
681             for (my $i=0;$i<$#{_};$i++)
682             {
683             if ($_[$i] =~ /\-(?:bg|background)/o)
684             {
685             my $oldBg = $cw->cget($_[$i]);
686             unless ($_[$i+1] eq $oldBg)
687             {
688             #IF CHANGING BACKGROUND, MUST RESET RULE COLORS TO PREVENT
689             #COLOR CONTRAST ILLEGABILITIES!
690             $cw->SUPER::configure($_[$i] => $_[$i+1]);
691             $cw->configure('-rules' => undef);
692             $cw->highlightPlug;
693             last;
694             }
695             }
696             }
697             }
698             $cw->SUPER::configure(@_);
699             }
700              
701             sub jumpToMatchingChar #ADDED 20060630 JWT TO CAUSE ^p TO WORK LIKE VI & SUPERTEXT - JUMP TO MATCHING CHARACTER!
702             {
703             my $cw = shift;
704             $cw->markSet('insert', $cw->index('insert'));
705             my $pm = -1;
706             eval { $pm = $cw->index('MyMatch'); };
707             if ($pm >= 0)
708             {
709             my $prevMatch = $cw->index('insert');
710             $prevMatch .= '.0' unless ($prevMatch =~ /\./o);
711             $cw->markSet('insert', $cw->index('MyMatch'));
712             $cw->see('insert');
713             $cw->markSet('MyMatch', $prevMatch);
714             }
715             }
716              
717             sub doShiftBackSpace
718             {
719             my $cw = shift;
720             my $curPos = $cw->index('insert');
721             my $leftPos = $cw->index('insert linestart');
722             $cw->delete($leftPos, $curPos) unless ($curPos <= $leftPos);
723             }
724              
725             sub deleteToEndofLine
726             {
727             my ($cw) = @_;
728             if ($cw->compare('insert','==','insert lineend'))
729             {
730             $cw->delete('insert')
731             }
732             else
733             {
734             $cw->delete('insert','insert lineend')
735             }
736             }
737              
738             sub doShiftDelete
739             {
740             my $cw = shift;
741             (my $curPos = $cw->index('insert')) =~ s/\..*$//o;
742             my $startPos = ($curPos > 1) ? $cw->index('insert - 1 line lineend')
743             : $cw->index('1.0');
744             my $endPos = $cw->index('insert lineend');
745             $cw->delete($startPos, $endPos); # unless ($startPos <= $endPos);
746             }
747              
748             sub doShiftInsert
749             {
750             my $cw = shift;
751             my $insPos = $cw->index('insert lineend');
752             $cw->insert($insPos, "\n");
753             }
754              
755             sub ClassInit #JWT: ADDED FOR VI-LIKE Control-P JUMP TO MATCHING BRACKET FEATURE.
756             {
757             my ($class,$w) = @_;
758            
759             $class->SUPER::ClassInit($w);
760              
761             # reset default Tk::Text binds
762             $w->bind($class, '', sub {} );
763             $w->bind($class, '', 'insertTabChar' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
764             $w->bind($class, '', 'doShiftBackSpace' ); #DOESN'T SEEM TO WORK?!?!?!
765             $w->bind($class, '', 'doShiftDelete' );
766             $w->bind($class, '', 'deleteToEndofLine' );
767             $w->bind($class, '', 'deleteToEndofLine' ); #DOESN'T SEEM TO WORK?!?!?!
768             $w->bind($class, '', 'insertTab' ); #ADDED TO ALLOW INSERTION OF TABS OR SPACES!
769             $w->bind($class, '', 'doShiftBackSpace' );
770             return $class;
771             }
772              
773             sub clipboardCopy {
774             my $cw = shift;
775             my @ranges = $cw->tagRanges('sel');
776             if (@ranges) {
777             $cw->SUPER::clipboardCopy(@_);
778             }
779             }
780              
781             sub clipboardCut {
782             my $cw = shift;
783             my @ranges = $cw->tagRanges('sel');
784             if (@ranges) {
785             $cw->SUPER::clipboardCut(@_);
786             }
787             }
788              
789             sub clipboardPaste {
790             my $cw = shift;
791             my @ranges = $cw->tagRanges('sel');
792             if (@ranges) {
793             $cw->tagRemove('sel', '1.0', 'end');
794             return;
795             }
796             $cw->SUPER::clipboardPaste(@_);
797             }
798              
799             sub delete {
800             my $cw = shift;
801             my $begin = $_[0];
802             if (defined($begin)) {
803             $begin = $cw->linenumber($begin);
804             } else {
805             $begin = $cw->linenumber('insert');
806             };
807             my $end = $_[1];
808             if (defined($end)) {
809             $end = $cw->linenumber($end);
810             } else {
811             $end = $begin;
812             };
813             $cw->SUPER::delete(@_);
814             $cw->highlightCheck($begin, $end);
815             }
816              
817             sub doAutoIndent {
818             my $cw = shift;
819             my $doAutoIndent = shift;
820             return unless ($doAutoIndent);
821              
822             if ($cw->cget('-autoindent')) {
823             my $i = $cw->index('insert linestart');
824             if ($cw->compare($i, ">", '0.0')) {
825             my $s = $cw->get("$i - 1 lines", "$i - 1 lines lineend");
826             # if ($s =~ /\S/) #JWT: UNCOMMENT TO CAUSE SUBSEQUENT BLANK LINES TO NOT BE AUTOINDENTED.
827             # {
828             #$s =~ /^(\s+)/; #CHGD. TO NEXT 20060701 JWT TO FIX "e" BEING INSERTED INTO LINE WHEN AUTOINDENT ON?!
829             $s =~ /^(\s*)/o;
830             if ($1) {
831             $cw->insert('insert', $1);
832             }
833             $cw->insert('insert', $cw->cget('-indentchar'))
834             if ($s =~ /[\{\[\(]\s*$/o); #ADDED 20060701 JWT - ADD AN INDENTION IF JUST OPENED A BLOCK!
835             # }
836             }
837             }
838             }
839              
840             sub EditMenuItems {
841             my $cw = shift;
842             return [
843             @{$cw->SUPER::EditMenuItems},
844             "-",
845             ["command"=>'Comment', -command => [$cw => 'selectionComment']],
846             ["command"=>'Uncomment', -command => [$cw => 'selectionUnComment']],
847             "-",
848             ["command"=>'Indent', -command => [$cw => 'selectionIndent']],
849             ["command"=>'Unindent', -command => [$cw => 'selectionUnIndent']],
850             ];
851             }
852              
853             sub EmptyDocument {
854             my $cw = shift;
855             my @r = $cw->SUPER::EmptyDocument(@_);
856             $cw->highlightPurge(1);
857             return @r
858             }
859              
860             sub highlight {
861             my ($cw, $begin, $end) = @_;
862             # return $begin if ($blockHighlight); #PREVENT RECURSIVE CALLING WHILST ALREADY REHIGHLIGHTING!
863             $blockHighlight = 1;
864             if (not defined($end)) { $end = $begin + 1};
865             #save selection and cursor position
866             my @sel = $cw->tagRanges('sel');
867             # my $cursor = $cw->index('insert');
868             #go over the source code line by line.
869             while ($begin < $end) {
870             $cw->highlightLine($begin);
871             $begin++; #move on to next line.
872             };
873             #restore original cursor and selection
874             # $cw->markSet('insert', $cursor);
875             #1 if ($sel[0]) {
876             #1 $cw->tagRaise('sel'); #JWT:REMOVED 20060703 SO THAT HIGHLIGHTING STAYS ON SELECTED STUFF AFTER SELECTION MOVES OVER UNTAGGED TEXT.
877             #1 };
878             $blockHighlight = 0;
879             return $begin;
880             }
881              
882             sub highlightCheck {
883             my ($cw, $begin, $end) = @_;
884             my $col = $cw->cget('-colored');
885             my $cli = $cw->cget('-colorinf');
886             if ($begin <= $col) {
887             #The operation occurred in an area that was highlighted already
888             if ($begin < $end) {
889             #it was a multiline operation, so highlighting is not reliable anymore
890             #restart hightlighting from the beginning of the operation.
891             $cw->highlightPurge($begin);
892             } else {
893             #just re-highlight the modified line.
894             my $hlt = $cw->highlightPlug;
895             my $i = $cli->[$begin];
896             $cw->highlight($begin);
897             if (($col < $cw->linenumber('end')) and (not $hlt->stateCompare($i))) {
898             #the proces ended inside a multiline token. try to fix it.
899             $cw->highlightPurge($begin);
900             }
901             };
902             $cw->matchCheck;
903             } else {
904             $cw->highlightVisual;
905             }
906             }
907              
908             sub highlightLine {
909             my ($cw, $num) = @_;
910             my $hlt = $cw->highlightPlug;
911             my $cli = $cw->cget('-colorinf');
912             my $k = $cli->[$num - 1];
913             $hlt->stateSet(@$k);
914             # remove all existing tags in this line
915             my $begin = "$num.0"; my $end = $cw->index("$num.0 lineend");
916             my $rl = $hlt->rules;
917             foreach my $tn (@$rl) {
918             $cw->tagRemove($tn->[0], $begin, $end);
919             }
920             my $txt = $cw->get($begin, $end); #get the text to be highlighted
921             my @v;
922             if ($txt) { #if the line is not empty
923             my $pos = 0;
924             my $start = 0;
925             my @h = $hlt->highlight("$txt\n"); #JWT: ADDED "\n" TO MAKE KATE WORK!
926             while (@h ne 0) {
927             $start = $pos;
928             $pos += shift @h;
929             my $tag = shift @h;
930             $cw->tagAdd($tag, "$num.$start", "$num.$pos");
931             };
932             $cw->DoOneEvent(2) unless ($nodoEvent
933             || !$cw->cget('-highlightInBackground')); #DON'T PREVENT USER-INTERACTION WHILE RE-HILIGHTING!
934             };
935             $cli->[$num] = [ $hlt->stateGet ];
936             }
937              
938             sub highlightPlug {
939             my $cw = shift;
940             my $plug = $cw->Subwidget('formatter');
941             my $syntax = $cw->cget('-syntax');
942             $syntax =~ s/\:\:.*$//o;
943             my $rules = $cw->cget('-rules');
944             if (not defined($plug)) {
945             $plug = $cw->highlightPlugInit;
946             } elsif (ref($syntax)) {
947             if ($syntax ne $plug) {
948             $plug = $cw->highlightPlugInit;
949             }
950             } elsif ($syntax ne $plug->syntax) {
951             $cw->rulesDelete;
952             $plug = $cw->highlightPlugInit;
953             $cw->highlightPurge(1);
954             } elsif (defined($rules)) {
955             # if ($rules ne $plug->rules) { #JWT: CHGD TO NEXT TO PREVENT INFINITE RECURSION WHEN "None" HIGHLIGHTER IS USED!
956             if ($#{$rules} >= 0 && $rules ne $plug->rules) {
957             $cw->rulesDelete;
958             $plug->rules($rules);
959             $cw->rulesConfigure;
960             $cw->highlightPurge(1);
961             }
962             } else {
963             $cw->rulesDelete;
964             $cw->highlightPlugInit;
965             $cw->highlightPurge(1);
966             }
967             return $plug
968             }
969              
970             sub highlightPlugInit {
971             my $cw = shift;
972             my $syntax = $cw->cget('-syntax');
973             if (not defined($cw->cget('-rules'))) { $cw->rulesFetch };
974             my $plug;
975             my $lang = '';
976             if (ref($syntax)) {
977             $plug = $syntax;
978             } else {
979             $lang = $1 if ($syntax =~ s/\:\:(.*)$//o);
980             my @opt = ();
981             if (my $rules = $cw->cget('-rules')) {
982             push(@opt, $rules);
983             }
984             my $evalStr = "require Tk::TextHighlight::$syntax; \$plug = new Tk::TextHighlight::$syntax("
985             .($lang ? "'$lang', " : '') . "\@opt);";
986             eval $evalStr;
987             #JWT: ADDED UNLESS 20060703 TO PROPERLY INITIALIZE RULES FROM PLUGIN, IF NO .rules FILE DEFINED.
988             unless ($@ || !defined($plug) || !defined($plug->rules)
989             || $cw->cget('-noPlugInit'))
990             {
991             my $rules = $plug->rules;
992             $cw->configure(-rules => \@$rules);
993             }
994             }
995             $cw->Advertise('formatter', $plug);
996             $cw->rulesConfigure;
997             my $bg = $cw->cget(-background);
998             my ($red, $green, $blue) = $cw->rgb($bg); #JWT: NEXT 11 ADDED 20070802 TO PREVENT INVISIBLE TEXT!
999             my @rgb = sort {$b <=> $a} ($red, $green, $blue);
1000             my $max = $rgb[0]+$rgb[1]; #TOTAL BRIGHTEST 2.
1001             my $daytime = 1;
1002             my $currentrules = $plug->rules;
1003             if ($max <= 52500) { #IF BG COLOR IS DARK ENOUGH, FORCE RULES WITH NORMAL BLACK-
1004             $daytime = 0; #FOREGROUND TO WHITE TO AVOID COLOR CONTRAST ILLEGABILITIES.
1005             #print "-NIGHT 65!\n";
1006             for (my $k=0;$k<=$#{$currentrules};$k++)
1007             {
1008             if ($currentrules->[$k]->[2] eq 'black')
1009             {
1010             $cw->setRule($currentrules->[$k]->[0],$currentrules->[$k]->[1],'white');
1011             }
1012             };
1013             }
1014             for (my $k=0;$k<=$#{$currentrules};$k++)
1015             {
1016             if (defined($currentrules->[$k]->[2]) and $currentrules->[$k]->[2] eq $bg)
1017             {
1018             #RULE FOREGROUND COLOR == BACKGROUND, CHANGE TO BLACK OR WHITE TO KEEP READABLE!
1019             $cw->setRule($currentrules->[$k]->[0],$currentrules->[$k]->[1],($daytime ? 'black' : 'white'));
1020             }
1021             };
1022             $cw->update;
1023             unless ($cw->cget('-noSyntaxMenu')) #JWT: ADDED TO ENSURE VIEW RADIO-BUTTON PROPERLY INITIALIZED/SET.
1024             {
1025             my @kateMenus;
1026             my $ViewSyntaxMenu = $cw->menu->entrycget('View','-menu')->entrycget('Syntax','-menu');
1027             my $lastMenuIndex = $ViewSyntaxMenu->index('end');
1028              
1029             #WE MUST FETCH THE VARIABLE REFERENCE USED BY THE "View" MENU RADIO-BUTTONS SO
1030             #THAT OUR NEW RADIO BUTTONS SHARE SAME VARIABLE (OTHERWISE, WILL HAVE >1 LIT AT
1031             #SAME TIME!
1032              
1033             my $var;
1034             foreach my $i (0..$lastMenuIndex)
1035             {
1036             if ($ViewSyntaxMenu->type($i) =~ /radiobutton/o)
1037             {
1038             $var = $ViewSyntaxMenu->entrycget($i, '-variable');
1039             tie $$var,'Tk::Configure',$cw,'-syntax';
1040             unless (ref($syntax))
1041             {
1042             $$var = $lang ? ($syntax.'::'.$lang) : $syntax;
1043             }
1044             last;
1045             }
1046             }
1047             }
1048             return $plug;
1049             }
1050              
1051             sub highlightPlugList {
1052             my $cw = shift;
1053             my @ml = ();
1054             my $haveKate = 0;
1055             foreach my $d (@INC) {
1056             my @fl = <$d/Tk/TextHighlight/*.pm>;
1057             foreach my $file (@fl) {
1058             my ($name, $path, $suffix) = fileparse($file, "\.pm");
1059             if ($name eq 'Kate') { #JWT:ADDED THIS PART OF CONDITIONAL 20160118:
1060             eval 'use Syntax::Highlight::Engine::Kate; $haveKate = 1; 1' unless ($haveKate);
1061             if ($haveKate) {
1062             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
1063             }
1064             #CHGD. TO NEXT 20160119: } elsif (($name ne 'None') and ($name ne 'Template')) {
1065             } elsif ($name !~ /^(?:None|Template|RulesEditor)/o) {
1066             #avoid duplicates
1067             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
1068             }
1069             }
1070             }
1071             return sort @ml;
1072             }
1073              
1074             sub highlightPurge {
1075             my ($cw, $line) = @_;
1076             $cw->configure('-colored' => $line);
1077             my $cli = $cw->cget('-colorinf');
1078             if (@$cli) { splice(@$cli, $line) };
1079             $cw->highlightVisual;
1080             }
1081              
1082             sub highlightVisual {
1083             my $cw = shift;
1084             return if ($blockHighlight);
1085             my $end = $cw->visualend;
1086             my $col = $cw->cget('-colored');
1087             if ($col < $end) {
1088             $col = $cw->highlight($col, $end);
1089             $cw->configure(-colored => $col);
1090             };
1091             $cw->matchCheck;
1092             }
1093              
1094             sub insert {
1095             my $cw = shift;
1096             my $pos = shift;
1097             $pos = $cw->index($pos);
1098             my $begin = $cw->linenumber("$pos - 1 chars");
1099             $cw->SUPER::insert($pos, @_);
1100             $cw->highlightCheck($begin, $cw->linenumber("insert lineend"));
1101             }
1102              
1103             sub Insert {
1104             my $cw = shift;
1105             $cw->SUPER::Insert(@_);
1106             $cw->see('insert');
1107             }
1108              
1109             sub InsertKeypress {
1110             my ($cw,$char) = @_;
1111             if ($char ne '') {
1112             my $index = $cw->index('insert');
1113             my $line = $cw->linenumber($index);
1114             if ($char =~ /^\S$/o and !$cw->OverstrikeMode and !$cw->tagRanges('sel')) {
1115             my $undo_item = $cw->getUndoAtIndex(-1);
1116             if (defined($undo_item) &&
1117             ($undo_item->[0] eq 'delete') &&
1118             ($undo_item->[2] == $index)
1119             ) {
1120             $cw->Tk::Text::insert($index,$char);
1121             $undo_item->[2] = $cw->index('insert');
1122             $cw->highlightCheck($line, $line);
1123             $cw->see('insert'); #ADDED 20060703 TO ALLOW USER TO SEE WHAT HE'S TYPING PAST END OF LINE (THIS IS BROKEN IN TEXTUNDO TOO).
1124             return;
1125             }
1126             }
1127             $cw->addGlobStart;
1128             $cw->Tk::Text::InsertKeypress($char);
1129             $cw->addGlobEnd;
1130             }
1131             }
1132              
1133             sub linenumber {
1134             my ($cw, $index) = @_;
1135             if (not defined($index)) { $index = 'insert'; }
1136             my $id = $cw->index($index);
1137             my ($line, $pos ) = split(/\./o, $id);
1138             return $line;
1139             }
1140              
1141             sub Load {
1142             my $cw = shift;
1143             my @r = $cw->SUPER::Load(@_);
1144             $cw->highlightVisual;
1145             return @r;
1146             }
1147              
1148             sub matchCheck {
1149             my $cw = shift;
1150             my $c = $cw->get('insert', 'insert + 1 chars');
1151             my $p = $cw->index('match');
1152             if ($p ne '0.0') {
1153             $cw->tagRemove('Match', $p, "$p + 1 chars");
1154             $cw->markSet('match', '0.0');
1155             $cw->markUnset('MyMatch');
1156             }
1157             if ($c) {
1158             my $v = $cw->cget('-match');
1159             my $p = index($v, $c);
1160             if ($p ne -1) { #a character in '-match' has been detected.
1161             my $count = 0;
1162             my $found = 0;
1163             if ($p % 2) {
1164             my $m = substr($v, $p - 1, 1);
1165             $cw->matchFind('-backwards', $c, $m,
1166             $cw->index('insert'),
1167             # $cw->index('@0,0'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
1168             $cw->index('0.0'),
1169             );
1170             } else {
1171             my $m = substr($v, $p + 1, 1);
1172             # print "searching -forwards, $c, $m\n";
1173             $cw->matchFind('-forwards', $c, $m,
1174             $cw->index('insert + 1 chars'),
1175             # $cw->index($cw->visualend . '.0 lineend'), #CHGD. TO NEXT 20060630 TO PERMIT ^p JUMPING TO MATCHING CHAR OUTSIDE VISIBLE AREA.
1176             $cw->index('end'),
1177             );
1178             }
1179             }
1180             }
1181             $cw->updateCall;
1182             }
1183              
1184             sub matchFind {
1185             my ($cw, $dir, $char, $ochar, $start, $stop) = @_;
1186             #first of all remove a previous match highlight;
1187             my $pattern = "\\$char|\\$ochar";
1188             my $found = 0;
1189             my $count = 0;
1190             while ((not $found) and (my $i = $cw->search(
1191             $dir, '-regexp', '-nocase', '--', $pattern, $start, $stop
1192             ))) {
1193             my $k = $cw->get($i, "$i + 1 chars");
1194             # print "found $k at $i and count is $count\n";
1195             if ($k eq $ochar) {
1196             if ($count > 0) {
1197             # print "decrementing count\n";
1198             $count--;
1199             if ($dir eq '-forwards') {
1200             $start = $cw->index("$i + 1 chars");
1201             } else {
1202             $start = $i;
1203             }
1204             } else {
1205             # print "Found !!!\n";
1206             $cw->markSet('match', $i);
1207             $cw->tagAdd('Match', $i, "$i + 1 chars");
1208             $cw->markSet('MyMatch', $i);
1209             $cw->tagRaise('Match');
1210             $found = 1;
1211             }
1212             } elsif ($k eq $char) {
1213             # print "incrementing count\n";
1214             $count++;
1215             if ($dir eq '-forwards') {
1216             $start = $cw->index("$i + 1 chars");
1217             } else {
1218             $start = $i;
1219             }
1220             } elsif ($i eq $start) {
1221             $found = 1;
1222             }
1223             }
1224             }
1225              
1226             sub matchoptions {
1227             my $cw = shift;
1228             if (my $o = shift) {
1229             my @op = ();
1230             if (ref($o)) {
1231             @op = @$o;
1232             } else {
1233             @op = split(/\s+/o, $o);
1234             }
1235             $cw->tagConfigure('Match', @op);
1236             }
1237             }
1238              
1239              
1240             sub PostPopupMenu {
1241             my $cw = shift;
1242             my @r;
1243             if (not $cw->cget('-disablemenu')) {
1244             @r = $cw->SUPER::PostPopupMenu(@_);
1245             }
1246             }
1247              
1248             sub rulesConfigure {
1249             my $cw = shift;
1250             if (my $plug = $cw->Subwidget('formatter')) {
1251             my $rules = $plug->rules;
1252             my @r = @$rules;
1253             foreach my $k (@r) {
1254             $cw->tagConfigure(@$k);
1255             };
1256             $cw->configure(-colored => 1, -colorinf => [[ $plug->stateGet]]);
1257             }
1258             }
1259              
1260             sub setRule #ADDED 20060530 JWT TO PERMIT CHANGING INDIVIDUAL RULES.
1261             {
1262             my $cw = shift;
1263             my @rule = @_;
1264              
1265             if (my $plug = $cw->Subwidget('formatter'))
1266             {
1267             my $rules = $plug->rules;
1268             my @r = @$rules;
1269             for (my $k=0;$k<=$#r;$k++)
1270             {
1271             if ($rule[0] eq $r[$k]->[0])
1272             {
1273             @{$r[$k]} = @rule;
1274             }
1275             };
1276             $cw->configure(-rules => \@r);
1277             }
1278             }
1279              
1280             sub rulesDelete {
1281             my $cw = shift;
1282             if (my $plug = $cw->Subwidget('formatter')) {
1283             my $rules = $plug->rules;
1284             foreach my $r (@$rules) {
1285             $cw->tagDelete($r->[0]);
1286             }
1287             }
1288             }
1289              
1290              
1291             sub rulesEdit {
1292             my $cw = shift;
1293             require Tk::TextHighlight::RulesEditor;
1294             $cw->RulesEditor(
1295             -class => 'Toplevel',
1296             );
1297             }
1298              
1299             sub rulesFetch {
1300             my $cw = shift;
1301             my $dir = $cw->cget('-rulesdir');
1302             my $syntax = $cw->cget('-syntax');
1303             $cw->configure(-rules => undef);
1304             # print "rulesFetch called\n";
1305             my $result = 0;
1306             if ($dir and (-e "$dir/$syntax.rules")) {
1307             my $file = "$dir/$syntax.rules";
1308             # print "getting $file\n";
1309             if (my $rl = retrieve("$dir/$syntax.rules")) {
1310             # print "configuring\n";
1311             $cw->configure(-rules => $rl);
1312             $result = 1;
1313             }
1314             }
1315             return $result;
1316             }
1317              
1318             sub rulesSave {
1319             my $cw = shift;
1320             my $dir = $cw->cget('-rulesdir');
1321             # print "rulesSave called\n";
1322             if ($dir) {
1323             my $syntax = $cw->cget('-syntax');
1324             my $file = "$dir/$syntax.rules";
1325             store($cw->cget('-rules'), $file);
1326             }
1327             }
1328              
1329             sub scan {
1330             my $cw = shift;
1331             my @r = $cw->SUPER::scan(@_);
1332             $cw->highlightVisual;
1333             return @r;
1334             }
1335              
1336             sub selectionModify {
1337             my ($cw, $char, $mode) = @_;
1338             my @ranges = $cw->tagRanges('sel');
1339             if (@ranges eq 2) {
1340             my $start = $cw->index($ranges[0]);
1341             my $end = $cw->index($ranges[1]);
1342             # print "doing from $start to $end\n";
1343             while ($cw->compare($start, "<", $end)) {
1344             # print "going to do something\n";
1345             if ($mode) {
1346             if ($cw->get("$start linestart", "$start linestart + 1 chars") eq $char) {
1347             $cw->delete("$start linestart", "$start linestart + 1 chars");
1348             }
1349             } else {
1350             $cw->insert("$start linestart", $char)
1351             }
1352             $start = $cw->index("$start + 1 lines");
1353             }
1354             $cw->tagAdd('sel', @ranges);
1355             }
1356             }
1357              
1358             sub selectionComment {
1359             my $cw = shift;
1360             $cw->selectionModify($cw->cget('-commentchar'), 0);
1361             }
1362              
1363             sub selectionIndent {
1364             my $cw = shift;
1365             $cw->selectionModify($cw->cget('-indentchar'), 0);
1366             }
1367              
1368             sub selectionUnComment {
1369             my $cw = shift;
1370             $cw->selectionModify($cw->cget('-commentchar'), 1);
1371             }
1372              
1373             sub selectionUnIndent {
1374             my $cw = shift;
1375             $cw->selectionModify($cw->cget('-indentchar'), 1);
1376             }
1377              
1378             sub syntax {
1379             my $cw = shift;
1380             if (@_) {
1381             my $name = shift;
1382             my $fm;
1383             eval ("require Tk::TextHighlight::$name; \$fm = new Tk::TextHighlight::$name(\$cw);");
1384             $cw->Advertise('formatter', $fm);
1385             $cw->configure('-langname' => $name);
1386             }
1387             return $cw->cget('-langname');
1388             }
1389              
1390             sub yview {
1391             my $cw = shift;
1392             my @r = ();
1393             if (@_) {
1394             @r = $cw->SUPER::yview(@_);
1395             if ($_[1] > 0) { #ONLY RE-HIGHLIGHT IF SCROLLING DOWN (PREV. LINES ALREADY HIGHLIGHTED)!
1396             my ($p) = caller;
1397             $nodoEvent = 1 if ($p =~ /scroll/io); #THIS PREVENTS REPEATING (RUN-AWAY) SCROLLING!
1398             $cw->highlightVisual;
1399             }
1400             } else {
1401             @r = $cw->SUPER::yview;
1402             }
1403             return @r;
1404             }
1405              
1406             sub see {
1407             my $cw = shift;
1408             my @r = $cw->SUPER::see(@_);
1409             $cw->highlightVisual;
1410             return @r
1411             }
1412              
1413             sub updateCall {
1414             my $cw = shift;
1415             my $call = $cw->cget('-updatecall');
1416             &$call;
1417             $nodoEvent = 0;
1418             }
1419              
1420             sub ViewMenuItems {
1421             my $cw = shift;
1422             my $s;
1423             tie $s,'Tk::Configure',$cw,'-syntax';
1424             my @stx = ('None', $cw->highlightPlugList);
1425             my @rad = (['command' => 'Reset', -command => sub {
1426             $cw->configure('-rules' => undef);
1427             $cw->highlightPlug;
1428             }]);
1429             foreach my $n (@stx) {
1430             push(@rad, [
1431             'radiobutton' => $n,
1432             -variable => \$s,
1433             -value => $n,
1434             -command => sub {
1435             $cw->configure('-rules' => undef);
1436             $cw->highlightPlug;
1437             }
1438             ]);
1439             }
1440             my $dir = $cw->cget('-rulesdir');
1441             my $syntax = $cw->cget('-syntax');
1442             my $menuExt = \@{$cw->SUPER::ViewMenuItems};
1443             unless ($cw->cget('-noRulesMenu'))
1444             {
1445             push (@{$menuExt},
1446             ['cascade'=>'Syntax',
1447             -menuitems => [@rad],
1448             ]) unless ($cw->cget('-noSyntaxMenu'));
1449             push (@{$menuExt},
1450             ['command'=>'Rules Editor',
1451             -command => sub { $cw->rulesEdit },
1452             ]) unless ($cw->cget('-noRulesEditMenu'));
1453             push (@{$menuExt},
1454             ['command'=>'Save Rules',
1455             -command => sub { $cw->rulesSave },
1456             ]) if (!$cw->cget('-noSaveRulesMenu') && $dir
1457             && (-w $dir));
1458             }
1459             return $menuExt;
1460             }
1461              
1462             sub visualend {
1463             my $cw = shift;
1464             my $end = $cw->linenumber('end - 1 chars');
1465             my ($first, $last) = $cw->Tk::Text::yview;
1466             my $vend = int($last * $end) + 2;
1467             if ($vend > $end) {
1468             $vend = $end;
1469             }
1470             return $vend;
1471             }
1472              
1473             sub fetchKateInfo #FETCH LISTS OF KATE LANGUAGES AND FILE EXTENSION PATTERNS W/O KATE:
1474             {
1475             #IT IS NECESSARY TO FETCH THIS INFORMATION W/O USING KATE METHODS SINCE WE MAY NOT
1476             #HAVE CREATED A KATE OBJECT WHEN THIS IS NEEDED!
1477             #We return 3 hash-references: 1st can be passed to addkate2viewmenu() to add the
1478             #Kate languages to the Syntax.View menu. the keys are "Kate::language" and the
1479             #values are what's needed to instantiate Kate for that language. the 2nd is
1480             #a list of file-extension pattern suitable for matching against file-names and
1481             #the values are the reccomended Kate language for that file-extension.
1482              
1483             my $cw = shift;
1484              
1485             my (%sectionHash, %extHash, %syntaxHash);
1486              
1487             foreach my $i (@INC)
1488             {
1489             if (-e "$i/Syntax/Highlight/Engine/Kate.pm"
1490             && open KATE, "$i/Syntax/Highlight/Engine/Kate.pm")
1491             {
1492             my $inExtensions = 0;
1493             my $inSyntaxes = 0;
1494             my $inSections = 0;
1495             while ()
1496             {
1497             chomp;
1498             $inExtensions = 1 if (/\$self\-\>\{\'extensions\'\}\s*\=\s*\{/o);
1499             $inSections = 1 if (/\$self\-\>\{\'sections\'\}\s*\=\s*\{/o);
1500             $inSyntaxes = 1 if (/\$self\-\>\{\'syntaxes\'\}\s*\=\s*\{/o);
1501             if ($inSections)
1502             {
1503             if (/\'([^\']+)\'\s*\=\>\s*\[/o)
1504             {
1505             $inSections = $1;
1506             @{$sectionHash{$inSections}} = ();
1507             }
1508             elsif (/\'([^\']+)\'\s*\,/o)
1509             {
1510             push (@{$sectionHash{$inSections}}, $1);
1511             }
1512             elsif (/\}\;/o)
1513             {
1514             $inSections = 0;
1515             }
1516             }
1517             elsif ($inExtensions)
1518             {
1519             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/o)
1520             {
1521             my $one = '^'.$1.'$';
1522             my $two = $2;
1523             $one =~ s/\./\\\./o;
1524             $one =~ s/\*/\.\*/go;
1525             $extHash{$one} = "Kate::$two";
1526             }
1527             elsif (/\}\;/o)
1528             {
1529             $inExtensions = 0;
1530             }
1531             }
1532             elsif ($inSyntaxes)
1533             {
1534             if (/\'([^\']+)\'\s*\=\>\s*\[\'([^\']+)\'/o)
1535             {
1536             $syntaxHash{$1} = $2;
1537             }
1538             elsif (/\}\;/o)
1539             {
1540             $inSyntaxes = 0;
1541             close KATE;
1542             last;
1543             }
1544             }
1545             }
1546             close KATE;
1547             last;
1548             }
1549             }
1550             return (\%sectionHash, \%extHash, \%syntaxHash);
1551             }
1552              
1553             sub addKate2ViewMenu #ADD ALL KATE-LANGUAGES AS OPTIONS TO THE "View" MENU:
1554             {
1555             my $cw = shift;
1556             my $sectionHash = shift;
1557              
1558             return undef if ($cw->cget('-noRulesMenu') || $cw->cget('-noSyntaxMenu'));
1559              
1560             my $ViewSyntaxMenu = $cw->menu->entrycget('View','-menu')->entrycget('Syntax','-menu');
1561             my $lastMenuIndex = $ViewSyntaxMenu->index('end');
1562              
1563             #WE MUST FETCH THE VARIABLE REFERENCE USED BY THE "View" MENU RADIO-BUTTONS SO
1564             #THAT OUR NEW RADIO BUTTONS SHARE SAME VARIABLE (OTHERWISE, WILL HAVE >1 LIT AT
1565             #SAME TIME!
1566              
1567             my $var;
1568             my $kateIndx = 'end';
1569             foreach my $i (0..$lastMenuIndex)
1570             {
1571             if ($ViewSyntaxMenu->type($i) =~ /radiobutton/o)
1572             {
1573             $var = $ViewSyntaxMenu->entrycget($i, '-variable');
1574             tie $$var,'Tk::Configure',$cw,'-syntax';
1575             if ($ViewSyntaxMenu->entrycget($i, '-label') eq 'Kate')
1576             {
1577             $ViewSyntaxMenu->delete($i); #REMOVE THE "Kate" ENTRY, SINCE WE'RE ADDING KATE STUFF SEPARATELY!
1578             #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.
1579             last;
1580             }
1581             }
1582             }
1583              
1584             #NOW ADD OUR "KATE" RADIO-BUTTONS!
1585              
1586             my ($nextMenu, $menuTitle);
1587             foreach my $sect (sort keys %{$sectionHash})
1588             {
1589             $nextMenu = $ViewSyntaxMenu->Menu;
1590             foreach my $lang (@{$sectionHash->{$sect}})
1591             {
1592             $menuTitle = "Kate::$lang";
1593             $nextMenu->radiobutton( -label => $menuTitle,
1594             -variable => $var,
1595             -value => $menuTitle,
1596             -command => sub
1597             {
1598             $cw->configure('-rules' => undef);
1599             $cw->highlightPlug;
1600             }
1601             );
1602             }
1603             $ViewSyntaxMenu->insert($kateIndx, 'cascade', -label => "Kate: $sect...",
1604             -menu => $nextMenu);
1605             ++$kateIndx if ($kateIndx =~ /^\d/o);
1606             }
1607             }
1608              
1609             sub insertTab
1610             {
1611             my ($w) = @_;
1612             # $w->Insert("\t");
1613             $w->Insert($w->cget('-indentchar'));
1614             $w->focus;
1615             $w->break
1616             }
1617              
1618             sub insertTabChar
1619             {
1620             my ($w) = @_;
1621             $w->Insert("\t");
1622             $w->focus;
1623             $w->break
1624             }
1625              
1626             1;
1627              
1628             __END__