File Coverage

blib/lib/Term/Pager.pm
Criterion Covered Total %
statement 6 227 2.6
branch 0 52 0.0
condition 0 25 0.0
subroutine 2 30 6.6
pod 0 28 0.0
total 8 362 2.2


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             # Copyright (c) 2004 by Jeff Weisberg
4             # Author: Jeff Weisberg
5             # Created: 2004-Jun-03 10:24 (EDT)
6             # Function: pager like more/less
7             #
8             # $Id: Pager.pm,v 1.4 2012/12/02 18:06:46 jaw Exp $
9              
10             =head1 NAME
11              
12             Term::Pager - Page through text, a screenful at a time, like more or less
13              
14             =head1 SYNOPSIS
15              
16             use Term::Pager;
17              
18             my $t = Term::Pager->new( rows => 25, cols => 80 );
19             $t->add_text( $text );
20             $t->more();
21              
22             =head1 DESCRIPTION
23              
24             This is a module for paging through text one screenful at a time.
25             It supports the features you expect, including backwards
26             movement and searching. It uses the keys you expect.
27              
28             =head1 USAGE
29              
30             =head2 Create the Pager
31              
32             $t = Term::Pager->new( option => value, ... );
33              
34             If no options are specified, sensible default values will be used.
35             The following options are recognized:
36              
37             =over 4
38              
39             =item C
40              
41             The number of rows on your terminal.
42             This defaults to 25.
43              
44             =item C
45              
46             The number of columns on your terminal.
47             This defaults to 80.
48              
49             =item C
50              
51             The speed (baud rate) of your terminal. Will default
52             to a sensible value.
53              
54             =back
55              
56             =head2 Adding Text
57              
58             You will need some text to page through. You can specify text as
59             as a parameter to the constructor:
60              
61             text => $text
62              
63             Or add text later:
64              
65             $t->add_text( $text );
66              
67             =cut
68             ;
69              
70             package Term::Pager;
71             $VERSION = '1.02';
72              
73 1     1   1407 use Term::Cap;
  1         2804  
  1         26  
74 1     1   6 use strict;
  1         1  
  1         2313  
