File Coverage

blib/lib/Devel/PDB.pm
Criterion Covered Total %
statement 18 20 90.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 25 27 92.5


line stmt bran cond sub pod time code
1             # vi: set autoindent shiftwidth=4 tabstop=8 softtabstop=4 expandtab:
2             package DB;
3              
4 1     1   21695 use 5.006001;
  1         3  
5 1     1   5 use strict;
  1         1  
  1         28  
6 1     1   5 use warnings;
  1         5  
  1         33  
7              
8 1     1   5 use Carp;
  1         2  
  1         85  
9 1     1   8 use B qw(svref_2object comppadlist class);
  1         2  
  1         66  
10 1     1   17221 use B::Showlex;
  1         21166  
  1         31  
11 1     1   451 use Curses;
  0            
  0            
12             use Curses::UI;
13             use Curses::UI::Common;
14             use Data::Dumper;
15             use Cwd;
16             use File::Basename;
17              
18             use Devel::PDB::Source;
19              
20             use vars qw(*dbline $usercontext $db_stop $ini_warn);
21              
22             our $VERSION = '1.6';
23              
24             our $single;
25             our $sub;
26             our $trace;
27             our $signal;
28             our $stack_depth;
29             our @stack;
30             our $current_sub;
31              
32             my @compiled;
33             my $inited = 0;
34             my $cui;
35             my $sv_win;
36             my $sv;
37             my $exit = 0;
38             my $db_exit = 0;
39             my $yield;
40             my %sources;
41             my $new_single;
42             my $current_source;
43             my $evalarg;
44             my $package;
45             my $filename;
46             my $line;
47             my @watch_exprs;
48             my $update_watch_list;
49              
50             my $std_file_win;
51             my $std_file;
52             my $help_win;
53             my $help;
54              
55             my $lower_win;
56             my $auto_win;
57             my $watch_win;
58             my $padvar_list;
59             my $watch_list;
60              
61             my $padlist_scope;
62             my %padlist;
63             my @padlist_disp;
64              
65             my $stdout;
66             my $stderr;
67             my $output;
68              
69             my $user_conf_readed = 0;
70             my $ui_window_focused = 0;
71              
72             $trace = $signal = $single = 0;
73             $stack_depth = 0;
74             @stack = (0);
75              
76             my %def_style = (
77             -bg => 'white',
78             -fg => 'blue',
79             -bbg => 'blue',
80             -bfg => 'white',
81             -tbg => 'white',
82             -tfg => 'blue',
83             );
84              
85             #
86             # Set or return window colour style
87             #
88             sub window_style {
89             if (@_) {
90             my %h = @_;
91             while (my ($k, $v) = each %h) {
92             $def_style{$k} = $v if ($k =~ /^-[tbs]?[fb]g$/);
93             }
94             }
95             return %def_style;
96             }
97              
98             BEGIN {
99             $Devel::PDB::scriptName = $0;
100             @Devel::PDB::script_args = @ARGV; # copy args
101             $ini_warn = $^W;
102              
103             # This is the flag that says "a debugger is running, please call
104             # DB::DB and DB::sub". We will turn it on forcibly before we try to
105             # execute anything in the user's context, because we always want to
106             # get control back.
107             $db_stop = 0; # Compiler warning ...
108             $db_stop = 1 << 30; # ... because this is only used in an eval() later.
109             }
110              
111             END {
112             open STDOUT, ">>&", $stdout if $stdout;
113             $single = 0;
114              
115             # Save actual breakpoints and watches
116             save_state_file(config_file("conf.rc"));
117              
118             my @ab = ({
119             -label => '< Quit >',
120             -value => 1,
121             -shortcut => 'q'
122             },
123             { -label => '< Show STD* files >',
124             -value => 2,
125             -shortcut => 'f'
126             },
127             { -label => '< Restart >',
128             -value => 3,
129             -shortcut => 'r'
130             },
131             { -label => '< Save config & Quit >',
132             -value => 4,
133             -shortcut => 's'
134             },
135             { -label => '< Save config & Restart >',
136             -value => 'a',
137             -shortcut => 5
138             },
139             );
140              
141             my $exitloop = ($db_exit || !$cui) ? 1 : 0;
142             while (!$exitloop) {
143             my $t = $cui->dialog(
144             -title => 'Exiting',
145             -buttons => \@ab,
146             -message => 'Choose one of this functions : ',
147             window_style(),
148             );
149              
150             if ($t == 1) {
151             $exitloop = 1;
152             } elsif ($t == 2) {
153             db_view_std_files(1);
154             } elsif ($t == 3) {
155             DoRestart();
156             } elsif ($t == 4) {
157             save_state_file(config_file("conf"));
158             $exitloop = 1;
159             } elsif ($t == 5) {
160             save_state_file(config_file("conf"));
161             DoRestart();
162             }
163             }
164             endwin();
165             }
166              
167             #
168             # Method for restarting debugger
169             #
170             sub DoRestart {
171              
172             # There is problem with Destroyer in Curses::UI
173             endwin();
174              
175             # We must destroyed $cui
176             $cui = undef;
177              
178             my @flags = ();
179              
180             # If warn was on before, turn it on again.
181             push @flags, '-w' if $ini_warn;
182              
183             # Rebuild the -I flags that were on the initial # command line.
184             my %h_inc = @INC;
185             foreach (split(" ", `perl -e 'print "\@INC";'`)) {
186             delete($h_inc{$_});
187             }
188              
189             foreach (keys %h_inc) {
190             push @flags, '-I', $_;
191             }
192              
193             # Turn on taint if it was on before.
194             push @flags, '-T' if ${^TAINT};
195              
196             if ($Devel::PDB::scriptName eq '-e') {
197             my $cl;
198             my $lines = *{$main::{'_<-e'}}{ARRAY};
199             for (1 .. $#$lines) { # The first line is PERL5DB
200             chomp($cl = $lines->[$_]);
201             push @flags, '-e', $cl;
202             }
203             } elsif ($Devel::PDB::scriptName !~ /perl/) {
204             push @flags, $Devel::PDB::scriptName;
205             }
206              
207             # print "$$ doing a restart with $fname\n" ;
208             exec "perl", "-d:PDB", @flags, @Devel::PDB::script_args;
209             }
210              
211             #
212             # print any error which is put as arguments
213             #
214             sub print_error {
215             $cui->error(
216             -title => "Error",
217             -message => join("\n", @_),
218             DB::window_style(),
219             ) if ($cui);
220             }
221              
222             #
223             # returns true if line is breakable
224             #
225             sub checkdbline($$) {
226             my ($fname, $lineno) = @_;
227              
228             return 0 unless $fname; # we're getting an undef here on 'Restart...'
229              
230             local ($^W) = 0; # spares us warnings under -w
231             local (*dbline) = $main::{'_<' . $fname};
232              
233             my $flag = $dbline[$lineno] != 0;
234             return $flag;
235              
236             } # end of checkdbline
237              
238             #
239             # sets a breakpoint 'through' a magic
240             # variable that perl is able to interpert
241             #
242             sub setdbline($$$) {
243             my ($fname, $lineno, $value) = @_;
244             local (*dbline) = $main::{'_<' . $fname};
245              
246             $dbline{$lineno} = $value;
247             } # end of setdbline
248              
249             sub getdbline($$) {
250             my ($fname, $lineno) = @_;
251             local (*dbline) = $main::{'_<' . $fname};
252             return $dbline{$lineno};
253             } # end of getdbline
254              
255             sub getdbtextline {
256             my ($fname, $lineno) = @_;
257             local (*dbline) = $main::{'_<' . $fname};
258             return $dbline[$lineno];
259             } # end of getdbline
260              
261             sub cleardbline($$;&) {
262             my ($fname, $lineno, $clearsub) = @_;
263             local (*dbline) = $main::{'_<' . $fname};
264             my $value; # just in case we want it for something
265              
266             $value = $dbline{$lineno};
267             delete $dbline{$lineno};
268             &$clearsub($value) if $value && $clearsub;
269              
270             return $value;
271             } # end of cleardbline
272              
273             sub clearalldblines(;&) {
274             my ($clearsub) = @_;
275             my ($key, $value, $brkPt, $dbkey);
276             local (*dbline);
277              
278             while (($key, $value) = each %main::) { # key loop
279             next unless $key =~ /^_
280             *dbline = $value;
281              
282             foreach $dbkey (keys %dbline) {
283             $brkPt = $dbline{$dbkey};
284             delete $dbline{$dbkey};
285             next unless $brkPt && $clearsub;
286             &$clearsub($brkPt); # if specificed, call the sub routine to clear the breakpoint
287             }
288              
289             } # end of key loop
290              
291             } # end of clearalldblines
292              
293             sub getdblineindexes {
294             my ($fname) = @_;
295             local (*dbline) = $main::{'_<' . $fname};
296             return keys %dbline;
297             } # end of getdblineindexes
298              
299             #
300             # Return list of breakpoints from files which are add as arguments
301             #
302             sub getbreakpoints {
303             my (@fnames) = @_;
304             my ($fname, @retList);
305              
306             foreach $fname (@fnames) {
307             next unless $main::{'_<' . $fname};
308             local (*dbline) = $main::{'_<' . $fname};
309             push @retList, values %dbline;
310             }
311             return @retList;
312             } # end of getbreakpoints
313              
314             #
315             # Return filename from param and remove _< character from begin
316             #
317             sub retfilename {
318             my $f = shift;
319             $f =~ s/^_
320             return $f;
321             }
322              
323             #
324             # Construct a hash of the files
325             # that have breakpoints to save
326             #
327             sub breakpoints_to_save {
328             my %brkList = ();
329              
330             foreach my $file (keys %main::) { # file loop
331             next unless $file =~ /^_
332              
333             #my @k = getdblineindexes(retfilename($file));
334             local (*dbline) = $main::{$file};
335             my @a = ();
336             while (my ($k, $d) = each %dbline) {
337             push(@a, {'line' => $k, 'breakpoint' => $d}) if ($d);
338             }
339             $brkList{$file} = \@a if (scalar(@a));
340             } # end of file loop
341             return \%brkList;
342              
343             } # end of breakpoints_to_save
344              
345             #
346             # When we restore breakpoints from a state file
347             # they've often 'moved' because the file
348             # has been editted.
349             #
350             # We search for the line starting with the original line number,
351             # then we walk it back 20 lines, then with line right after the
352             # orginal line number and walk forward 20 lines.
353             #
354             # NOTE: dbline is expected to be 'local'
355             # when called
356             #
357             sub fix_breakpoints {
358             my (@brkPts) = @_;
359             my ($startLine, $endLine, $nLines, $brkPt);
360             my (@retList);
361             local ($^W) = 0;
362              
363             $nLines = scalar @dbline;
364              
365             foreach $brkPt (@brkPts) {
366              
367             #$startLine = $brkPt->{'line'} > 20 ? $brkPt->{'line'} - 20 : 0 ;
368             #$endLine = $brkPt->{'line'} < $nLines - 20 ? $brkPt->{'line'} + 20 : $nLines ;
369             #
370             #for( (reverse $startLine..$brkPt->{'line'}), $brkPt->{'line'} + 1 .. $endLine ) {
371             # next unless $brkPt->{'text'} eq $dbline[$_] ;
372             # $brkPt->{'line'} = $_ ;
373             # push @retList, $brkPt ;
374             # last ;
375             #}
376             push @retList, $brkPt;
377             } # end of breakpoint list
378              
379             return @retList;
380              
381             } # end of fix_breakpoints
382              
383             sub set_breakpoints {
384             my ($fname, $newList) = @_;
385              
386             local (*dbline) = $main::{$fname};
387              
388             my $offset = 0;
389             $offset = 1 if $dbline[1] =~ /use\s+.*Devel::_?PDB/;
390              
391             foreach my $brkPt (@$newList) {
392             if (!checkdbline(retfilename($fname), $brkPt->{'line'} + $offset)) {
393             print_error("Breakpoint $fname:$brkPt->{'line'} in config file is not breakable.");
394             next;
395             }
396              
397             #$dbline{$brkPt->{'line'}} = { %$brkPt } ; # make a fresh copy
398             $dbline{$brkPt->{'line'}} = exists($brkPt->{'breakpoint'}) ? $brkPt->{'breakpoint'} : 1;
399             }
400              
401             }
402              
403             my %postponed_file = ();
404              
405             #
406             # Restore breakpoints saved above
407             #
408             sub restore_breakpoints_from_save {
409             my ($brkList) = @_;
410             my ($key, $list, @newList);
411              
412             while (($key, $list) = each %$brkList) { # reinsert loop
413             $postponed_file{$key} = $list;
414              
415             next unless exists $main::{$key};
416              
417             @newList = fix_breakpoints(@$list);
418             set_breakpoints($key, \@newList);
419             } # end of reinsert loop
420              
421             } # end of restore_breakpoints_from_save ;
422              
423             #
424             # Loading watches and breakpoint from state file(it is param)
425             #
426             sub load_state_file {
427             my ($fName) = @_;
428              
429             if (-e $fName && -r $fName) {
430             no strict;
431             local ($files, $expr_list);
432             do $fName;
433             if ($@) {
434             print_error($@);
435             }
436              
437             %postponed_file = ();
438              
439             restore_breakpoints_from_save($files);
440              
441             # Don't load saved watches against
442             my %h = map { $_->{name} => 1 } @watch_exprs;
443             foreach $rh (@$expr_list) {
444             push @watch_exprs, {name => $rh->{name}} unless exists($h{$rh->{name}});
445             }
446             $update_watch_list = 1;
447              
448             if ($current_source) {
449             my $view = $current_source->view;
450             $view->intellidraw if (defined $view);
451             }
452             }
453             } # end of Restore State
454              
455             #
456             # Save watches and breakpoints to state filename(it is param)
457             #
458             sub save_state_file {
459             my ($fname) = @_;
460             my ($files, $d, $saveStr);
461              
462             $files = breakpoints_to_save();
463              
464             $d = Data::Dumper->new([$files, \@watch_exprs], [qw(files expr_list)]);
465              
466             $d->Indent(1);
467             $d->Purity(1);
468             $d->Terse(0);
469             if (Data::Dumper->can('Dumpxs')) {
470             $saveStr = $d->Dumpxs();
471             } else {
472             $saveStr = $d->Dump();
473             }
474              
475             local (*F);
476             open F, ">$fname" || die "Couldn't open file $fname";
477             print F $saveStr || die "Couldn't write file";
478             close F;
479             } # end of save_state_file
480              
481             my $_log_opened = 0;
482              
483             #
484             # Internal method for printing anything to file
485             # 1. name of text
486             # 2. variable
487             #
488             sub log_dumper {
489             my ($name, $a) = @_;
490              
491             my $fDUMP = config_file("dump");
492             local (*W);
493             open(W, ($_log_opened ? ">" : "") . ">$fDUMP")
494             or die "Can't open dump file : $fDUMP\n";
495             $_log_opened = 1;
496             print W "$name";
497              
498             if ($a) {
499             local $Data::Dumper::Purity = 0;
500             local $Data::Dumper::Terse = 0;
501             local $Data::Dumper::Indent = 2;
502             local $Data::Dumper::Sortkeys = 1;
503             print W Dumper($a);
504             }
505             print W "\n";
506             close(W);
507             }
508              
509             #
510             # UI for exiting
511             #
512             sub ui_db_quit {
513             return
514             if not $cui->dialog(
515             -title => 'Quit Debugger',
516             -buttons => ['yes', 'no'],
517             -message => 'Do you really want to quit?',
518             window_style(),
519             );
520             save_state_file(config_file("conf.rc"));
521              
522             $single = 0;
523             for (my $i = 0; $i <= $stack_depth; ++$i) {
524             $stack[$i] = 0;
525             }
526              
527             $db_exit = 1;
528              
529             #print(STDERR $_, "\n") foreach (@compiled);
530             exit(0);
531             }
532              
533             sub db_cont {
534             $new_single = 0;
535             for (my $i = 0; $i <= $stack_depth; ++$i) {
536             $stack[$i] &= ~1;
537             }
538             $yield = 1;
539             }
540              
541             #
542             # Key for step into method
543             #
544             sub db_step_in {
545             $new_single = 1;
546             $yield = 1;
547             }
548              
549             #
550             # Key for step over - next step
551             #
552             sub db_step_over {
553             $new_single = 2;
554             $yield = 1;
555             }
556              
557             #
558             # Key for step from given method
559             #
560             sub db_step_out {
561             $new_single = 0;
562             $stack[-1] &= ~1;
563             $yield = 1;
564             }
565              
566             #
567             # $code is 0 or 1 and $r is ref to error string
568             # 0 - Set breakpoint, If breakpoint exist on given line, than remove
569             # 1 - Set breakpoint with condition
570             # StringRef - Problem with condition in breakpoint, that reedit
571             #
572             sub db_toggle_break {
573             my ($code, $r) = shift;
574             local (*dbline) = $main::{'_<' . $current_source->filename};
575             $current_source->toggle_break($code, $r);
576             }
577              
578             #
579             # Add watch expression
580             #
581             sub db_add_watch_expr {
582             my $text = shift;
583             my $expr = $cui->question(
584             -question => "Please enter an expression to watches\n"
585             . "Global variables must be set as '\$main::varname'\n"
586             . 'Array or Hash must set as Reference like \@a, otherwise show size',
587             -title => "Add watch expresion",
588             (defined($text) && length($text) ? (-answer => $text) : ()),
589             window_style(),
590             );
591             if (defined($text) && length($text)) {
592             my $pos = -1;
593             for (my $i = 0; $pos == -1 && $i < scalar(@watch_exprs); $i++) {
594             $pos = $i if ($watch_exprs[$i]->{name} eq $text);
595             }
596             splice(@watch_exprs, $pos, 1, {name => $expr}) if ($expr && $pos >= 0);
597             } else {
598             return if !$expr;
599             push @watch_exprs, {name => $expr};
600             }
601             $update_watch_list = 1;
602             }
603              
604             sub db_edit_watch_expr {
605             my $watch_list = shift;
606              
607             my $id = $watch_list->get_active_id;
608             my $item = $watch_list->{-named_list}->[$id];
609             db_add_watch_expr($item->{name});
610             }
611              
612             #
613             # List breapoints
614             #
615             sub ui_list_breakpoints {
616             my @a = ();
617             foreach my $file (keys %main::) { # file loop
618             next unless $file =~ /^_
619              
620             local (*dbline) = $main::{$file};
621             while (my ($k, $d) = each %dbline) {
622             next unless ($d);
623             my $str = retfilename($file) . " line:$k ";
624             if ($d =~ /\0/) {
625             my ($s, $action) = split(/\0/, $d);
626             $str .= "test ( $action )";
627             }
628             push(@a, $str);
629             }
630             } # end of file loop
631              
632             my $filename = $cui->tempdialog(
633             'Devel::PDB::Dialog::FileBrowser',
634             -title => "List all breakpoints",
635             -files => \@a,
636             -its_breakpoints => 1,
637             window_style(),
638             );
639              
640             if ($filename) {
641             my @a1 = split(" ", $filename);
642             my @a2 = split(":", $a1[1]);
643             my $source = $current_source = get_source($a1[0]);
644             if ($source) {
645             $sv->source($source);
646             $sv->goto(int($a2[1]) + 1);
647             }
648             $sv->intellidraw;
649             } else {
650             clearalldblines ();
651              
652             my %h = ();
653             foreach (@a) {
654             my @a1 = split(" ");
655             my @a2 = split(":", $a1[1]);
656              
657             my $fname = '_<' . $a1[0];
658             $h{$fname} = [] if (!exists($h{$fname}));
659             push(@{$h{$fname}}, {line => $a2[1]});
660             }
661             restore_breakpoints_from_save(\%h);
662             $update_watch_list = 1;
663              
664             my $view = $current_source->view;
665             $view->intellidraw if (defined $view);
666             }
667             }
668              
669             sub refresh_stack_menu {
670             my ($str, $name, $i, $sub_offset, $subStack);
671              
672             #
673             # CAUTION: In the effort to 'rationalize' the code
674             # are moving some of this function down from DB::DB
675             # to here. $sub_offset represents how far 'down'
676             # we are from DB::DB. The $DB::subroutine_depth is
677             # tracked in such a way that while we are 'in' the debugger
678             # it will not be incremented, and thus represents the stack depth
679             # of the target program.
680             #
681             $sub_offset = 1;
682             $subStack = [];
683              
684             # clear existing entries
685             for ($i = 0; $i <= ($DB::subroutine_depth || 0); $i++) {
686             my @a = caller $i + $sub_offset;
687             my ($package, $filename, $line, $subName) = caller $i + $sub_offset;
688             last if !$subName;
689             push @$subStack, {'name' => $subName, 'pck' => $package, 'filename' => $filename, 'line' => $line};
690             }
691              
692             #$self->{stack_menu}->menu->delete(0, 'last') ; # delete existing menu items
693             #for( $i = 0 ; $subStack->[$i] ; $i++ ) {
694             # $str = defined $subStack->[$i+1] ? "$subStack->[$i+1]->{name}" : "MAIN" ;
695             # my ($f, $line) = ($subStack->[$i]->{filename}, $subStack->[$i]->{line}) ; # make copies of the values for use in 'sub'
696             # $self->{stack_menu}->command(-label => $str, -command => sub { $self->goto_sub_from_stack($f, $line) ; } ) ;
697             #}
698             } # end of refresh_stack_menu
699              
700             # dump_trace(skip[,count])
701             #
702             # Actually collect the traceback information available via C. It does
703             # some filtering and cleanup of the data, but mostly it just collects it to
704             # make C's job easier.
705             #
706             # C defines the number of stack frames to be skipped, working backwards
707             # from the most current. C determines the total number of frames to
708             # be returned; all of them (well, the first 10^9) are returned if C
709             # is omitted.
710             #
711             # This routine returns a list of hashes, from most-recent to least-recent
712             # stack frame. Each has the following keys and values:
713             sub dump_trace {
714              
715             # How many levels to skip.
716             my $skip = shift;
717              
718             # How many levels to show. (1e9 is a cheap way of saying "all of them";
719             # it's unlikely that we'll have more than a billion stack frames. If you
720             # do, you've got an awfully big machine...)
721             my $count = shift || 1e9;
722              
723             # We increment skip because caller(1) is the first level *back* from
724             # the current one. Add $skip to the count of frames so we have a
725             # simple stop criterion, counting from $skip to $count+$skip.
726             $skip++;
727             $count += $skip;
728              
729             # These variables are used to capture output from caller();
730             my ($p, $file, $line, $sub, $h, $context);
731              
732             my ($e, $r, @a, @sub, $args);
733              
734             #.....
735             my @args = ();
736             our $frame = 0;
737              
738             # XXX Okay... why'd we do that?
739             my $nothard = not $frame & 8;
740             local $frame = 0;
741              
742             # Do not want to trace this.
743             my $otrace = $trace;
744             $trace = 0;
745              
746             # Start out at the skip count.
747             # If we haven't reached the number of frames requested, and caller() is
748             # still returning something, stay in the loop. (If we pass the requested
749             # number of stack frames, or we run out - caller() returns nothing - we
750             # quit.
751             # Up the stack frame index to go back one more level each time.
752             for (my $i = $skip; $i < $count and ($p, $file, $line, $sub, $h, $context, $e, $r) = caller($i); $i++) {
753              
754             # Go through the arguments and save them for later.
755             @a = ();
756             for my $arg (@args) {
757             my $type;
758             if (not defined $arg) { # undefined parameter
759             push @a, "undef";
760             }
761              
762             elsif ($nothard and tied $arg) { # tied parameter
763             push @a, "tied";
764             } elsif ($nothard and $type = ref $arg) { # reference
765             push @a, "ref($type)";
766             } else { # can be stringified
767             local $_ = "$arg"; # Safe to stringify now - should not call f().
768              
769             # Backslash any single-quotes or backslashes.
770             s/([\'\\])/\\$1/g;
771              
772             # Single-quote it unless it's a number or a colon-separated
773             # name.
774             s/(.*)/'$1'/s
775             unless /^(?: -?[\d.]+ | \*[\w:]* )$/x;
776              
777             # Turn high-bit characters into meta-whatever.
778             s/([\200-\377])/sprintf("M-%c",ord($1)&0177)/eg;
779              
780             # Turn control characters into ^-whatever.
781             s/([\0-\37\177])/sprintf("^%c",ord($1)^64)/eg;
782              
783             push(@a, $_);
784             } ## end else [ if (not defined $arg)
785             } ## end for $arg (@args)
786              
787             # If context is true, this is array (@)context.
788             # If context is false, this is scalar ($) context.
789             # If neither, context isn't defined. (This is apparently a 'can't
790             # happen' trap.)
791             $context = $context ? '@' : (defined $context ? "\$" : '.');
792              
793             # if the sub has args ($h true), make an anonymous array of the
794             # dumped args.
795             $args = $h ? [@a] : undef;
796              
797             # remove trailing newline-whitespace-semicolon-end of line sequence
798             # from the eval text, if any.
799             $e =~ s/\n\s*\;\s*\Z// if $e;
800              
801             # Escape backslashed single-quotes again if necessary.
802             $e =~ s/([\\\'])/\\$1/g if $e;
803              
804             # if the require flag is true, the eval text is from a require.
805             if ($r) {
806             $sub = "require '$e'";
807             }
808              
809             # if it's false, the eval text is really from an eval.
810             elsif (defined $r) {
811             $sub = "eval '$e'";
812             }
813              
814             # If the sub is '(eval)', this is a block eval, meaning we don't
815             # know what the eval'ed text actually was.
816             elsif ($sub eq '(eval)') {
817             $sub = "eval {...}";
818             }
819              
820             # Stick the collected information into @sub as an anonymous hash.
821             push(
822             @sub,
823             { context => $context,
824             sub => $sub,
825             args => $args,
826             file => $file,
827             line => $line
828             });
829              
830             # Stop processing frames if the user hit control-C.
831             last if $signal;
832             } ## end for ($i = $skip ; $i < ...
833              
834             # Restore the trace value again.
835             $trace = $otrace;
836             @sub;
837             } ## end sub dump_trace
838              
839             #
840             # List of stack - methods call
841             #
842             sub ui_view_stack {
843             my $rev = shift;
844              
845             my $i = -1;
846             my @a = ();
847             my %h = ();
848             my %h_ret = ();
849             foreach my $rh (dump_trace(2)) {
850             if ($rh->{'sub'} =~ /DB::DB/) {
851             $i = 1;
852             next;
853             } elsif ($i < 0) {
854             next;
855             }
856             push(@a, $i);
857             $h{$i} =
858             $rh->{'sub'} . "("
859             . (ref($rh->{args}) eq "ARRAY" ? join(",", @{$rh->{args}}) : "")
860             . ") in file "
861             . $rh->{file} . ":"
862             . $rh->{line};
863             $h_ret{$i} = $rh;
864             $i++;
865             }
866              
867             @a = reverse @a;
868             my $win = $cui->add(
869             'winstackwindow', 'Window',
870             -padtop => 1,
871             -border => 0,
872             -centered => 1,
873             -title => 'Stack',
874             window_style(),
875             );
876             my $listbox = $win->add(
877             'StackWindow', 'Listbox',
878             -title => "Stack window",
879             -y => 0,
880             -border => 1,
881             -padbottom => 1,
882              
883             #-width => $cui->canvaswidth,
884             -vscrollbar => 1,
885             -values => \@a,
886             -labels => \%h,
887              
888             #-onselchange => \&on_file_active,
889             window_style(),
890             );
891             $win->add(
892             "help", "Label",
893             -y => -1,
894             -width => -1,
895             -reverse => 1,
896             -paddingspaces => 1,
897             -text => " Ctrl+Q|Ctrl+C|F10|ESC - Exit | Ctrl+R|F2 - Reverse | Return - jump to given function "
898             );
899             $listbox->set_routine(
900             'option-select',
901             sub {
902             my $this = shift;
903              
904             #$this->{-id_value} = $this->get_active_value;
905             $this->loose_focus;
906             });
907              
908             $listbox->set_binding(sub { shift->loose_focus; }, "\cQ", "\cC", KEY_F(10), CUI_ESCAPE());
909              
910             $listbox->set_binding(sub { my $this = shift; my @ar = reverse @a; $this->values(\@ar); }, "\cR", KEY_F(2));
911              
912             my $sel = $listbox->modalfocus();
913             my $ia = $sel ? $sel->get_active_value() : undef;
914             $win->delete("StackWindow");
915             $cui->delete("winstackwindow");
916              
917             if ($ia) {
918             my $source = $current_source = get_source($h_ret{$ia}->{file});
919             $sv->source($source) if $source;
920             $sv->intellidraw;
921             $sv->goto($h_ret{$ia}->{line} + 1);
922             }
923              
924             $sv_win->focus;
925             }
926              
927             #
928             # UI export information
929             #
930             sub ui_db_export {
931             my $win = $cui->add(
932             'winexportwindow', 'Window',
933             -border => 1,
934             -centered => 1,
935             -title => 'Export information from actuall position to file',
936             -height => 14,
937             window_style(),
938             );
939              
940             $win->add("ExportLabel_1", 'Label', -y => 1, -x => 2, -text => 'Number of lines : ', -bold => 1);
941              
942             my $lines = 10;
943             $win->add(
944             "ExportNumber", "TextEntry",
945             -y => 1,
946             -x => 20,
947             -width => 20,
948             -text => $lines,
949             -regexp => '/^\d*$/',
950             -onchange => sub { $lines = shift->get(); },
951             );
952              
953             $win->add("ExportLabel_2", 'Label', -y => 3, -x => 2, -text => 'Filename : ', -bold => 1);
954              
955             my $filename = undef;
956             $win->add(
957             "ExportFilename", "TextEntry",
958             -y => 3,
959             -x => 14,
960             -width => 30,
961             -onchange => sub { $filename = shift->get(); },
962             );
963              
964             $win->add(
965             "ExportLabel_3", 'Label',
966             -y => 5,
967             -x => 2,
968             -text => 'Variables separated by space or export everything : ',
969             -bold => 1,
970             );
971             my $variables = undef;
972             $win->add(
973             "ExportVariables", "TextEntry",
974             -y => 6,
975             -x => 2,
976             -width => 30,
977             -onchange => sub { $variables = shift->get(); },
978             );
979              
980             my $use_watches = 0;
981             $win->add(
982             'ExportWatches', 'Checkbox',
983             -label => "Export all variables from watch tables",
984             -y => 8,
985             -x => 2,
986             -onchange => sub { $use_watches = shift->get(); },
987             window_style(),
988             );
989              
990             my $exit = 1;
991             $win->add(
992             'ExportButtons',
993             'Buttonbox',
994             -buttons => [{
995             -label => '< Ok >',
996             -shortcut => 'o',
997             -onpress => sub { $exit = 0; $win->loose_focus; }
998             },
999             { -label => '< Cancel >',
1000             -shortcut => 'c',
1001             -onpress => sub {
1002             $win->loose_focus;
1003             }
1004             }
1005             ],
1006             -y => 10,
1007             -x => 2,
1008             );
1009              
1010             $win->set_binding(sub { shift->loose_focus; }, "\cQ", "\cC", KEY_F(10), CUI_ESCAPE());
1011             my $sel = $win->modalfocus();
1012             $cui->delete("winexportwindow");
1013              
1014             local *W;
1015             if ($exit) {
1016             } elsif (!$filename || !length($filename)) {
1017             print_error("Filename must be set");
1018             } elsif (!open(W, ">$filename")) {
1019             print_error("Can't open file $filename : $!");
1020             } else {
1021             local (*dbline) = $main::{'_<' . $current_source->filename};
1022              
1023             my $current_line = $current_source->current_line;
1024             my $from = $current_line - $lines;
1025             $from = 0 if ($from < 0);
1026             my $to = $current_line + $lines;
1027             my $l = length(sprintf("%d", $to));
1028              
1029             print W "----- Filename : " . $current_source->filename . "----------\n";
1030             for my $i ($from .. $to) {
1031             last unless exists $dbline[$i];
1032             if ($i == 0 && $dbline[$i] =~ /use\s+.*Devel::_?PDB/) {
1033             $to++;
1034             next;
1035             }
1036             printf W "%s%*d %s", $i == $current_line ? '*' : ' ', $l, $i, $dbline[$i];
1037             }
1038              
1039             sub print_variables {
1040             my ($rh) = @_;
1041             print W $rh->{name} . " -> " . $rh->{long_value} . "\n";
1042             }
1043             print W "----- Stack : -------------\n";
1044             my %h = ();
1045             %h = map { $_ => 1 } split(" ", $variables) if (length($variables));
1046             foreach my $rh (@padlist_disp) {
1047             print_variables($rh) if (!keys(%h) || exists($h{$rh->{name}}));
1048             }
1049              
1050             if ($use_watches) {
1051             print W "----- Watches : -----------\n";
1052             foreach my $rh (@watch_exprs) {
1053             print_variables($rh);
1054             }
1055             }
1056              
1057             close(W);
1058             }
1059              
1060             $sv_win->focus;
1061             }
1062              
1063             #
1064             # UI open file
1065             #
1066             sub ui_open_file {
1067             my ($title, $files) = @_;
1068              
1069             my $filename = $cui->tempdialog(
1070             'Devel::PDB::Dialog::FileBrowser',
1071             -title => $title,
1072             -files => $files,
1073             window_style(),
1074             );
1075             if ($filename) {
1076             my $source = $current_source = get_source($filename);
1077             $sv->source($source) if $source;
1078             $sv->intellidraw;
1079             }
1080             }
1081              
1082             #
1083             # UI view STD[OUT|ERR] files
1084             #
1085             sub db_view_std_files {
1086             my ($use_exit) = @_;
1087             my @ab = ({
1088             -label => '< STDOUT >',
1089             -value => 1,
1090             -shortcut => 'o'
1091             },
1092             { -label => '< STDERR >',
1093             -value => 2,
1094             -shortcut => 'e'
1095             });
1096             unshift(
1097             @ab,
1098             { -label => '< Exit >',
1099             -value => -1,
1100             -shortcut => 'x'
1101             }) if ($use_exit);
1102              
1103             my $t = $cui->dialog(
1104             -title => 'Open STD* files',
1105             -buttons => \@ab,
1106             -message => 'Choose which STD* file to open it?',
1107             window_style(),
1108             );
1109             return if ($t == -1);
1110              
1111             my $text = "";
1112             if (open F, "<" . config_file($t == 2 ? "stderr" : "stdout")) {
1113             while () { $text .= $_ }
1114             close F;
1115             } else {
1116             $cui->error(-message => "Cannot read file " . config_file($t == 2 ? "stderr" : "stdout") . ":\n$!");
1117             exit(127);
1118             }
1119             my $win = $cui->add(
1120             'winmytextviewer', 'Window',
1121             -border => 0,
1122             -title => 'Source',
1123             window_style(),
1124             );
1125             my $textviewer = $win->add(
1126             "mytextviewer", "TextViewer",
1127             -homeonblur => 1, # cursor to homepos on blur?
1128             -fg => -1,
1129             -bg => -1,
1130             -cursor => 1,
1131             -border => 1,
1132             -padtop => 0,
1133             -padbottom => 1,
1134             -showlines => 0,
1135             -sbborder => 0,
1136             -vscrollbar => 1,
1137             -hscrollbar => 1,
1138             -showhardreturns => 0,
1139             -wrapping => 0, # wrapping slows down the editor :-(
1140             -text => $text,
1141             -title => " Viewing file STD" . ($t == 2 ? "ERR" : "OUT") . " : " . config_file($t == 2 ? "stderr" : "stdout"),
1142             window_style(),
1143             );
1144             $win->add(
1145             "help", "Label",
1146             -y => -1,
1147             -width => -1,
1148             -reverse => 1,
1149             -paddingspaces => 1,
1150             -text => " Ctrl+Q|Ctrl+C|F10|ESC - Return "
1151             );
1152             $textviewer->set_binding(sub { shift->loose_focus; }, "\cQ", "\cC", KEY_F(10), CUI_ESCAPE());
1153             $textviewer->modalfocus();
1154             $win->delete("mytextviewer");
1155             $cui->delete("winmytextviewer");
1156             }
1157              
1158             #
1159             # Change vertical size of windows. This change size of windows between Source and Watches+Stack
1160             # 1 - decrease Source window
1161             # -1 - increase Source window
1162             #
1163             sub ui_adjust_vert_parts {
1164             my $delta = shift;
1165             return
1166             if $delta > 0 && $sv_win->{-padbottom} >= $cui->{-height} - $sv_win->{-padtop} - 5
1167             or $delta < 0 && $lower_win->{-height} <= 5;
1168             $sv_win->{-padbottom} += $delta;
1169             $lower_win->{-height} += $delta;
1170             $cui->layout_contained_objects;
1171             }
1172              
1173             #
1174             # Change horizontal size of windows. This change size of windows between Watches expresion and Stack
1175             # 1 - increasing Watches window
1176             # -1 - decreasing Watches window
1177             #
1178             sub ui_adjust_hori_parts {
1179             my $delta = shift;
1180             return
1181             if $delta > 0 && $auto_win->{-width} >= $cui->{-width} - 15
1182             or $delta < 0 && $auto_win->{-width} <= 15;
1183             $auto_win->{-width} += $delta;
1184             $watch_win->{-padleft} += $delta;
1185             $cui->layout_contained_objects;
1186             }
1187              
1188             #
1189             # Return name for config file
1190             #
1191             sub config_file {
1192             my $name = shift;
1193             my $file_name = File::Basename::basename($Devel::PDB::scriptName);
1194             my $dir_name = File::Basename::dirname(Cwd::abs_path($Devel::PDB::scriptName));
1195             if ($ENV{PDB_use_HOME} && exists($ENV{HOME})) {
1196             $dir_name = $ENV{HOME} . "/.PDB";
1197             mkdir($dir_name) unless (-d $dir_name);
1198             }
1199             return $dir_name . "/.$file_name" . "-" . $name;
1200             }
1201              
1202             my $keys_binded = undef;
1203             my @keys_global = ();
1204             my %keys_hash = ();
1205              
1206             #
1207             # Set key
1208             # 1 - CodeRef for appened action
1209             # 2 - nickname for given action
1210             # 3 - Text which will be printed
1211             # 4 and others are keys for binding
1212             #
1213             sub set_key_binding($$@) {
1214             my $rf = shift;
1215             my $name = shift;
1216             my $text = shift;
1217             my @keys = @_;
1218              
1219             if (!defined($keys_binded)) {
1220             if (open(my $fh, $ENV{HOME} . "/.PDB.keys")) {
1221             while (<$fh>) {
1222             chomp;
1223             my @a = split("=");
1224             next if (scalar(@a) < 2);
1225             my @akeys = ();
1226             foreach my $r (split(",", $a[1])) {
1227             if ($r =~ /F/) {
1228             $r =~ s/F//;
1229             $r = KEY_F(int($r));
1230             } elsif ($r =~ /Control-/) {
1231             $r =~ s/Control-//;
1232             $r = chr(ord(uc($r)) & 0x1F);
1233             } elsif ($r =~ /KEY_/) {
1234             no strict;
1235             $r = $Curses::{$r} ? &{"Curses::" . $r}() : undef;
1236             }
1237             push(@akeys, $r) if ($r);
1238             }
1239             $keys_binded->{$a[0]} = \@akeys;
1240             }
1241             close($fh);
1242             } else {
1243             $keys_binded = {};
1244             }
1245             }
1246              
1247             push(@keys_global, {name => $text, key => \@keys});
1248             $cui->set_binding($rf, exists($keys_binded->{$name}) ? @{$keys_binded->{$name}} : @keys);
1249              
1250             $text .= " ";
1251             foreach my $k (exists($keys_binded->{$name}) ? @{$keys_binded->{$name}} : @keys) {
1252             my $key = $cui->key_to_ascii($k);
1253             $text .= $key . " ";
1254              
1255             # Add duplicity
1256             $keys_hash{$key} = [] unless (exists($keys_hash{$key}));
1257             my $ra = $keys_hash{$key};
1258             push(@$ra, $name);
1259             }
1260              
1261             return {-value => $rf, -label => $text};
1262             }
1263              
1264             sub val_unctrl {
1265             local ($_) = @_;
1266              
1267             return \$_ if ref \$_ eq "GLOB";
1268             if (ord('A') == 193) { # EBCDIC.
1269             # EBCDIC has no concept of "\cA" or "A" being related
1270             # to each other by a linear/boolean mapping.
1271             } else {
1272             s/([\001-\037\177])/'^'.pack('c',ord($1)^64)/eg;
1273             }
1274             $_;
1275             }
1276              
1277             #
1278             # Window wieving or editing
1279             # 1 - Editing program params
1280             # 2 - Editing enviroment
1281             # 3 - Viewing Perl special variables
1282             #
1283             sub ui_text_editor {
1284             my $type = shift;
1285              
1286             my @rows = ();
1287             my $str_title = "";
1288             my $str_label = "";
1289             my $use_editor = 1;
1290              
1291             if ($type == 1) {
1292             @rows = @Devel::PDB::script_args;
1293             $str_title = 'Edit program params';
1294             $str_label = " Enter => Save ";
1295             } elsif ($type == 2) {
1296             $str_title = 'Edit enviroments';
1297             $str_label = " F2 => Save ";
1298             foreach my $k (sort keys %ENV) {
1299             push(@rows, $k . "=" . $ENV{$k});
1300             }
1301             } elsif ($type == 3) {
1302             $str_title = 'View special variables';
1303             $use_editor = 0;
1304              
1305             sub rep_dumper {
1306             my $s = shift;
1307             $s =~ s/^\$//;
1308             chomp($s);
1309             return $s;
1310             }
1311              
1312             no strict;
1313             *stab = *{"main::"};
1314             foreach my $key (sort keys %stab) {
1315             next if ($key =~ /^_
1316             local (*entry) = $stab{$key};
1317              
1318             my $fileno;
1319              
1320             local $Data::Dumper::Purity = 0;
1321             local $Data::Dumper::Terse = 0;
1322             local $Data::Dumper::Indent = 2;
1323             local $Data::Dumper::Sortkeys = 1;
1324             if (defined $entry) {
1325             push(@rows, '$' . &val_unctrl($key) . " = " . $entry);
1326             } elsif (@entry) {
1327             local $Data::Dumper::Varname = "\@$key";
1328             push(@rows, &rep_dumper(Dumper(@entry)));
1329             } elsif ($key ne "main::"
1330             && $key ne "DB::"
1331             && %entry
1332             && $key !~ /::$/
1333             && !($package eq "dumpvar" and $key eq "stab")) {
1334             local $Data::Dumper::Varname = "\%$key";
1335             push(@rows, &rep_dumper(Dumper(%entry)));
1336             }
1337             }
1338             }
1339              
1340             my $row = scalar(@rows) || 1;
1341              
1342             my $win = $cui->add(
1343             'winChangeParams', 'Window',
1344             -border => 1,
1345              
1346             #-y => int(($LINES - ($row + 3)) / 2), # Buggy
1347             #-height => $row + 3,
1348             -centered => 1,
1349             -title => $str_title,
1350             window_style(),
1351             );
1352             my $x = $win->add(
1353             "ChangeParams", $use_editor ? "TextEditor" : "TextViewer",
1354             -homeonblur => 1, # cursor to homepos on blur?
1355             -fg => -1,
1356             -bg => -1,
1357             -cursor => 1,
1358             -padbottom => 1,
1359             -text => join("\n", @rows),
1360             );
1361             $win->add(
1362             "help", "Label",
1363             -y => -1,
1364             -width => -1,
1365             -reverse => 1,
1366             -paddingspaces => 1,
1367             -text => " Ctrl+Q|Ctrl+C|F10|ESC -> Return " . $str_label,
1368             );
1369              
1370             # Setup bindings.
1371             $x->clear_binding('loose-focus');
1372             $x->set_binding(sub { shift->loose_focus; }, "\cQ", "\cC", KEY_F(10), CUI_ESCAPE());
1373              
1374             if ($type == 1) {
1375             $x->set_binding(
1376             sub {
1377             my $this = shift;
1378             @Devel::PDB::script_args = ();
1379             foreach my $s (split("\n", $this->get())) {
1380             my $x = $s;
1381             $x =~ s/ //g;
1382             push(@Devel::PDB::script_args, $s) if (length($x));
1383             }
1384             $this->loose_focus;
1385             },
1386             KEY_ENTER(),
1387             KEY_BTAB(),
1388             CUI_TAB());
1389             } elsif ($type == 2) {
1390             $x->set_binding(
1391             sub {
1392             my $this = shift;
1393             %ENV = ();
1394             foreach my $s (split("\n", $this->get())) {
1395             my $x = $s;
1396             $x =~ s/ //g;
1397             if (length($x)) {
1398             my @a = split("=", $s);
1399             $ENV{$a[0]} = $a[1] if (scalar(@a) == 2);
1400             }
1401             }
1402             $this->loose_focus;
1403             },
1404             KEY_F(2));
1405             } elsif ($type == 3) {
1406             }
1407              
1408             $x->modalfocus();
1409             $win->delete('ChangeParams');
1410             $cui->delete('winChangeParams');
1411             $sv_win->focus;
1412             }
1413              
1414             #
1415             # Print helping keys association
1416             #
1417             sub ui_db_help {
1418             my @a = ();
1419             push(@a, "Global");
1420             foreach my $rh (@keys_global) {
1421             my $s = " ";
1422             foreach (@{$rh->{key}}) {
1423             $s .= $cui->key_to_ascii($_) . " ";
1424             }
1425             push(@a, $s . "\t" . $rh->{name});
1426             }
1427              
1428             push(@a, "Source Code Window");
1429             push(@a, " UP/DOWN/LEFT/RIGHT/PAGE UP/PAGE DOWN\tMove the cursor");
1430             push(@a, " H/J/K/L/Ctrl+F/Ctrl+B\tIf you use VI, you will know");
1431             push(@a, " /\tSearch using a RegEx in the current opened file");
1432             push(@a, " n\tSearch Next");
1433             push(@a, " N\tSearch Previous");
1434             push(@a, " Ctrl+G\tGoto a specific line");
1435              
1436             push(@a, "Lexical Variable Window / Watch Window");
1437             push(@a, " UP/DOWN\tMove the cursor");
1438             push(@a, " ENTER\tShow the Data::Dumper output of the highlighted item in a scrollable dialog");
1439             push(@a, " DEL\tRemove the highlighted expression (Watch Window only)");
1440              
1441             push(@a, "Compiled File Dialog / Opened File Dialog");
1442             push(@a, " TAB\tToggle the focus between the file list and the filter");
1443             push(@a, " ENTER\tSelect the highlighted file or apply the filter to the file list");
1444             push(@a, "Other");
1445             push(@a, " Esc,F10\tBack,Exit function");
1446             push(@a, " Ctrl+S,Ctrl+L,F6\tExporting to file");
1447              
1448             if (keys %keys_hash) {
1449             my @ad = ();
1450             foreach my $k (sort %keys_hash) {
1451             next if (ref($k));
1452             my $ra = $keys_hash{$k};
1453             next if (scalar(@$ra) <= 1);
1454             push(@ad, $k);
1455             push(@ad, map { $_ } @$ra);
1456             }
1457             push(@a, " ", " ", "Duplicity in keys", " ", @ad) if (@ad);
1458             }
1459              
1460             dialog_message(
1461             -title => "Help Keys",
1462             -message => join("\n", @a),
1463             );
1464             }
1465              
1466             #
1467             # Create dialog message window with binded key F2 for saving text
1468             #
1469             sub dialog_message {
1470             my %args = @_;
1471             Devel::PDB::Dialog::Message->run(%args, window_style());
1472             }
1473              
1474             #
1475             # Exporting to file
1476             #
1477             sub export_to_file {
1478             my ($name, $title, $rh_str) = @_;
1479              
1480             return unless $cui;
1481             $name ||= "Title";
1482             my $fname = $cui->question(-question => 'Add filename to export', DB::window_style()) || return;
1483             if (open(my $fh, ">", $fname)) {
1484             print $fh "----- $name : " . $title . " ----------\n" if ($title);
1485             print $fh $$rh_str;
1486             print $fh "\n";
1487             close($fh);
1488             } else {
1489             DB::print_error("Can't open file $fname : $!");
1490             }
1491             }
1492              
1493             #
1494             # Activate window
1495             #
1496             sub set_active_window {
1497             my $win = shift;
1498              
1499             if ($win == 2) {
1500             $ui_window_focused = 1;
1501             $auto_win->focus;
1502             } elsif ($win == 3) {
1503             $ui_window_focused = 2;
1504              
1505             #ui_update_watch_list();
1506             $watch_win->focus;
1507             } else {
1508             $ui_window_focused = 0;
1509             $sv_win->focus;
1510             }
1511             }
1512              
1513             #
1514             # Initialize ncurses methods
1515             #
1516             sub init {
1517              
1518             # Set own colours
1519             if (open(my $fh, $ENV{HOME} . "/.PDB.colours")) {
1520             my %h;
1521             while (<$fh>) {
1522             chomp;
1523             my @a = split(/\s+/);
1524             $h{$a[0]} = $a[1];
1525             }
1526             close($fh);
1527             window_style(%h);
1528             }
1529              
1530             # can anybody tell me why $win->notimeout(1) doesn't work?
1531             $ENV{ESCDELAY} = '0';
1532              
1533             $cui = new Curses::UI(
1534             -clear_on_exit => 1,
1535             -color_support => 1,
1536             -mouse_support => 1,
1537             );
1538              
1539             if ($Curses::UI::VERSION > 0.9602) {
1540              
1541             # In version 0.9603 has ben removed rootobject, but we need in this modules :
1542             # - PDB/SourceView.pm
1543             # - PDB/Dialog/Message.pm
1544             $Curses::UI::rootobject = $cui;
1545             }
1546              
1547             if ($Curses::UI::color_support) {
1548             my $old_draw = \&Curses::UI::Widget::draw;
1549             no warnings;
1550             *Curses::UI::Widget::draw = sub (;$) {
1551             my ($this) = @_;
1552             if (defined $this->{-fg} && defined $this->{-bg}) {
1553             my $canvas =
1554             defined $this->{-borderscr}
1555             ? $this->{-borderscr}
1556             : $this->{-canvasscr};
1557             $canvas->bkgdset(COLOR_PAIR($Curses::UI::color_object->get_color_pair($this->{-fg}, $this->{-bg})));
1558             }
1559             &$old_draw(@_);
1560             };
1561             }
1562              
1563             my $lower_height = int($cui->{-height} * 0.25);
1564             my $half_width = int($cui->{-width} * 0.5);
1565              
1566             $sv_win = $cui->add(
1567             'sv_win', 'Window',
1568             -padtop => 1,
1569             -padbottom => $lower_height,
1570             -border => 0,
1571             -ipad => 0,
1572             -title => 'Source',
1573             );
1574             $sv = $sv_win->add(
1575             'sv', 'Devel::PDB::SourceView',
1576             -border => 1,
1577              
1578             #-padbottom => 3,
1579             window_style(),
1580             );
1581              
1582             $lower_win = $cui->add(
1583             'lower_win', 'Window',
1584             -border => 0,
1585             -y => -1,
1586             -height => $lower_height,
1587             window_style(),
1588             );
1589              
1590             $auto_win = $lower_win->add(
1591             'auto_win', 'Window',
1592             -border => 1,
1593             -y => -1,
1594             -width => $half_width,
1595             -title => 'Auto',
1596             window_style(),
1597             );
1598             $padvar_list = $auto_win->add(
1599             'padvar_list', 'Devel::PDB::NamedListbox',
1600             -readonly => 1,
1601             -sort_key => 'name',
1602             -named_list => \@padlist_disp,
1603             );
1604             $padvar_list->userdata($cui);
1605              
1606             $watch_win = $lower_win->add(
1607             'watch_win', 'Window',
1608             -border => 1,
1609             -x => -1,
1610             -y => -1,
1611             -padleft => $half_width,
1612             -title => 'Watch',
1613             window_style(),
1614             );
1615             $watch_list = $watch_win->add(
1616             'watch_list', 'Devel::PDB::NamedListbox',
1617              
1618             # -sort_key => 'name', # For sorting by name
1619             -named_list => \@watch_exprs,
1620             );
1621              
1622             my $fConfig = config_file("conf");
1623              
1624             my @aFile = ();
1625             my @aEdit = ();
1626             my @aView = ();
1627             my @aExecution = ();
1628             my @aBreakpoint = ();
1629             my @aSettings = ();
1630              
1631             set_key_binding(\&ui_db_help, "Keys", "Keys help", "\cK");
1632             set_key_binding(sub { shift->getobj('menu')->focus }, "Menu", "Main menu", KEY_F(10));
1633              
1634             # Submenu - File
1635             push(@aFile, set_key_binding(sub { db_view_std_files(0); $sv_win->focus; }, "ViewSTDFiles", "View STD* files", KEY_F(4)));
1636              
1637             push(
1638             @aFile,
1639             set_key_binding(
1640             sub {
1641             if ($ui_window_focused == 2) {
1642             $update_watch_list = 1;
1643             return;
1644             }
1645              
1646             my $ret = $cui->dialog(
1647             -title => 'Restarting program',
1648             -buttons => [{
1649             -label => '< Save config first >',
1650             -value => 1,
1651             -shortcut => 's'
1652             },
1653             { -label => '< Restart only >',
1654             -value => 2,
1655             -shortcut => 'r'
1656             },
1657             { -label => '< Exit - Return >',
1658             -value => 0,
1659             -shortcut => 'x'
1660             },
1661             ],
1662             -message => 'Choose option to restarting program',
1663             window_style(),
1664             );
1665             if ($ret) {
1666             save_state_file($fConfig) if ($ret == 1);
1667             $db_exit = 1;
1668             DoRestart();
1669             }
1670             },
1671             "Restart",
1672             "Restart program",
1673             "\cR"
1674             ));
1675             push(
1676             @aFile,
1677             set_key_binding(
1678             sub {
1679             my $filename = $cui->filebrowser(
1680             -title => "Find and load Perl module from file ",
1681             -mask => [['\.p[lm]$', 'Perl modules']],
1682             DB::window_style(),
1683             );
1684             if ($filename) {
1685             if (!exists($main::{"_<$filename"})) {
1686              
1687             # Delete dir from modules in actuall directory
1688             my $dir = getcwd();
1689             if ($dir) {
1690             $dir .= "/";
1691             $filename =~ s/$dir//;
1692             }
1693             require $filename;
1694             }
1695             my $source = $current_source = get_source($filename);
1696             $sv->source($source) if $source;
1697             $sv->intellidraw;
1698             }
1699             $sv_win->focus;
1700             },
1701             "Filebrowser",
1702             "Find and load Perl module via browser",
1703             "\cF"
1704             ));
1705             push(
1706             @aFile,
1707             set_key_binding(
1708             sub { ui_open_file('Compiled Files', \@compiled); },
1709             "FilesCompiled", "Show 'Compiled Files' Dialog",
1710             KEY_F(11)));
1711             push(
1712             @aFile,
1713             set_key_binding(
1714             sub { ui_open_file('Opened Files', [keys(%sources)]); },
1715             "FilesOpened", "Show 'Opened Files' Dialog",
1716             KEY_F(12)));
1717             push(@aFile, set_key_binding(\&ui_db_export, "Export", "Export information", "\cY"));
1718             push(
1719             @aFile,
1720             set_key_binding(
1721             sub {
1722             redrawwin($stdscr);
1723             ui_update_watch_list();
1724             refresh_stack_menu();
1725             $cui->draw;
1726             },
1727             "Refresh",
1728             "Refresh windows",
1729             "\cN"
1730             ));
1731             push(@aFile, set_key_binding(\&ui_db_quit, "Quit", "Quit the debugger", "\cQ", "\cC"));
1732              
1733             # Submenu - Execution
1734             push(@aExecution, set_key_binding(\&db_cont, "Continue", "Run|Continue execution", KEY_F(5)));
1735             push(@aExecution, set_key_binding(\&db_step_out, "StepOut", "Step Out", KEY_F(6)));
1736             push(@aExecution, set_key_binding(\&db_step_in, "StepIn", "Step In", KEY_F(7)));
1737             push(@aExecution, set_key_binding(\&db_step_over, "StepOver", "Step Over", KEY_F(8)));
1738             push(
1739             @aExecution,
1740             set_key_binding(
1741             sub {
1742             if ($ui_window_focused == 2) {
1743             db_edit_watch_expr($watch_list);
1744             } else {
1745             ui_text_editor(1);
1746             }
1747             },
1748             "ArgumentsEdit",
1749             "Edit program paramaters or watched variable",
1750             "\cE"
1751             ));
1752             push(@aExecution, set_key_binding(sub { ui_text_editor(2); }, "EnviromentsEdit", "Edit enviroment paramaters", "\cM"));
1753             push(
1754             @aExecution,
1755             set_key_binding(
1756             sub {
1757             my $ret = $cui->question(
1758             -title => 'Command Execution',
1759             -question => 'Please enter an command to enter',
1760             DB::window_style(),
1761             );
1762             $usercontext = $ret if ($ret);
1763             },
1764             "RunCommand",
1765             "Run perl command",
1766             "\cP"
1767             ));
1768              
1769             # Submenu - Breakpoint
1770             push(@aBreakpoint,
1771             set_key_binding(sub { set_active_window(1); db_toggle_break(0, undef) }, "Breakpoint", "Toggle Breakpoint", KEY_F(9)));
1772             push(
1773             @aBreakpoint,
1774             set_key_binding(
1775             sub { set_active_window(1); db_toggle_break(1, undef) },
1776             "BreakpointCode", "Toggle Breakpoint Code", "\cO"
1777             ));
1778             push(@aBreakpoint, set_key_binding(sub { db_add_watch_expr(undef) }, "WatchExpression", "Add watch expression", "\cW"));
1779             push(@aBreakpoint, set_key_binding(\&ui_list_breakpoints, "ListBreakpoints", "List all breakpoints", "\cB"));
1780             push(@aBreakpoint, set_key_binding(\&clearalldblines, "ClearBreakpoints", "Clear all breakpoints"));
1781             push(@aBreakpoint,
1782             set_key_binding(sub { @watch_exprs = (); $update_watch_list = 1; }, "ClearWatches", "Clear all watches"));
1783             push(
1784             @aBreakpoint,
1785             set_key_binding(
1786             sub { &clearalldblines(); @watch_exprs = (); $update_watch_list = 1; },
1787             "ClearAll", "Clear all settings", "\cX"
1788             ));
1789              
1790             # Submenu - Settings
1791             push(
1792             @aSettings,
1793             set_key_binding(
1794             sub {
1795             my $ret = $cui->dialog(
1796             -title => 'Load saved config files',
1797             -buttons => [{
1798             -label => '< User conf >',
1799             -value => 1,
1800             -shortcut => 'u'
1801             },
1802             { -label => '< Default conf >',
1803             -value => 2,
1804             -shortcut => 'd'
1805             },
1806             { -label => '< Exit >',
1807             -value => 0,
1808             -shortcut => 'x'
1809             },
1810             ],
1811             -message => 'Do you really want load config?',
1812             window_style(),
1813             );
1814             if ($ret) {
1815             load_state_file($fConfig, ($ret == 2 ? ".rc" : ""));
1816             $user_conf_readed = $ret == 1 ? 1 : 0;
1817             }
1818             },
1819             "ConfigLoad",
1820             "Load config file",
1821             "\cL"
1822             ));
1823             push(
1824             @aSettings,
1825             set_key_binding(
1826             sub {
1827             save_state_file($fConfig)
1828             if $cui->dialog(
1829             -title => 'Save config file',
1830             -buttons => ['yes', 'no'],
1831             -message => 'Do you really want save config?',
1832             window_style(),
1833             );
1834             },
1835             "ConfigSave",
1836             "Save config file",
1837             "\cS"
1838             ));
1839              
1840             # Submenu - View
1841             push(
1842             @aView,
1843             set_key_binding(
1844             sub {
1845             my $text;
1846             local $Data::Dumper::Purity = 0;
1847             local $Data::Dumper::Terse = 1;
1848             local $Data::Dumper::Indent = 2;
1849             local $Data::Dumper::Sortkeys = 1;
1850             $text = (scalar(@Devel::PDB::script_args) ? Dumper(@Devel::PDB::script_args) : "Not arguments putted");
1851             dialog_message(
1852             -title => "Arguments",
1853             -message => $text
1854             );
1855              
1856             },
1857             "Arguments",
1858             "View program parameters",
1859             "\cA"
1860             ));
1861             push(@aView, set_key_binding(sub { set_active_window(1) }, "WindowSource", "Switch to the Source Code Window", KEY_F(1)));
1862             push(@aView,
1863             set_key_binding(sub { set_active_window(2) }, "WindowLexical", "Switch to the Lexical Variable Window", KEY_F(2)));
1864             push(@aView, set_key_binding(sub { set_active_window(3) }, "WindowWatches", "Switch to the Watch Window", KEY_F(3)));
1865             push(@aView, set_key_binding(sub { ui_view_stack(0) }, "WindowStack", "View Stack Window", "\cT"));
1866             push(@aView, set_key_binding(sub { ui_text_editor(3) }, "ViewVariables", "View special variables", "\cU"));
1867              
1868             push(@aView,
1869             set_key_binding(sub { ui_adjust_vert_parts(1) }, "VerticalPartsMin", "Vertical window(Source file) minimize", '{'));
1870             push(@aView,
1871             set_key_binding(sub { ui_adjust_vert_parts(-1) }, "VerticalPartsMax", "Vertical window(Source file) maximize", '}'));
1872             push(@aView,
1873             set_key_binding(sub { ui_adjust_hori_parts(-1) }, "HorizontalPartsMin", "Horizontal window(Stack) minimize", '['));
1874             push(@aView,
1875             set_key_binding(sub { ui_adjust_hori_parts(1) }, "HorizontalPartsMin", "Horizontal window(Stack) maximize", ']'));
1876              
1877             $cui->add(
1878             'menu',
1879             'Menubar',
1880             -menu => [{
1881             -label => 'File',
1882             -submenu => \@aFile,
1883             },
1884             { -label => 'View',
1885             -submenu => \@aView,
1886             },
1887             { -label => 'Execution',
1888             -submenu => \@aExecution,
1889             },
1890             { -label => 'Breakpoint',
1891             -submenu => \@aBreakpoint,
1892             },
1893             { -label => 'Settings',
1894             -submenu => \@aSettings,
1895             },
1896             { -label => 'Help',
1897             -submenu => [{
1898             -label => 'Keys',
1899             -value => \&ui_db_help,
1900             },
1901             { -label => 'About',
1902             -value => sub {
1903             dialog_message(
1904             -title => "About",
1905             -message => <
1906             Devel::PDB - A simple Curses-based Perl DeBugger in version $VERSION
1907              
1908             PerlDeBugger is a Curses-based Perl debugger with most of the essential functions such as monitoring windows for paddlist,
1909             call stack, custom watch expressions, etc.
1910             Suitable for debugging or tracing complicated Perl applications on the spot.
1911              
1912             AUTHORS
1913             Ivan Yat-Cheung Wong
1914             Igor Bujna
1915              
1916             MODULES
1917             Curses - $Curses::VERSION
1918             Curses:UI - $Curses::UI::VERSION
1919              
1920             EOF
1921             ,
1922             DB::window_style(),
1923             );
1924             },
1925             },
1926             ]
1927             },
1928             ],
1929             window_style(),
1930             );
1931              
1932             #open my $fd0, '>stdout';
1933             #open my $fd1, '>stderr';
1934             #open STDOUT, ">&$fd0";
1935             #open STDERR, ">&$fd1";
1936             #open STDOUT, ">stdout";
1937              
1938             unlink config_file($_) foreach ('stderr', 'stdout');
1939             open STDERR, ">>" . config_file("stderr");
1940             open $output, ">>" . config_file("stdout");
1941             open $stdout, ">>&STDOUT";
1942              
1943             select(STDERR);
1944             $| = 1;
1945             select(STDOUT);
1946             $| = 1;
1947              
1948             $inited = 1;
1949              
1950             # Load actual breakpoints and watches
1951             load_state_file(config_file("conf.rc"));
1952             }
1953              
1954             #
1955             # Return for given filename which find or creater for given param
1956             #
1957             sub get_source {
1958             my $filename = shift;
1959             my $source = $sources{$filename};
1960              
1961             if (!defined $source) {
1962             local (*dbline) = $main::{"_<$filename"};
1963             $sources{$filename} = $source = new Devel::PDB::Source(
1964             filename => $filename,
1965             lines => \@dbline,
1966             breaks => \%dbline,
1967             );
1968             }
1969              
1970             return $source;
1971             }
1972              
1973             #
1974             # Updating watch list in Watches window
1975             #
1976             sub ui_update_watch_list {
1977             local $Data::Dumper::Terse = 1;
1978             local $Data::Dumper::Maxdepth;
1979             local $Data::Dumper::Indent;
1980             local $Data::Dumper::Sortkeys = 1;
1981              
1982             foreach my $expr (@watch_exprs) {
1983             $evalarg = $expr->{name};
1984             my $res = &DB::eval;
1985             $Data::Dumper::Indent = 0;
1986             $Data::Dumper::Maxdepth = 2;
1987             $expr->{value} = Dumper $res;
1988             $Data::Dumper::Indent = 1;
1989             $Data::Dumper::Maxdepth = 0;
1990             $expr->{long_value} = Dumper $res;
1991             }
1992              
1993             $watch_list->update;
1994             }
1995              
1996             #
1997             # Perl Debugger methods
1998             #
1999             my @saved;
2000              
2001             sub save {
2002             @saved = ($@, $!, $,, $/, $\, $^W);
2003             $, = '';
2004             $/ = "\n";
2005             $\ = '';
2006             $^W = 0;
2007             }
2008              
2009             sub eval {
2010             ($@, $!, $,, $/, $\, $^W) = @saved;
2011             my $res = eval "package $package; $evalarg";
2012              
2013             #my $res = eval 'no strict;($@, $!, $^E, $,, $/, $\, $^W) = @saved;' . "package $package;$evalarg ;";
2014              
2015             save;
2016             $res;
2017             }
2018              
2019             # Main method which is load when program started, stopped or step in position where is breakpoint
2020             sub DB {
2021             return if $exit;
2022             save;
2023             init if !$inited;
2024              
2025             RESTART:
2026             open STDOUT, ">>&", $stdout;
2027              
2028             ($package, $filename, $line) = caller;
2029              
2030             my $scope = $current_sub ? $current_sub : $package;
2031             my $renew = !defined $padlist_scope || $scope ne $padlist_scope;
2032             if ($renew) {
2033             %padlist = ();
2034             @padlist_disp = ();
2035             $padlist_scope = $scope;
2036             }
2037              
2038             # BUGS:
2039             # compadlist not return, not defined variables.
2040             # Variables must be defined via (my,our,....etc) or 'use strict;' on yours script
2041             {
2042             my ($names, $vals) =
2043             $scope eq 'main'
2044             ? comppadlist->ARRAY
2045             : svref_2object(\&$scope)->PADLIST->ARRAY;
2046             my @names = $names->ARRAY;
2047             my @vals = $vals->ARRAY;
2048             my $count = @names;
2049              
2050             refresh_stack_menu();
2051              
2052             local $Data::Dumper::Terse = 1;
2053             local $Data::Dumper::Maxdepth;
2054             local $Data::Dumper::Indent;
2055             local $Data::Dumper::Sortkeys = 1;
2056              
2057             my %h_pd = map { $_->{name} => $_ } @padlist_disp;
2058              
2059             for (my ($i, $j) = (0, 0); $i < $count; $i++) {
2060             my $sv = $names[$i];
2061             next if class($sv) eq 'SPECIAL';
2062             my $name = $sv->PVX;
2063             $Data::Dumper::Indent = 0;
2064             $Data::Dumper::Maxdepth = 2;
2065             my $val = Dumper $vals[$i]->object_2svref;
2066             $val =~ s/^\\// if class($sv) ne 'RV';
2067             $Data::Dumper::Indent = 1;
2068             $Data::Dumper::Maxdepth = 0;
2069             my $long_val = Dumper $vals[$i]->object_2svref;
2070             $long_val =~ s/^\\// if class($sv) ne 'RV';
2071              
2072             if ($renew || $val ne $padlist{$name}) {
2073             my $rh = {name => $name, value => $val, long_value => $long_val};
2074             $padlist_disp[$j] = $rh;
2075             $padlist{$name} = $val;
2076             $h_pd{$name} = $rh;
2077             }
2078             ++$j;
2079             }
2080              
2081             # Sorting values in stack by name
2082             @padlist_disp = ();
2083             @padlist_disp = sort { $a->{name} cmp $b->{name} } values %h_pd;
2084              
2085             $padvar_list->update($renew);
2086             }
2087              
2088             #local (*dbline) = $main::{"_<$filename"};
2089             $sv->source($current_source = get_source($filename));
2090             $current_source->current_line($line);
2091              
2092             ui_update_watch_list;
2093              
2094             $yield = 0;
2095              
2096             # Breakpoint with action
2097             my $brkp = $current_source->ret_line_breakpoint();
2098             my ($stop, $action) = $brkp ? split(/\0/, $brkp) : ();
2099             if ($action) {
2100             my $res = eval "return 1 if ($action); return 0;\n";
2101             if ($@) {
2102             my $str = $@;
2103             db_toggle_break(1, \$str);
2104             }
2105             $yield = 1 unless ($res);
2106             }
2107              
2108             $new_single = $single;
2109             $cui->focus(undef, 1);
2110             $cui->draw;
2111             $update_watch_list = 0;
2112             while (!$yield) {
2113              
2114             # Wait for any key
2115             $cui->do_one_event;
2116             if ($update_watch_list) {
2117             ui_update_watch_list;
2118             $cui->draw;
2119             }
2120              
2121             if ($usercontext) { # User eval
2122             #my $usc = 'no strict;($@, $!, $^E, $,, $/, $\, $^W) = @saved;' . "package $package;";
2123             #my $arg = "\$^D = \$^D | \$DB::db_stop;\n$usercontext";
2124             #eval "$usc $arg;\n";
2125             eval "$usercontext;\n";
2126             print_error($@) if ($@);
2127             $usercontext = undef;
2128             goto RESTART;
2129             }
2130             }
2131             $single = $new_single;
2132              
2133             open STDOUT, ">>&", $output;
2134             ($@, $!, $,, $/, $\, $^W) = @saved;
2135             }
2136              
2137             sub sub {
2138             my ($ret, @ret);
2139              
2140             local $current_sub = $sub;
2141             local $stack_depth = $stack_depth + 1;
2142             $#stack = $stack_depth;
2143             $stack[-1] = $single;
2144             $single &= 1;
2145              
2146             if (wantarray) {
2147             no strict;
2148             @ret = &$sub;
2149             use strict;
2150             $single |= $stack[$stack_depth--];
2151             @ret;
2152             } else {
2153             if (defined wantarray) {
2154             no strict;
2155             $ret = &$sub;
2156             use strict;
2157             } else {
2158             no strict;
2159             &$sub;
2160             use strict;
2161             undef $ret;
2162             }
2163              
2164             $single |= $stack[$stack_depth--];
2165             $ret;
2166             }
2167             }
2168              
2169             sub postponed {
2170             my $file = shift;
2171             push @compiled, $$file;
2172              
2173             my $key = "_<" . $$file;
2174             return if (!exists($postponed_file{$key}));
2175              
2176             set_breakpoints($key, $postponed_file{$key});
2177             delete($postponed_file{$key});
2178              
2179             }
2180              
2181             package Devel::PDB;
2182              
2183             1;
2184              
2185             __END__