File Coverage

lib/Devel/Trepan/CmdProcessor/Command/List.pm
Criterion Covered Total %
statement 78 314 24.8
branch 0 156 0.0
condition 0 60 0.0
subroutine 26 36 72.2
pod 0 10 0.0
total 104 576 18.0


line stmt bran cond sub pod time code
1             # -*- coding: utf-8 -*-
2             # Copyright (C) 2011-2012, 2014-2015 Rocky Bernstein <rocky@cpan.org>
3 12     12   91 use warnings; no warnings 'redefine';
  12     12   32  
  12     1   369  
  12     1   61  
  12         30  
  12         347  
  1         8  
  1         3  
  1         23  
  1         5  
  1         4  
  1         32  
4 12     12   62 use rlib '../../../..';
  12     1   24  
  12         68  
  1         6  
  1         2  
  1         5  
5              
6             # require_relative '../../app/condition'
7              
8             package Devel::Trepan::CmdProcessor::Command::List;
9 12     12   4130 use English qw( -no_match_vars );
  12     1   28  
  12         103  
  1         310  
  1         3  
  1         10  
10 12     12   5030 use Devel::Trepan::DB::LineCache;
  12     1   32  
  12         1963  
  1         293  
  1         2  
  1         136  
11 12     12   81 use Devel::Trepan::CmdProcessor::Validate;
  12     1   36  
  12         438  
  1         6  
  1         3  
  1         28  
12 12     12   67 use if !@ISA, Devel::Trepan::CmdProcessor::Command;
  12     1   26  
  12         74  
  1         4  
  1         3  
  1         6  
13             unless (@ISA) {
14 12     12   90 eval <<'EOE';
  12     12   28  
  12     12   759  
  12     12   69  
  12     12   30  
  12     12   523  
  12         73  
  12         45  
  12         545  
  12         78  
  12         27  
  12         787  
  12         76  
  12         31  
  12         540  
  12         82  
  12         32  
  12         507  
15             use constant ALIASES => qw(l list> l>);
16             use constant CATEGORY => 'files';
17             use constant SHORT_HELP => 'List source code';
18             use constant MIN_ARGS => 0; # Need at least this many
19             use constant MAX_ARGS => 3; # Need at most this many - undef -> unlimited.
20             use constant NEED_STACK => 0;
21             EOE
22             }
23              
24 12     12   1749 use strict; use vars qw(@ISA); @ISA = @CMD_ISA;
  12     12   36  
  12     1   247  
  12     1   58  
  12         27  
  12         523  
  1         63  
  1         2  
  1         24  
  1         5  
  1         2  
  1         43  
25 12     12   62 use vars @CMD_VARS; # Value inherited from parent
  12     1   28  
  12         17689  
  1         6  
  1         2  
  1         1443  
