File Coverage

blib/lib/Tk/CodeText.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::CodeText;
2              
3 1     1   84035 use vars qw($VERSION);
  1         4  
  1         111  
4             $VERSION = '0.3.4';
5 1     1   6 use base qw(Tk::Derived Tk::TextUndo);
  1         1  
  1         1151  
6             use strict;
7             use Storable;
8             use File::Basename;
9              
10             Construct Tk::Widget 'CodeText';
11              
12             sub Populate {
13             my ($cw,$args) = @_;
14             $cw->SUPER::Populate($args);
15             $cw->ConfigSpecs(
16             -autoindent => [qw/PASSIVE autoindent Autoindent/, 0],
17             -match => [qw/PASSIVE match Match/, '[]{}()'],
18             -matchoptions => [qw/METHOD matchoptions Matchoptions/,
19             [-background => 'red', -foreground => 'yellow']],
20             -indentchar => [qw/PASSIVE indentchar Indentchar/, "\t"],
21             -disablemenu => [qw/PASSIVE disablemenu Disablemenu/, 0],
22             -commentchar => [qw/PASSIVE commentchar Commentchar/, "#"],
23             -colorinf => [qw/PASSIVE undef undef/, []],
24             -colored => [qw/PASSIVE undef undef/, 0],
25             -syntax => [qw/PASSIVE syntax Syntax/, 'None'],
26             -rules => [qw/PASSIVE undef undef/, undef],
27             -rulesdir => [qw/PASSIVE rulesdir Rulesdir/, ''],
28             -updatecall => [qw/PASSIVE undef undef/, sub {}],
29             DEFAULT => [ 'SELF' ],
30             );
31             $cw->bind('', sub { $cw->highlightVisual });
32             $cw->bind('', sub { $cw->doAutoIndent });
33             $cw->markSet('match', '0.0');
34             }
35              
36             sub clipboardCopy {
37             my $cw = shift;
38             my @ranges = $cw->tagRanges('sel');
39             if (@ranges) {
40             $cw->SUPER::clipboardCopy(@_);
41             }
42             }
43              
44             sub clipboardCut {
45             my $cw = shift;
46             my @ranges = $cw->tagRanges('sel');
47             if (@ranges) {
48             $cw->SUPER::clipboardCut(@_);
49             }
50             }
51              
52             sub clipboardPaste {
53             my $cw = shift;
54             my @ranges = $cw->tagRanges('sel');
55             if (@ranges) {
56             $cw->tagRemove('sel', '1.0', 'end');
57             return;
58             }
59             $cw->SUPER::clipboardPaste(@_);
60             }
61              
62             sub delete {
63             my $cw = shift;
64             my $begin = $_[0];
65             if (defined($begin)) {
66             $begin = $cw->linenumber($begin);
67             } else {
68             $begin = $cw->linenumber('insert');
69             };
70             my $end = $_[1];
71             if (defined($end)) {
72             $end = $cw->linenumber($end);
73             } else {
74             $end = $begin;
75             };
76             $cw->SUPER::delete(@_);
77             $cw->highlightCheck($begin, $end);
78             }
79              
80             sub doAutoIndent {
81             my $cw = shift;
82             if ($cw->cget('-autoindent')) {
83             my $i = $cw->index('insert linestart');
84             if ($cw->compare($i, ">", '0.0')) {
85             my $s = $cw->get("$i - 1 lines", "$i - 1 lines lineend");
86             $s =~ /^(\s+)/;
87             if ($1) {
88             $cw->insert('insert', $1);
89             }
90             }
91             }
92             }
93              
94             sub EditMenuItems {
95             my $cw = shift;
96             return [
97             @{$cw->SUPER::EditMenuItems},
98             "-",
99             ["command"=>'Comment', -command => [$cw => 'selectionComment']],
100             ["command"=>'Uncomment', -command => [$cw => 'selectionUnComment']],
101             "-",
102             ["command"=>'Indent', -command => [$cw => 'selectionIndent']],
103             ["command"=>'Unindent', -command => [$cw => 'selectionUnIndent']],
104             ];
105             }
106              
107             sub EmptyDocument {
108             my $cw = shift;
109             my @r = $cw->SUPER::EmptyDocument(@_);
110             $cw->highlightPurge(1);
111             return @r
112             }
113              
114             sub highlight {
115             my ($cw, $begin, $end) = @_;
116             if (not defined($end)) { $end = $begin + 1};
117             #save selection and cursor position
118             my @sel = $cw->tagRanges('sel');
119             # my $cursor = $cw->index('insert');
120             #go over the source code line by line.
121             while ($begin < $end) {
122             $cw->highlightLine($begin);
123             $begin++; #move on to next line.
124             };
125             #restore original cursor and selection
126             # $cw->markSet('insert', $cursor);
127             if ($sel[0]) {
128             $cw->tagRaise('sel');
129             };
130             return $begin;
131             }
132              
133             sub highlightCheck {
134             my ($cw, $begin, $end) = @_;
135             my $col = $cw->cget('-colored');
136             my $cli = $cw->cget('-colorinf');
137             if ($begin <= $col) {
138             #The operation occurred in an area that was highlighted already
139             if ($begin < $end) {
140             #it was a multiline operation, so highlighting is not reliable anymore
141             #restart hightlighting from the beginning of the operation.
142             $cw->highlightPurge($begin);
143             } else {
144             #just re-highlight the modified line.
145             my $hlt = $cw->highlightPlug;
146             my $i = $cli->[$begin];
147             $cw->highlight($begin);
148             if (($col < $cw->linenumber('end')) and (not $hlt->stateCompare($i))) {
149             #the proces ended inside a multiline token. try to fix it.
150             $cw->highlightPurge($begin);
151             }
152             };
153             $cw->matchCheck;
154             } else {
155             $cw->highlightVisual;
156             }
157             }
158              
159             sub highlightLine {
160             my ($cw, $num) = @_;
161             my $hlt = $cw->highlightPlug;
162             my $cli = $cw->cget('-colorinf');
163             my $k = $cli->[$num - 1];
164             $hlt->stateSet(@$k);
165             # remove all existing tags in this line
166             my $begin = "$num.0"; my $end = $cw->index("$num.0 lineend");
167             my $rl = $hlt->rules;
168             foreach my $tn (@$rl) {
169             $cw->tagRemove($tn->[0], $begin, $end);
170             }
171             my $txt = $cw->get($begin, $end); #get the text to be highlighted
172             if ($txt) { #if the line is not empty
173             my $pos = 0;
174             my $start = 0;
175             my @h = $hlt->highlight($txt);
176             while (@h ne 0) {
177             $start = $pos;
178             $pos += shift @h;
179             my $tag = shift@h;
180             $cw->tagAdd($tag, "$num.$start", "$num.$pos");
181             };
182             };
183             $cli->[$num] = [ $hlt->stateGet ];
184             }
185              
186             sub highlightPlug {
187             my $cw = shift;
188             my $plug = $cw->Subwidget('formatter');
189             my $syntax = $cw->cget('-syntax');
190             my $rules = $cw->cget('-rules');
191             if (not defined($plug)) {
192             $plug = $cw->highlightPlugInit;
193             } elsif (ref($syntax)) {
194             if ($syntax ne $plug) {
195             $plug = $cw->highlightPlugInit;
196             }
197             } elsif ($syntax ne $plug->syntax) {
198             $cw->rulesDelete;
199             $plug = $cw->highlightPlugInit;
200             $cw->highlightPurge(1);
201             } elsif (defined($rules)) {
202             if ($rules ne $plug->rules) {
203             $cw->rulesDelete;
204             $plug->rules($rules);
205             $cw->rulesConfigure;
206             $cw->highlightPurge(1);
207             }
208             };
209             return $plug
210             }
211              
212             sub highlightPlugInit {
213             my $cw = shift;
214             my $syntax = $cw->cget('-syntax');
215             if (not defined($cw->cget('-rules'))) { $cw->rulesFetch };
216             my $plug;
217             if (ref($syntax)) {
218             $plug = $syntax;
219             } else {
220             my @opt = ();
221             if (my $rules = $cw->cget('-rules')) {
222             push(@opt, $rules);
223             }
224             eval ("require Tk::CodeText::$syntax; \$plug = new Tk::CodeText::$syntax(\@opt);");
225             }
226             $cw->Advertise('formatter', $plug);
227             $cw->rulesConfigure;
228             return $plug;
229             }
230              
231             sub highlightPlugList {
232             my $cw = shift;
233             my @ml = ();
234             foreach my $d (@INC) {
235             my @fl = <$d/Tk/CodeText/*.pm>;
236             foreach my $file (@fl) {
237             my ($name, $path, $suffix) = fileparse($file, "\.pm");
238             if (($name ne 'None') and ($name ne 'Template')) {
239             #avoid duplicates
240             unless (grep { ($name eq $_) } @ml) { push(@ml, $name); };
241             }
242             }
243             }
244             return sort @ml;
245             }
246              
247             sub highlightPurge {
248             my ($cw, $line) = @_;
249             # print "purging from $line\n";
250             $cw->configure('-colored' => $line);
251             my $cli = $cw->cget('-colorinf');
252             if (@$cli) { splice(@$cli, $line) };
253             $cw->highlightVisual;
254             }
255              
256             sub highlightVisual {
257             my $cw = shift;
258             # print "checking coloring\n";
259             my $end = $cw->visualend;
260             # print "\tvisual $end\n";
261             my $col = $cw->cget('-colored');
262             # print "\tcolored to $col\n";
263             if ($col < $end) {
264             $col = $cw->highlight($col, $end);
265             $cw->configure(-colored => $col);
266             };
267             $cw->matchCheck;
268             }
269              
270             sub insert {
271             my $cw = shift;
272             my $pos = shift;
273             $pos = $cw->index($pos);
274             my $begin = $cw->linenumber("$pos - 1 chars");
275             $cw->SUPER::insert($pos, @_);
276             $cw->highlightCheck($begin, $cw->linenumber("insert lineend"));
277             }
278              
279             sub Insert {
280             my $cw = shift;
281             $cw->SUPER::Insert(@_);
282             $cw->see('insert');
283             }
284              
285             sub InsertKeypress {
286             my ($cw,$char) = @_;
287             # print "calling InsertKeypress\n";
288             if ($char ne '') {
289             my $index = $cw->index('insert');
290             my $line = $cw->linenumber($index);
291             if ($char =~ /^\S$/ and !$cw->OverstrikeMode and !$cw->tagRanges('sel')) {
292             my $undo_item = $cw->getUndoAtIndex(-1);
293             if (defined($undo_item) &&
294             ($undo_item->[0] eq 'delete') &&
295             ($undo_item->[2] == $index)
296             ) {
297             $cw->Tk::Text::insert($index,$char);
298             $undo_item->[2] = $cw->index('insert');
299             $cw->highlightCheck($line, $line);
300             return;
301             }
302             }
303             $cw->addGlobStart;
304             $cw->Tk::Text::InsertKeypress($char);
305             $cw->addGlobEnd;
306             }
307             }
308              
309             sub linenumber {
310             my ($cw, $index) = @_;
311             if (not defined($index)) { $index = 'insert'; }
312             my $id = $cw->index($index);
313             my ($line, $pos ) = split(/\./, $id);
314             # print "linenumber $line\n";
315             return $line;
316             }
317              
318             sub Load {
319             my $cw = shift;
320             my @r = $cw->SUPER::Load(@_);
321             $cw->highlightVisual;
322             return @r;
323             }
324              
325             sub matchCheck {
326             my $cw = shift;
327             my $c = $cw->get('insert - 1 chars', 'insert');
328             my $p = $cw->index('match');
329             if ($p ne '0.0') {
330             $cw->tagRemove('Match', $p, "$p + 1 chars");
331             $cw->markSet('match', '0.0');
332             }
333             if ($c) {
334             my $v = $cw->cget('-match');
335             my $p = index($v, $c);
336             # print "character $c number $p\n";
337             if ($p ne -1) { #a character in '-match' has been detected.
338             my $count = 0;
339             my $found = 0;
340             if ($p % 2) {
341             my $m = substr($v, $p - 1, 1);
342             # print "searching -backwards $c $m\n";
343             $cw->matchFind('-backwards', $c, $m,
344             $cw->index('insert - 1 chars'),
345             $cw->index('@0,0'),
346             );
347             } else {
348             my $m = substr($v, $p + 1, 1);
349             # print "searching -forwards, $c, $m\n";
350             $cw->matchFind('-forwards', $c, $m,
351             $cw->index('insert'),
352             $cw->index($cw->visualend . '.0 lineend'),
353             );
354             }
355             }
356             }
357             $cw->updateCall;
358             }
359              
360             sub matchFind {
361             my ($cw, $dir, $char, $ochar, $start, $stop) = @_;
362             #first of all remove a previous match highlight;
363             my $pattern = "\\$char|\\$ochar";
364             my $found = 0;
365             my $count = 0;
366             while ((not $found) and (my $i = $cw->search(
367             $dir, '-regexp', '-nocase', '--', $pattern, $start, $stop
368             ))) {
369             my $k = $cw->get($i, "$i + 1 chars");
370             # print "found $k at $i and count is $count\n";
371             if ($k eq $ochar) {
372             if ($count > 0) {
373             # print "decrementing count\n";
374             $count--;
375             if ($dir eq '-forwards') {
376             $start = $cw->index("$i + 1 chars");
377             } else {
378             $start = $i;
379             }
380             } else {
381             # print "Found !!!\n";
382             $cw->markSet('match', $i);
383             $cw->tagAdd('Match', $i, "$i + 1 chars");
384             $cw->tagRaise('Match');
385             $found = 1;
386             }
387             } elsif ($k eq $char) {
388             # print "incrementing count\n";
389             $count++;
390             if ($dir eq '-forwards') {
391             $start = $cw->index("$i + 1 chars");
392             } else {
393             $start = $i;
394             }
395             } elsif ($i eq $start) {
396             $found = 1;
397             }
398             }
399             }
400              
401             sub matchoptions {
402             my $cw = shift;
403             if (my $o = shift) {
404             my @op = ();
405             if (ref($o)) {
406             @op = @$o;
407             } else {
408             @op = split(/\s+/, $o);
409             }
410             $cw->tagConfigure('Match', @op);
411             }
412             }
413              
414              
415             sub PostPopupMenu {
416             my $cw = shift;
417             my @r;
418             if (not $cw->cget('-disablemenu')) {
419             @r = $cw->SUPER::PostPopupMenu(@_);
420             }
421             }
422              
423             sub rulesConfigure {
424             my $cw = shift;
425             if (my $plug = $cw->Subwidget('formatter')) {
426             my $rules = $plug->rules;
427             my @r = @$rules;
428             foreach my $k (@r) {
429             $cw->tagConfigure(@$k);
430             };
431             $cw->configure(-colored => 1, -colorinf => [[ $plug->stateGet]]);
432             }
433             }
434              
435             sub rulesDelete {
436             my $cw = shift;
437             if (my $plug = $cw->Subwidget('formatter')) {
438             my $rules = $plug->rules;
439             foreach my $r (@$rules) {
440             $cw->tagDelete($r->[0]);
441             }
442             }
443             }
444              
445              
446             sub rulesEdit {
447             my $cw = shift;
448             require Tk::RulesEditor;
449             $cw->RulesEditor(
450             -class => 'Toplevel',
451             );
452             }
453              
454             sub rulesFetch {
455             my $cw = shift;
456             my $dir = $cw->cget('-rulesdir');
457             my $syntax = $cw->cget('-syntax');
458             $cw->configure(-rules => undef);
459             # print "rulesFetch called\n";
460             my $result = 0;
461             if ($dir and (-e "$dir/$syntax.rules")) {
462             my $file = "$dir/$syntax.rules";
463             # print "getting $file\n";
464             if (my $rl = retrieve("$dir/$syntax.rules")) {
465             # print "configuring\n";
466             $cw->configure(-rules => $rl);
467             $result = 1;
468             }
469             }
470             return $result;
471             }
472              
473             sub rulesSave {
474             my $cw = shift;
475             my $dir = $cw->cget('-rulesdir');
476             # print "rulesSave called\n";
477             if ($dir) {
478             my $syntax = $cw->cget('-syntax');
479             my $file = "$dir/$syntax.rules";
480             store($cw->cget('-rules'), $file);
481             }
482             }
483              
484             sub scan {
485             my $cw = shift;
486             my @r = $cw->SUPER::scan(@_);
487             $cw->highlightVisual;
488             return @r;
489             }
490              
491             sub selectionModify {
492             my ($cw, $char, $mode) = @_;
493             my @ranges = $cw->tagRanges('sel');
494             if (@ranges eq 2) {
495             my $start = $cw->index($ranges[0]);
496             my $end = $cw->index($ranges[1]);
497             # print "doing from $start to $end\n";
498             while ($cw->compare($start, "<", $end)) {
499             # print "going to do something\n";
500             if ($mode) {
501             if ($cw->get("$start linestart", "$start linestart + 1 chars") eq $char) {
502             $cw->delete("$start linestart", "$start linestart + 1 chars");
503             }
504             } else {
505             $cw->insert("$start linestart", $char)
506             }
507             $start = $cw->index("$start + 1 lines");
508             }
509             $cw->tagAdd('sel', @ranges);
510             }
511             }
512              
513             sub selectionComment {
514             my $cw = shift;
515             $cw->selectionModify($cw->cget('-commentchar'), 0);
516             }
517              
518             sub selectionIndent {
519             my $cw = shift;
520             $cw->selectionModify($cw->cget('-indentchar'), 0);
521             }
522              
523             sub selectionUnComment {
524             my $cw = shift;
525             $cw->selectionModify($cw->cget('-commentchar'), 1);
526             }
527              
528             sub selectionUnIndent {
529             my $cw = shift;
530             $cw->selectionModify($cw->cget('-indentchar'), 1);
531             }
532              
533             sub syntax {
534             my $cw = shift;
535             if (@_) {
536             my $name = shift;
537             my $fm;
538             eval ("require Tk::CodeText::$name; \$fm = new Tk::CodeText::$name(\$cw);");
539             $cw->Advertise('formatter', $fm);
540             $cw->configure('-langname' => $name);
541             }
542             return $cw->cget('-langname');
543             }
544              
545             sub yview {
546             my $cw = shift;
547             my @r = ();
548             if (@_) {
549             @r = $cw->SUPER::yview(@_);
550             $cw->highlightVisual;
551             } else {
552             @r = $cw->SUPER::yview;
553             }
554             return @r;
555             }
556              
557             sub see {
558             my $cw = shift;
559             my @r = $cw->SUPER::see(@_);
560             $cw->highlightVisual;
561             return @r
562             }
563              
564             sub updateCall {
565             my $cw = shift;
566             my $call = $cw->cget('-updatecall');
567             &$call;
568             }
569              
570             sub ViewMenuItems {
571             my $cw = shift;
572             my $s;
573             tie $s,'Tk::Configure',$cw,'-syntax';
574             my @stx = ('None', $cw->highlightPlugList);
575             my @rad = ();
576             foreach my $n (@stx) {
577             push(@rad, [
578             'radiobutton' => $n,
579             -variable => \$s,
580             -value => $n,
581             -command => sub {
582             $cw->configure('-rules' => undef);
583             $cw->highlightPlug;
584             }
585             ]);
586             }
587             return [
588             @{$cw->SUPER::ViewMenuItems},
589             ['cascade'=>'Syntax',
590             -menuitems => [@rad],
591             ],
592             ['command'=>'Rules Editor',
593             -command => sub { $cw->rulesEdit },
594             ],
595             ];
596             }
597              
598             sub visualend {
599             my $cw = shift;
600             my $end = $cw->linenumber('end - 1 chars');
601             my ($first, $last) = $cw->Tk::Text::yview;
602             my $vend = int($last * $end) + 2;
603             if ($vend > $end) {
604             $vend = $end;
605             }
606             return $vend;
607             }
608              
609             =cut
610              
611             1;
612              
613             __END__