#!perl -w # # PTKSH 2.0 # # A graphical user interface for testing Perl/Tk commands and scripts. # # VERSION HISTORY: # ...truncated earlier stuff... # 4/23/98 V1.7 Achim Bohnet -- some fixes to "o" command # 6/08/98 V2.01 M. Beller -- merge in GUI code for "wish"-like interface # # 2.01d1 6/6/98 First development version # # 2.01d2 6/7/98 # - apply A.B. patch for pod and -option # - fix "use of uninitialized variable" in END{ } block (for -c option) # - support h and ? only for help # - misc. pod fixes (PITFALLS) # - use default fonts and default colors ## NOT YET--still working on it # - get rid of Data::Dumper for history # # 2.01d3 6/8/98 # - Remove "use Data::Dumper" line # - Put in hack for unix vs. win32 window manager focus problem # - Achim's pod and histfile patch # # 2.01d4 6/18/98 # - Slaven's patch to make work properly # - Add help message to banner (per Steve Lydie) # - Fix horizontal scrolling (turn off wrapping in console window) # - Clarify in docs and help means "up arrow" # - Use HOMEDRIVE/HOMEPATH on Win32 # # For more history look at the various Changes files in the Perl/Tk # distribution. =head1 NAME ptksh - Perl/Tk script to provide a graphical user interface for testing Perl/Tk commands and scripts. =head1 SYNOPSIS % ptksh ?scriptfile? ... version information ... ptksh> $b=$mw->Button(-text=>'Hi',-command=>sub{print 'Hi'}) ptksh> $b->pack ptksh> o $b ... list of options ... ptksh> help ... help information ... ptksh> exit % =head1 DESCRIPTION ptksh is a perl/Tk shell to enter perl commands interactively. When one starts ptksh a L is automaticly created, along with a ptksh command window. One can access the main window by typing commands using the variable $mw at the 'ptksh> ' prompt of the command window. ptksh supports command line editing and history. Just type "" at the command prompt to see a history list. The last 50 commands entered are saved, then reloaded into history list the next time you start ptksh. ptksh supports some convenient commands for inspecting Tk widgets. See below. To exit ptksh use: C. ptksh is B<*not*> a full symbolic debugger. To debug perl/Tk programs at a low level use the more powerful L. (Just enter ``O tk'' on debuggers command line to start the Tk eventloop.) =head1 FEATURES =head2 History Press (the Up Arrow) in the perlwish window to obtain a gui-based history list. Press on any history line to enter it into the perlwish window. Then hit return. So, for example, repeat last command is . You can quit the history window with . NOTE: history is only saved if exit is "graceful" (i.e. by the "exit" command from the console or by quitting all main windows--NOT by interrupt). =head2 Debugging Support ptksh provides some convenience function to make browsing in perl/Tk widget easier: =over 4 =item B, or B displays a short help summary. =item B, or B ?I, ...? Dumps recursively arguments to stdout. (see L). You must have installed to support this feature. B was introduced for perl debugger compatibility. =item B