75              
76             sub new {
77 0     0 0   my $class = shift;
78 0           my %param = @_;
79              
80 0   0       my $t = Term::Cap->Tgetent({ OSPEED => ($param{speed} || 38400) });
81 0           my $dumbp;
82              
83 0           eval{
84 0           $t->Trequire(qw/cm ce cl sf sr/);
85             };
86 0 0         $dumbp = 1 if $@;
87              
88 0           my $me = bless {
89             # default values
90             term => $t,
91             cols => 80,
92             rows => 25,
93             dumbp => $dumbp,
94              
95             # if the termcap entries don't exist, nothing bad will happen
96             HI => $t->Tputs('md') . $t->Tputs('us'), # search hilight
97             SE => $t->Tputs('md') . $t->Tputs('us'), # search entry
98             MN => $t->Tputs('md') . $t->Tputs('mr'), # popup menus
99             ML => $t->Tputs('mr'), # mode line
100             NO => $t->Tputs('me'), # normal
101              
102             # user supplied values override
103             %param,
104             }, $class;
105              
106 0           $me->{fnc} = {
107             "\n"=> \&downline,
108             ' ' => \&downpage,
109             'd' => \&downhalf,
110             'q' => \&done,
111             'b' => \&uppage,
112             'y' => \&upline,
113             'u' => \&uphalf,
114             'r' => \&refresh,
115             'h' => \&help,
116             '?' => \&help,
117             '0' => \&to_top,
118             'g' => \&to_bott,
119             '$' => \&to_bott, # '
120             '/' => \&search,
121             '<' => \&move_left,
122             '>' => \&move_right,
123             };
124              
125 0           $me;
126             }
127              
128             sub add_text {
129 0     0 0   my $me = shift;
130 0           my $tx = shift;
131              
132 0           $me->{text} .= $tx;
133             }
134              
135             sub add_func {
136 0     0 0   my $me = shift;
137 0           my $fn = shift;
138 0           my $fc = shift;
139              
140 0           $me->{fnc}{$fn} = $fc;
141             }
142              
143             sub more {
144 0     0 0   my $me = shift;
145 0           my $sp = $|;
146 0           my $t = $me->{term};
147              
148 0           $me->{L} = $me->{rows} - 1;
149 0           $me->{l} = [ split /\n/, $me->{text} ];
150 0           $me->{nl}= @{ $me->{l} };
  0            
151              
152 0           $me->{start} = 0;
153 0           $me->{end} = $me->{L} - 1;
154              
155 0           $SIG{INT} = $SIG{QUIT} = \&done;
156 0           system('stty -icanon -echo min 1');
157 0           $| = 1;
158              
159 0           eval {
160 0 0         if( $me->{dumbp} ){
161 0           $me->dumb_mode();
162             }else{
163 0           print $me->{NO};
164 0           $me->refresh();
165              
166 0           while(1){
167 0           print $t->Tgoto('cm', 0, $me->{L}); # bottom left
168 0           print $t->Tputs('ce'); # clear line
169              
170 0           print $me->{ML}; # reverse video
171 0           $me->prompt();
172 0           print $me->{NO}; # normal video
173              
174 0           my $q = getc();
175              
176 0           print $t->Tgoto('cm', 0, $me->{L}); # bottom left
177 0           print $t->Tputs('ce'); # clear line
178              
179 0           $me->{msg} = '';
180 0   0       my $f = $me->{fnc}->{lc($q)} || \&beep;
181 0           $f->($me);
182             }
183             }
184             };
185              
186 0           system('stty icanon echo');
187 0           $| = $sp;
188              
189 0 0 0       if( $@ && !ref $@ ){
190 0           die $@;
191             }
192 0           return;
193             }
194              
195             *less = \&more;
196             *page = \&more;
197              
198 0     0 0   sub beep { print "\a" }
199              
200             # display a prompt, etc
201             sub prompt {
202 0     0 0   my $me = shift;
203              
204 0 0         my $pct = ($me->{nl} > 1) ? 100*$me->{end}/($me->{nl}-1) : 100;
205 0 0         my $p = sprintf "[more] %d%% %s %s", $pct,
    0          
206             ($me->{start} ? ($me->{end}==$me->{nl}-1) ? 'Bottom' : '' : 'Top'), $me->{msg};
207              
208 0           my $p2 = " =down =back =help =quit";
209              
210 0           $p .= ' ' x ($me->{cols} - 2 - length($p) - length($p2));
211              
212 0           print $p,$p2;
213             }
214              
215             sub done {
216 0     0 0   die \ 'foo';
217             }
218              
219             # put a box around some text
220             sub box_text {
221 0     0 0   my $me = shift;
222 0           my $txt = shift;
223 0           my $l;
224              
225 0           my @l = split /\n/, $txt;
226 0 0         foreach (@l){ $l = length($_) if length($_) > $l };
  0            
227 0           my $b = '+' . '=' x ($l + 2) . '+';
228 0           my $o = join('', map { "| $_" . (' 'x($l-length($_))) ." |\n" } @l);
  0            
229              
230 0           "$b\n$o$b\n";
231             }
232              
233             # provide help to user
234             sub help {
235 0     0 0   my $me = shift;
236              
237 0           my $help = $me->box_text(<
238             q quit h help
239             / search
240             space page down b page up
241             enter line down y line up
242             d half page down u half page up
243             0 goto top g goto bottom
244             < scroll left > scroll right
245              
246             press any key to continue
247             EOH
248             ;
249              
250 0           $me->disp_menu( $help );
251 0           getc();
252 0           $me->remove_menu();
253              
254             }
255              
256             # display a popup menu (or other text)
257             sub disp_menu {
258 0     0 0   my $me = shift;
259 0           my $menu = shift;
260 0           my $t = $me->{term};
261              
262 0           my $nl = @{[split /\n/, $menu]};
  0            
263 0           $me->{menu_nl} = $nl;
264              
265 0           print $t->Tgoto('cm', 0, $me->{L} - $nl); # move
266 0           print $me->{MN}; # set color
267              
268 0           my $x = $t->Tgoto('RI', 0,4); # 4 transparent spaces
269 0           $menu =~ s/^\s*/$x/gm;
270 0           print $menu;
271 0           print $me->{NO}; # normal color
272              
273             }
274              
275             # remove popup and repaint
276             sub remove_menu {
277 0     0 0   my $me = shift;
278 0           my $t = $me->{term};
279              
280 0           my $s = $me->{end} - $me->{menu_nl} + 1;
281 0           foreach my $n ($s .. $me->{end}){
282 0           print $t->Tgoto('cm', 0, $n - $me->{start}); # move
283 0           print $t->Tputs('ce'); # clear
284 0           $me->line($n);
285             }
286             }
287              
288             # refresh screen
289             sub refresh {
290 0     0 0   my $me = shift;
291 0           my $t = $me->{term};
292              
293 0           print $t->Tputs('cl'); # home, clear
294 0           for my $n ($me->{start} .. $me->{end}){
295 0           print $t->Tgoto('cm', 0, $n - $me->{start}); # move
296 0           print $t->Tputs('ce'); # clear line
297 0           $me->line($n);
298             }
299             }
300              
301             sub prline {
302 0     0 0   my $me = shift;
303 0           my $line = shift;
304              
305 0           my $len = length($line);
306 0           $line = substr($line, $me->{left}, $me->{cols});
307 0 0         if( $len - $me->{left} > $me->{cols} ){
308 0           substr($line, -1, 1, "\$");
309             }
310              
311 0 0         if( $me->{search} ne '' ){
312 0           my $s = $me->{HI};
313 0           my $e = $me->{NO};
314 0           $line =~ s/($me->{search})/$s$1$e/g;
315             }
316 0           print $line;
317              
318             }
319              
320             sub line {
321 0     0 0   my $me = shift;
322 0           my $n = shift;
323              
324 0           $me->prline( $me->{l}[$n] );
325             }
326              
327             sub down_lines {
328 0     0 0   my $me = shift;
329 0           my $n = shift;
330 0           my $t = $me->{term};
331              
332 0           for (1 .. $n){
333 0 0         if( $me->{end} >= $me->{nl}-1 ){
334 0           print "\a";
335 0           last;
336             }else{
337             # why? because some terminals have bugs...
338 0           print $t->Tgoto('cm', 0, $me->{L} ); # move
339 0           print $t->Tputs('sf'); # scroll
340 0           print $t->Tgoto('cm', 0, $me->{L} - 1); # move
341 0           print $t->Tputs('ce'); # clear line
342              
343 0           $me->line( ++$me->{end} );
344 0           $me->{start} ++;
345             }
346             }
347             }
348              
349             sub downhalf {
350 0     0 0   my $me = shift;
351 0           $me->down_lines( $me->{L} / 2 );
352             }
353              
354             sub downpage {
355 0     0 0   my $me = shift;
356 0           $me->down_lines( $me->{L} );
357             }
358              
359             sub downline {
360 0     0 0   my $me = shift;
361 0           $me->down_lines( 1 );
362             }
363              
364             sub up_lines {
365 0     0 0   my $me = shift;
366 0           my $n = shift;
367 0           my $t = $me->{term};
368              
369 0           for (1 .. $n){
370 0 0         if( $me->{start} <= 0 ){
371 0           print "\a";
372 0           last;
373             }else{
374 0           print $t->Tgoto('cm',0,0); # move
375 0           print $t->Tputs('sr'); # scroll back
376 0           $me->line( --$me->{start} );
377 0           $me->{end} --;
378             }
379             }
380              
381 0           print $t->Tgoto('cm',0,$me->{L}); # goto bottom
382             }
383              
384             sub uppage {
385 0     0 0   my $me = shift;
386 0           $me->up_lines( $me->{L} );
387             }
388              
389             sub upline {
390 0     0 0   my $me = shift;
391 0           $me->up_lines( 1 );
392             }
393              
394             sub uphalf {
395 0     0 0   my $me = shift;
396 0           $me->up_lines( $me->{L} / 2 );
397             }
398              
399             sub to_top {
400 0     0 0   my $me = shift;
401              
402 0           $me->{start} = 0;
403 0           $me->{end} = $me->{L} - 1;
404 0           $me->refresh();
405             }
406              
407             sub to_bott {
408 0     0 0   my $me = shift;
409              
410 0           $me->{start} = $me->{nl} - $me->{L};
411 0 0         $me->{start} = 0 if $me->{start} < 0;
412 0           $me->{end} = $me->{start} + $me->{L} - 1;
413 0           $me->refresh();
414             }
415              
416             sub move_right {
417 0     0 0   my $me = shift;
418              
419 0           $me->{left} += 8;
420 0           $me->refresh();
421             }
422              
423             sub move_left {
424 0     0 0   my $me = shift;
425              
426 0           $me->{left} -= 8;
427 0 0         $me->{left} = 0 if $me->{left} < 0;
428 0           $me->refresh();
429             }
430              
431             sub search {
432 0     0 0   my $me = shift;
433 0           my $t = $me->{term};
434              
435             # get pattern
436 0           my $prev = $me->{search};
437 0           $me->{search} = '';
438              
439 0           print $t->Tgoto('cm', 0, $me->{L}); # move bottom
440 0           print $t->Tputs('ce'); # clear line
441 0           print $me->{SE}; # set color
442 0           print "/";
443              
444 0           while(1){
445 0           my $l = getc();
446 0 0 0       last if $l eq "\n" || $l eq "\r";
447 0 0 0       if( $l eq "\e" || !defined($l) ){
448 0           $me->{search} = '';
449 0           last;
450             }
451 0 0 0       if( $l eq "\b" || $l eq "\177" || $l eq '#' ){
      0        
452 0 0         print "\b \b" if $me->{search} ne '';
453 0           substr($me->{search}, -1, 1, '');
454 0           next;
455             }
456 0           print $l;
457 0           $me->{search} .= $l;
458             }
459 0           print $me->{NO}; # normal color
460 0           print $t->Tgoto('cm', 0, $me->{L}); # move bottom
461 0           print $t->Tputs('ce'); # clear line
462 0 0         return if $me->{search} eq '';
463              
464 0 0 0       $me->{search} = $prev if $me->{search} eq '/' && $prev;
465              
466 0           for my $n ( $me->{start} .. $me->{nl}-1 ){
467 0 0         next unless $me->{l}[$n] =~ /$me->{search}/;
468              
469 0           $me->{start} = $n;
470 0 0         $me->{start} = 0 if $me->{nl} < $me->{L} - 1;
471 0           $me->{end} = $me->{start} + $me->{L} - 1;
472              
473 0 0 0       if( $me->{end} > $me->{nl} - 1 && $me->{start} ){
474 0           my $x = $me->{end} - $me->{nl} + 1;
475 0 0         $x = $me->{start} if $x > $me->{start};
476 0           $me->{start} -= $x;
477 0           $me->{end} -= $x;
478             }
479              
480 0           $me->refresh();
481 0           return;
482             }
483             # not found
484 0           print "\a";
485 0           my $m = $me->box_text( 'Not Found' );
486 0           $me->disp_menu($m);
487 0           sleep 1;
488 0           $me->remove_menu();
489 0           return;
490              
491             }
492              
493              
494             sub dumb_mode {
495 0     0 0   my $me = shift;
496 0           my $end = 0;
497              
498 0           while(1){
499 0           for my $i (1 .. $me->{rows} - 1){
500 0 0         last if $end >= $me->{nl};
501 0           print $me->{l}[$end++], "\n";
502             }
503              
504 0           print "--more [dumb]--";
505 0           my $a = getc();
506 0           print "\b \b"x15;
507              
508 0 0         return if $a eq 'q';
509 0 0         return if $end >= $me->{nl};
510             }
511             }
512              
513              
514              
515             =head1 FEATURES
516              
517             This code uses termcap. If the termcap entry for your ancient esoteric
518             terminal is wrong or incomplete, this module may either fill your screen
519             with unintelligible gibberish, or drop back to a feature-free mode.
520              
521             =head1 SEE ALSO
522              
523             Term::Cap, termcap(5), more(1), less(1)
524             Yellowstone National Park
525              
526             =head1 AUTHOR
527              
528             Jeff Weisberg - http://www.tcp4me.com
529              
530             =cut
531             ;