#! /usr/bin/perl # podwatch FILE # # adrianh@quietstars.com # 20020520 # # Copyright 2002-2003 Adrian Howard, All Rights Reserved. # # This program is free software; you can redistribute it # and/or modify it under the same terms as Perl itself. # # 20020522 # - now moves changed line to the top on refresh # - refactored move_by out from command subs # - added help # 20030117 # - tided up formatting a bit # - added beep # 20030118 # - added ReadMode 0 on quit (thanks to castaway) use strict; use warnings; use Term::ReadKey; use Pod::Text; use IO::String; $|=1; ReadMode 3; my $Filename = $ARGV[-1]; my $Help = <<'END_HELP'; =================================== SUMMARY OF KEYS return - forward one line space - forward one page b - back one page g - go to first line in file G - go to last line in file / - search r - refresh page q - quit =================================== END_HELP my $Status_line = "(%5d) $Filename - h:elp q:uit"; my $Line_num = 0; my $Modification_time = -1; my $Page_length = -1; my $Column_num = -1; my @Pod = (); my $Search = ''; my %Command = ( "undef" => \&beep, "\n" => \&next_line, " " => \&next_page, "b" => \&prev_page, "g" => \&end, "G" => \&top, "/" => \&search, "r" => \&refresh, "h" => \&help, "q" => \&quit, ); while (1) { refresh() if changed(); my $key = ReadKey 0.25; next unless $key; &{$Command{$key} || $Command{'undef'}}; }; ########### sub beep { print "\a" }; sub next_line { move_by(+1) }; sub next_page { move_by(+$Page_length) }; sub prev_page { move_by(-$Page_length) }; sub top { move_by(-$Line_num) }; sub end { move_by($#Pod) }; sub help { clear_status(); print $Help; print status_line(); }; sub search { clear_status(); print "/"; ReadMode 0; my $input = ; ReadMode 3; chomp($input); $Search = $input eq '' ? $Search : $input; my $final_line_num = $Line_num; foreach my $n ($Line_num+1 .. $#Pod) { if (eval {$Pod[$n] =~ /$Search/}) { $final_line_num = $n; last; }; }; beep() if $Line_num == $final_line_num; inc_line($final_line_num-$Line_num); show_page(); }; sub quit { print "\n"; ReadMode 0; exit(0); }; ########### sub clear_status { print "\r", " " x length(status_line()), "\r"; }; sub status_line { return(substr(sprintf($Status_line, $Line_num+1), 0, $Column_num)); }; sub last_line { return($Line_num + $Page_length - 1); }; sub print_line { my $line_num = shift; my $line = $Pod[$line_num]; $line = "" unless defined($line); print "\r$line"; my $status_line = status_line(); my $overlap = length($status_line) - length($line); print " " x $overlap if $overlap > 0; print "\n", $status_line; }; sub inc_line($) { my $n = shift; my $start_line = $Line_num; $Line_num += $n; if ($Line_num < 0) { $Line_num = 0 } elsif ($Line_num > $#Pod){ $Line_num = $#Pod; }; return($start_line != $Line_num); } sub show_page { print_line($_) foreach ($Line_num..last_line()); }; sub move_by { my $n = shift; if ($n < 0 || $n >= $Page_length) { show_page() if inc_line($n); } else { foreach (1..$n) { return unless inc_line(1); print_line( last_line() ) }; }; }; ########### sub modification_time { my $filename = shift; return( (stat $filename)[9] || die "$filename ($!)\n" ); }; sub page_size { my ($cols, $rows) = GetTerminalSize *STDOUT{IO}; die "cannot determine terminal size" unless $cols && $rows; return($cols, $rows-1); }; sub changed { my ($cols, $rows) = page_size(); return( $Modification_time != modification_time($Filename) || $Page_length != $rows || $Column_num != $cols ); }; sub fetch_pod { my $filename = shift; my $pod = IO::String->new; open(POD, $filename) or die "$Filename ($!)\n"; Pod::Text->new( width => ($Column_num > 76 ? 76 : $Column_num) )->parse_from_filehandle(*POD{IO}, $pod); close(POD); return(${$pod->string_ref}); }; sub refresh { ($Column_num, $Page_length) = page_size(); $Modification_time = modification_time($Filename); my @old_pod = @Pod; @Pod = split(/\n/, fetch_pod($Filename)); LINE: foreach my $n (0..$#Pod) { my ($line, $old_line) = ($Pod[$n], $old_pod[$n]); unless (defined($old_line) && $line eq $old_line) { $Line_num = $n if ($Line_num > $n || $n > last_line()); last LINE; }; }; inc_line(0); show_page(); beep(); };