?I, ...? appends "|\n" to each of it's arguments and prints it. If value is B, '(undef)' is printed to stdout. =item B I<$widget> ?I<-option> ...? prints the option(s) of I<$widget> one on each line. If no options are given all options of the widget are listed. See L for more details on the format and contents of the returned list. =item B I<$widget> BIB Lists options of I<$widget> matching the L I. =item B ?I? If no argument is given it lists the modules loaded by the commands you executed or since the last time you called C. If argument is the empty string lists all modules that are loaded by ptksh. If argument is a string, ``text'' it tries to do a ``use Tk::Text;''. =back =head2 Packages Ptksh compiles into package Tk::ptksh. Your code is eval'ed into package main. The coolness of this is that your eval code should not interfere with ptksh itself. =head2 Multiline Commands ptksh will accept multiline commands. Simply put a "\" character immediately before the newline, and ptksh will continue your command onto the next line. =head2 Source File Support If you have a perl/Tk script that you want to do debugging on, try running the command ptksh> do 'myscript'; -- or (at shell command prompt) -- % ptksh myscript Then use the perl/Tk commands to try out different operations on your script. =head1 ENVIRONMENT Looks for your .ptksh_history in the directory specified by the $HOME environment variable ($HOMEPATH on Win32 systems). =head1 FILES =over 4 =item F<.ptksh_init> If found in current directory it is read in an evaluated after the mainwindow I<$mw> is created. F<.ptksh_init> can contain any valid perl code. =item F<~/.ptksh_history> Contains the last 50 lines entered in ptksh session(s). =back =head1 PITFALLS It is best not to use "my" in the commands you type into ptksh. For example "my $v" will make $v local just to the command or commands entered until is pressed. For a related reason, there are no file-scopy "my" variables in the ptksh code itself (else the user might trounce on them by accident). =head1 BUGS B function interactively entered or sourced in a init or script file will block ptksh. =head1 SEE ALSO L L =head1 VERSION VERSION 2.03 =head1 AUTHORS Mike Beller , Achim Bohnet Copyright (c) 1996 - 1998 Achim Bohnet and Mike Beller. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut package Tk::ptksh; require 5.004; use strict; use Tk; ##### Constants use vars qw($NAME $VERSION $FONT @FONT $WIN32 $HOME $HISTFILE $HISTSAVE $PROMPT $INITFILE); $NAME = 'ptksh'; $VERSION = '2.03'; $WIN32 = 1 if $^O =~ /Win32/; $HOME = $WIN32 ? ($ENV{HOMEDRIVE} . $ENV{HOMEPATH}) || 'C:\\' : $ENV{HOME} . "/"; @FONT = ($WIN32 ? (-font => 'systemfixed') : () ); #@FONT = ($WIN32 ? (-font => ['courier', 9, 'normal']) : () ); $HISTFILE = "${HOME}.${NAME}_history"; $HISTSAVE = 50; $INITFILE = ".${NAME}_init"; $PROMPT = "$NAME> "; sub Win32Fix { my $p = shift; $p =~ s'\\'/'g; $p =~ s'/$''; return $p } use vars qw($mw $st $t @hist $hist $list $isStartOfCommand); # NOTE: mainwindow creation order seems to impact who gets focus, and # order is different on Win32 & *nix!! So hack is to create the windows # in an order dependent on the OS! $mw = Tk::MainWindow->new unless $WIN32; # &&& hack to work around focus problem ##### set up user's main window package main; $main::mw = Tk::MainWindow->new; $main::mw->title('$mw'); $main::mw->geometry("+1+1"); package Tk::ptksh; ##### Set up ptksh windows $mw = Tk::MainWindow->new if $WIN32; # &&& hack to work around focus problem $mw->title($NAME); $st = $mw->Scrolled('Text', -scrollbars => 'osoe', -wrap => 'none', -width => 80, -height => 25, @FONT); $t = $st->Subwidget('scrolled'); $st->pack(-fill => 'both', -expand => 'true'); # $mw_mapped assures that Center is only called exactly twice: first time # will determine geometry of window, second time centering will work. # I observed a couple of further events, which are now ignored # and thus window creation seems to be faster now. my $mw_mapped; $mw->bind('', sub {return if $mw_mapped && $mw_mapped>=2; Center($mw); $mw_mapped++;} ); # Event bindings $t->bindtags([$t, ref($t), $t->toplevel, 'all']); # take first crack at events $t->bind('', \&EvalInput); $t->bind('', \&BackSpace); $t->bind('', \&HistKill); $t->bind('', \&History); $t->bind('', \&BeginLine); $t->bind('', \&BeginLine); $t->bind('', [\&Key, Tk::Ev('K'), Tk::Ev('A')]); my $default_font = $t->cget(-font); my %default_font = $t->fontActual($default_font); my $normal_font; if (!$t->fontMetrics($default_font, '-fixed')) { $normal_font = $t->fontCreate(%default_font, -family => "courier"); $t->configure(-font => $normal_font); } else { $normal_font = $default_font; } my %normal_font = $t->fontActual($normal_font); my $bold_font = $t->fontCreate(%normal_font, -weight => "bold"); # Set up different colors for the various window outputs #$t->tagConfigure('prompt', -underline => 'true'); $t->tagConfigure('prompt', -foreground => 'blue', -font => $bold_font); $t->tagConfigure('result', -foreground => 'purple'); $t->tagConfigure('error', -foreground => 'red'); $t->tagConfigure('output', -foreground => 'blue'); # The tag 'limit' is the beginning of the input command line $t->markSet('limit', 'insert'); $t->markGravity('limit', 'left'); # redirect stdout #tie (*STDOUT, 'Tk::Text', $t); tie (*STDOUT, 'Tk::ptksh'); #tie (*STDERR, 'Tk::ptksh'); # Print banner print "$NAME V$VERSION"; print " perl V$] Tk V$Tk::VERSION MainWindow -> \$mw\n"; print "\n\t\@INC:\n"; foreach (@INC) { print "\t $_\n" }; print "Type 'h' at the prompt for help\n"; ##### Read .ptkshinit if ( -r $INITFILE) { print "Reading $INITFILE ...\n"; package main; do $Tk::ptksh::INITFILE; package Tk::ptksh; } ###### Source the file if given as argument 0 if (defined($ARGV[0]) && -r $ARGV[0]) { print "Reading $ARGV[0] ...\n"; package main; do $ARGV[0]; package Tk::ptksh; } ##### Read history @hist = (); if ( -r $HISTFILE and open(HIST, $HISTFILE) ) { print "Reading history ...\n"; my $c = ""; while () { chomp; $c .= $_; if ($_ !~ /\\$/) { #end of command if no trailing "\" push @hist, $c; $c = ""; } else { chop $c; # kill trailing "\" $c .= "\n"; } } close HIST; } ##### Initial prompt Prompt($PROMPT); $Tk::ptksh::mw->focus; $t->focus; #$mw->after(1000, sub {print STDERR "now\n"; $mw->focus; $t->focus;}); ##### Now enter main loop #$mw->afterIdle(sub {Center($mw);}); MainLoop(); ####### Callbacks/etc. # EvalInput -- Eval the input area (between 'limit' and 'insert') # in package main; use vars qw($command $result); # use globals instead of "my" to avoid conflict w/ 'eval' sub EvalInput { # If return is hit when not inside the command entry range, reprompt if ($t->compare('insert', '<=', 'limit')) { $t->markSet('insert', 'end'); Prompt($PROMPT); Tk->break; } # Support multi-line commands if ($t->get('insert-1c', 'insert') eq "\\") { $t->insert('insert', "\n"); $t->insert('insert', "> ", 'prompt'); # must use this pattern for continue $t->see('insert'); Tk->break; } # Get the command and strip out continuations $command = $t->get('limit','end'); $t->markSet('insert','end'); $command =~ s/\\\n>\s/\n/mg; # Eval it if ( $command !~ /^\s*$/) { chomp $command; push(@hist, $command) unless @hist && ($command eq $hist[$#hist]); #could elim more redundancy $t->insert('insert', "\n"); $isStartOfCommand = 1; $command = PtkshCommand($command); exit if ($command eq 'exit'); package main; no strict; $Tk::ptksh::result = eval "local \$^W=0; $Tk::ptksh::command;"; use strict; package Tk::ptksh; if ($t->compare('insert', '!=', 'insert linestart')) { $t->insert('insert', "\n"); } if ($@) { $t->insert('insert', '## ' . $@, 'error'); } else { $result = "" if !defined($result); $t->insert('insert', '# ' . $result, 'result'); } } Prompt($PROMPT); Tk->break; } sub Prompt { my $pr = shift; if ($t->compare('insert', '!=', 'insert linestart')) { $t->insert('insert', "\n"); } $t->insert('insert', $pr, 'prompt'); $t->see('insert'); $t->markSet('limit', 'insert'); } sub BackSpace { if ($t->tagNextrange('sel', '1.0', 'end')) { $t->delete('sel.first', 'sel.last'); } elsif ($t->compare('insert', '>', 'limit')) { $t->delete('insert-1c'); $t->see('insert'); } Tk->break; } sub BeginLine { $t->SetCursor('limit'); $t->break; } sub Key { my ($self, $k, $a) = @_; #print "key event: ", $k, "\n"; if ($t->compare('insert', '<', 'limit')) { $t->markSet('insert', 'end'); } #$t->break; #for testing bindtags } sub History { Tk->break if defined($hist); $hist = $mw->Toplevel; $hist->title('History'); $list = $hist->ScrlListbox(-scrollbars => 'oe', -width => 30, -height => 10, @FONT)->pack(qw(-fill both -expand 1)); Center($hist); $list->insert('end', @hist); $list->see('end'); $list->activate('end'); $hist->bind('', \&HistPick); $hist->bind('', \&HistPick); $hist->bind('', \&HistKill); my $hist_mapped; # see above for $mw_mapped $hist->bind('', sub {return if $hist_mapped && $hist_mapped>=2; Center($hist); $hist_mapped++;} ); $hist->bind('', \&HistDestroy); $hist->focus; $list->focus; $hist->grab; #$mw->afterIdle(sub {Center($hist);}); Tk->break; } sub HistPick { my $item = $list->get('active'); return if (!$item); $t->markSet('insert', 'end'); $t->insert('insert',$item); $t->see('insert'); $mw->focus; $t->focus; HistKill(); } sub HistKill { if ($hist) { $hist->grabRelease; $hist->destroy; } } # Called from destroy event mapping sub HistDestroy { if (defined($hist) && (shift == $hist)) { $hist = undef; $mw->focus; $t->focus; } } sub LastCommand { if ($t->compare('insert', '==', 'limit')) { $t->insert('insert', $hist[$#hist]); $t->break; } } # Center a toplevel on screen or above parent sub Center { my $w = shift; my ($x, $y); if ($w->parent) { #print STDERR $w->screenwidth, " ", $w->width, "\n"; $x = $w->parent->x + ($w->parent->width - $w->width)/2; $y = $w->parent->y + ($w->parent->height - $w->height)/2; } else { #print STDERR $w->screenwidth, " ", $w->width, "\n"; $x = ($w->screenwidth - $w->width)/2; $y = ($w->screenheight - $w->height)/2; } $x = int($x); $y = int($y); my $g = "+$x+$y"; #print STDERR "Setting geometry to $g\n"; $w->geometry($g); } # To deal with "TIE". # We have to make sure the prints don't go into the command entry range. sub TIEHANDLE { # just to capture the tied calls my $self = []; return bless $self; } sub PRINT { my ($bogus) = shift; $t->markSet('insert', 'end'); if ($isStartOfCommand) { # Then no prints have happened in this command yet so... if ($t->compare('insert', '!=', 'insert linestart')) { $t->insert('insert', "\n"); } # set flag so we know at least one print happened in this eval $isStartOfCommand = 0; } while (@_) { $t->insert('end', shift, 'output'); } $t->see('insert'); $t->markSet('limit', 'insert'); # don't interpret print as an input command } sub PRINTF { my $w = shift; $w->PRINT(sprintf(shift,@_)); } ### ### Utility function ### sub _o { my $w = shift; my $what = shift; $what =~ s/^\s+//; $what =~ s/\s+$//; my (@opt) = split " ", $what; print 'o(', join('|', @opt), ")\n"; require Tk::Pretty; # check for regexp if ($opt[0] =~ s|^/(.*)/$|$1|) { print "options matching /$opt[0]/:\n"; foreach ($w->configure()) { print Tk::Pretty::Pretty($_),"\n" if $_->[0] =~ /\Q$opt[0]\E/; } return; } # list of options (allow as bar words) foreach (@opt) { s/^['"]//; s/,$//; s/['"]$//; s/^([^-])/-$1/; } if (length $what) { foreach (@opt) { print Tk::Pretty::Pretty($w->configure($_)),"\n"; } } else { foreach ($w->configure()) { print Tk::Pretty::Pretty($_),"\n" } } } sub _p { foreach (@_) { print $_, "|\n"; } } use vars qw($u_init %u_last $u_cnt); $u_init = 0; %u_last = (); sub _u { my $module = shift; if (defined($module) and $module ne '') { $module = "Tk/".ucfirst($module).".pm" unless $module =~ /^Tk/; print " --- Loading $module ---\n"; require "$module"; print $@ if $@; } else { %u_last = () if defined $module; $u_cnt = 0; foreach (sort keys %INC) { next if exists $u_last{$_}; $u_cnt++; $u_last{$_} = 1; #next if m,^/, and m,\.ix$,; # Ignore autoloader files #next if m,\.ix$,; # Ignore autoloader files if (length($_) < 20 ) { printf "%-20s -> %s\n", $_, $INC{$_}; } else { print "$_ -> $INC{$_}\n"; } } print STDERR "No modules loaded since last 'u' command (or startup)\n" unless $u_cnt; } } sub _d { require Data::Dumper; local $Data::Dumper::Deparse; $Data::Dumper::Deparse = 1; print Data::Dumper::Dumper(@_); } sub _h { print <<'EOT'; ? or h print this message d or x arg,... calls Data::Dumper::Dumper p arg,... print args, each on a line and "|\n" o $w /regexp/ print options of widget matching regexp o $w [opt ...] print (all) options of widget u xxx xxx = string : load Tk::Xxx = '' : list all modules loaded = undef : list modules loaded since last u call (or after ptksh startup) Press (the "up arrow" key) for command history Press to leave command history window Type "exit" to quit (saves history) Type \ for continuation of command to following line EOT } # Substitute our special commands into the command line sub PtkshCommand { $_ = shift; foreach ($_) { last if s/^\?\s*$/Tk::ptksh::_h /; last if s/^h\s*$/Tk::ptksh::_h /; last if s/^u(\s+|$)/Tk::ptksh::_u /; last if s/^[dx]\s+/Tk::ptksh::_d /; last if s/^u\s+(\S+)/Tk::ptksh::_u('$1')/; last if s/^p\s+(.*)$/Tk::ptksh::_p $1;/; last if s/^o\s+(\S+)\s*?$/Tk::ptksh::_o $1;/; last if s/^o\s+(\S+)\s*,?\s+(.*)?$/Tk::ptksh::_o $1, '$2';/; } %u_last = %INC unless $u_init++; # print STDERR "Command is: $_\n"; $_; } ### ### Save History -- use Data::Dumper to preserve multiline commands ### END { if ($HISTFILE) { # because this is probably perl -c if $HISTFILE is not set $#hist-- if $hist[-1] =~ /^(q$|x$|\s*exit\b)/; # chop off the exit command @hist = @hist[($#hist-$HISTSAVE)..($#hist)] if $#hist > $HISTSAVE; if( open HIST, ">$HISTFILE" ) { while ($_ = shift(@hist)) { s/\n/\\\n/mg; print HIST "$_\n"; } close HIST; } else { print STDERR "Error: Unable to open history file '$HISTFILE'\n"; } } } 1; # just in case we decide to be "use"'able in the future.