26              
27             our $NAME = set_name();
28             =head2 Synopsis:
29              
30             =cut
31             our $HELP = <<'HELP';
32             =pod
33              
34             B<list>[E<gt>] [I<filename>] [I<first> [I<number>]]
35              
36             B<list>[E<gt>] I<location> [I<number>]
37              
38             List Perl source code.
39              
40             Without arguments, prints lines centered around the current
41             line. If this is the first list command issued since the debugger
42             command loop was entered, then the current line is the current
43             frame. If a subsequent list command was issued with no intervening
44             frame changing, then that is start the line after we last one
45             previously shown.
46              
47             If the command has a '>' suffix, then line centering is disabled and
48             listing begins at the specificed location.
49              
50             The number of lines to show is controlled by the debugger "listsize"
51             setting. Use L<C<set max
52             list>|Devel::Trepan::CmdProcessor::Set::Max::List> or L<C<show max
53             list>|Devel::Trepan::CmdProcessor::Show::Max::List> to see or set the
54             value.
55              
56             If the location form is used with a subsequent parameter, the
57             parameter is the starting line number. When there two numbers are
58             given, the last number value is treated as a stopping line unless it
59             is less than the start line, in which case it is taken to mean the
60             number of lines to list instead.
61              
62             =head2 Examples:
63              
64             list 5 # List centered around line 5
65             list 5> # List starting at line 5
66             list foo.pl 5 # Same as above.
67             list foo.pl 5 6 # list lines 5 and 6 of foo.pl
68             list foo.pl 5 2 # Same as above, since 2 < 5.
69             list . # List lines centered from where we currently are stopped
70             list . 3 # List 3 lines starting from where we currently are stopped
71             # if . > 3. Otherwise we list from . to 3.
72             list - # List lines previous to those just shown
73              
74             The output of the list command give a line number, and some status
75             information about the line and the text of the line. Here is some
76             hypothetical list output modeled roughly around line 251 of one
77             version of this code:
78              
79             251 cmd.proc.frame_setup(tf)
80             252 -> brkpt_cmd.run(['break'])
81             253 B01 line = __LINE__
82             254 b02 cmd.run(['list', __LINE__.to_s])
83             255 t03 puts '--' * 10
84              
85             Line 251 has nothing special about it. Line 252 is where we are
86             currently stopped. On line 253 there is a breakpoint 1 which is
87             enabled, while at line 255 there is an breakpoint 2 which is
88             disabled.
89              
90             =head2 See also:
91              
92             L<C<set
93             autolist>|Devel::Trepan::CmdProcessor::Command::Set::Auto::List>,
94             L<C<help syntax
95             location>|Devel::Trepan::CmdProcessor::Command::Help::location>,
96             L<C<disassemble>|Devel::Trepan::CmdProcessor::Command::Disassemble>,
97             and L<C<deparse>|Devel::Trepan::CmdProcessor::Command::Deparse>.
98              
99             =cut
100             HELP
101              
102             # FIXME: Should we include all files?
103             # Combine with BREAK completion.
104             sub complete($$)
105             {
106 0     0 0   my ($self, $prefix) = @_;
  0     0 0    
107 0           my $filename = $self->{proc}->filename;
  0            
108             # For line numbers we'll use stoppable line number even though one
109             # can enter line numbers that don't have breakpoints associated with them
110 0           my @completions = sort(('.', '-', file_list, DB::subs(),
  0            
111             trace_line_numbers($filename)));
112 0           Devel::Trepan::Complete::complete_token(\@completions, $prefix);
  0            
113             }
114              
115             # If last is less than first, assume last is a count rather than an
116             # end line number.
117             sub adjust_end($$)
118             {
119 0     0 0   my ($start, $end) = @_;
  0     0 0    
120 0 0         return ($start < $end ) ? $start + $end - 1 : $end;
  0 0          
121             }
122              
123             sub no_frame_msg($)
124             {
125 0     0 0   my $self = shift;
  0     0 0    
126 0           $self->errmsg("No Perl program loaded.");
  0            
127 0           return (undef, undef, undef);
  0            
128             }
129              
130              
131             # What a mess. Necessitated I suppose because we want to allow
132             # somewhat flexible parsing with either module names, files or none
133             # and optional line counts or end-line numbers.
134             # TODO: allow a negative start to count from the end of the file.
135              
136             # Parses arguments for the "list" command and returns the tuple:
137             # filename, start, last
138             # or sets these to nil if there was some problem.
139             sub parse_list_cmd($$$$)
140             {
141 0     0 0   my ($self, $args, $listsize, $center_correction) = @_;
  0     0 0    
142 0           my $proc = $self->{proc};
  0            
143 0           my $frame = $proc->{frame};
  0            
144 0           my @args = @$args;
  0            
145 0           shift @args;
  0            
146              
147 0           my $filename = $proc->{list_filename};
  0            
148 0           my $fn;
  0            
149 0           my ($start, $end);
  0            
150              
151 0 0 0       if (scalar @args > 0) {
  0 0 0        
    0 0        
    0 0        
152 0 0         if ($args[0] eq '-') {
  0 0          
    0          
    0          
153 0 0         return $self->no_frame_msg() unless $proc->{list_line};
  0 0          
154 0           $start = $proc->{list_line} - 2*$listsize;
  0            
155 0 0         $start = 1 if $start < 1;
  0 0          
156             } elsif ($args[0] eq '.') {
157 0 0         return $self->no_frame_msg() unless $frame->{line};
  0 0          
158 0           $filename = $proc->filename;
  0            
159 0           $start = $proc->line;
  0            
160 0 0         $start = 1 if $start < 1;
  0 0          
161 0 0         if (scalar @args == 2) {
  0 0          
162 0           my $opts = {
  0            
163             'msg_on_error' =>
164             "${NAME} command $end or count parameter expected, " .
165             "got: $args[2]"
166             };
167 0           my $second = $proc->get_an_int($args[1], $opts);
  0            
168 0 0         return (undef, undef, undef) unless $second;
  0 0          
169 0           $end = $self->adjust_end($start, $second);
  0            
170             }
171             } else {
172 0           my ($rest, $gobble_count);
  0            
173 0           ($filename, $start, $fn, $gobble_count, $rest) = $proc->parse_position(\@args);
  0            
174 0 0         return (undef, undef, undef) unless defined $start;
  0 0          
175 0 0         shift @args if $gobble_count > 0;
  0 0          
176             # error should have been shown previously
177             }
178 0 0 0       if (scalar @args <= 1) {
  0 0 0        
    0 0        
    0 0        
    0          
    0          
179 0 0 0       $start = 1 if !$start and $fn;
  0 0 0        
180 0           $start = $start - $center_correction;
  0            
181 0 0         $start = 1 if $start < 1;
  0 0          
182             } elsif (scalar @args == 2 or (scalar @args == 3 and $fn)) {
183 0           my $opts = {
  0            
184             msg_on_error =>
185             "${NAME} command starting line expected, got $args[-1]"
186             };
187 0           $end = $proc->get_an_int($args[1], $opts);
  0            
188 0 0         return (undef, undef, undef) unless $end;
  0 0          
189 0 0         if ($fn) {
  0 0          
190 0 0         if ($start) {
  0 0          
191 0           $start = $end;
  0            
192 0 0 0       if (scalar @args == 3 and $fn) {
  0 0 0        
193 0           my $opts = {
  0            
194             'msg_on_error' =>
195             ("${NAME} command $end or count parameter expected, " .
196 0           "got: ${$args[2]}.")};
  0            
197 0           $end = $proc->get_an_int($args[2], $opts);
  0            
198 0 0         return (undef, undef, undef) unless $end;
  0 0          
199             }
200             }
201             }
202 0           $end = $self->adjust_end($start, $end);
  0            
203             } elsif (! $fn) {
204 0           $proc->errmsg('At most 2 parameters allowed when no module' .
  0            
205             " name is found/given. Saw: @args parameters");
206 0           return (undef, undef, undef);
  0            
207             } else {
208 0           $proc->errmsg('At most 3 parameters allowed when a module' +
  0            
209             " name is given. Saw: @args parameters");
210 0           return (undef, undef, undef);
  0            
211             }
212             } elsif ($frame && !$frame->{line} and $proc->{frame}) {
213 0           $start = $frame->{line} - $center_correction;
  0            
214             } else {
215 0   0       $start = ($proc->{list_line} || $frame->{line}) - $center_correction;
  0   0        
216             }
217 0 0         $start = 1 if $start < 1;
  0 0          
218 0 0         $end = $start + $listsize - 1 unless $end;
  0 0          
219              
220 0 0         cache_file($filename) unless is_cached($filename);
  0 0          
221 0           return ($filename, $start, $end);
  0            
222             }
223              
224             # This method runs the command
225             sub run($$)
226             {
227 0     0 0   my ($self, $args) = @_;
  0     0 0    
228 0           my $proc = $self->{proc};
  0            
229              
230 0           my $listsize = $proc->{settings}{maxlist};
  0            
231 0 0         my $center_correction =
  0 0          
232             (substr($args->[0], -1, 1) eq '>') ? 0 : int(($listsize-1) / 2);
233              
234 0           my ($filename, $start, $end) = parse_list_cmd($self, $args, $listsize,
  0            
235             $center_correction);
236 0 0         return unless $filename;
  0 0          
237              
238             # We now have range information. Do the listing.
239 0           my $max_line = Devel::Trepan::DB::LineCache::size($filename);
  0            
240 0           $filename = map_file($filename);
  0            
241 0 0         unless (defined $max_line) {
  0 0          
242 0           $proc->errmsg("File \"$filename\" not found.");
  0            
243 0           return;
  0            
244             }
245              
246 0 0         if ($start > $max_line) {
  0 0          
247 0           my $mess = sprintf('Bad line range [%d...%d]; file "%s" has only %d lines',
  0            
248             $start, $end, $proc->canonic_file($filename), $max_line);
249 0           $proc->errmsg($mess);
  0            
250 0           return;
  0            
251             }
252              
253 0 0         if ($end > $max_line) {
  0 0          
254             # msg('End position changed to end line %d ' % max_line)
255 0           $end = $max_line;
  0            
256             }
257              
258             # begin
259             my $opts = {
260             reload_on_change => $proc->{settings}{reload},
261             output => $proc->{settings}{highlight}
262 0           };
  0            
263 0           my $bp;
  0            
264 0           local(*DB::dbline) = "::_<$filename";
  0            
265 0           my $lineno;
  0            
266 0           my $msg = sprintf("%s [%d-%d]",
  0            
267             $proc->canonic_file($filename), $start, $end);
268              
269             # FIXME: put in frame?
270 0           my $frame_filename = $proc->filename();
  0            
271 0 0         $frame_filename = map_file($frame_filename)
  0 0          
272             if filename_is_eval($frame_filename);
273              
274 0           $self->section($msg);
  0            
275 0           for ($lineno = $start; $lineno <= $end; $lineno++) {
  0            
276 0           my $a_pad = ' ';
  0            
277 0           my $line = getline($filename, $lineno, $opts);
  0            
278 0 0         unless (defined $line) {
  0 0          
279 0 0         if ($lineno > $max_line) {
  0 0          
280 0           $proc->msg('[EOF]');
  0            
281 0           last;
  0            
282             } else {
283 0           $line = '';
  0            
284             }
285             }
286 0           chomp $line;
  0            
287 0           my $s = sprintf('%3d', $lineno);
  0            
288 0 0         $s = $s . ' ' if length($s) < 4;
  0 0          
289              
290             ## FIXME: move into DB::Breakpoint and adjust List.pm
291 0 0 0       if (exists($DB::dbline{$lineno}) and
  0 0 0        
292             my $brkpts = $DB::dbline{$lineno}) {
293 0           my $found = 0;
  0            
294 0           for my $bp (@{$brkpts}) {
  0            
  0            
  0            
295 0 0         if (defined($bp)) {
  0 0          
296 0           $a_pad = sprintf('%02d', $bp->id);
  0            
297 0           $s .= $bp->icon_char;
  0            
298 0           $found = 1;
  0            
299 0           last;
  0            
300             }
301             }
302 0 0         $s .= ' ' unless $found;
  0 0          
303             } else {
304 0           $s .= ' ';
  0            
305             }
306             ## FIXME move above code
307 0           my $opts = {unlimited => 1};
  0            
308 0           my $mess;
  0            
309 0 0 0       if ($proc->{frame} && $lineno == $proc->line &&
  0 0 0        
      0        
      0        
310             $frame_filename eq $filename) {
311 0           $s .= '->';
  0            
312 0           $s = $proc->bolden($s);
  0            
313             } else {
314 0           $s .= $a_pad;
  0            
315             }
316 0           $mess = "$s\t$line";
  0            
317 0           $proc->msg($mess, $opts);
  0            
318             }
319 0           $proc->{list_line} = $lineno + $center_correction;
  0            
320 0           $proc->{list_filename} = $filename;
  0            
321             # rescue => e
322             # errmsg e.to_s if settings[:debugexcept]
323             # end
324             }
325              
326             unless (caller) {
327             require Devel::Trepan::CmdProcessor::Mock;
328             my $proc = Devel::Trepan::CmdProcessor->new(undef, 'bogus');
329             my $cmd = __PACKAGE__->new($proc);
330             require Devel::Trepan::DB::Sub;
331             require Devel::Trepan::DB::LineCache;
332             cache_file(__FILE__);
333             my $frame_ary = Devel::Trepan::CmdProcessor::Mock::create_frame();
334             $proc->frame_setup($frame_ary);
335             $proc->{settings}{highlight} = undef;
336             $cmd->run([$NAME]);
337             print '-' x 20, "\n";
338             $cmd->run([$NAME]);
339             print '-' x 20, "\n";
340             $cmd->run(["{$NAME}>", __FILE__, __LINE__]);
341             print '-' x 20, "\n";
342             $cmd->run(["{$NAME}>", __FILE__, 1]);
343             }
344              
345             1;