App-Prima-REPL

 view release on metacpan or  search on metacpan

bin/prima-repl  view on Meta::CPAN

		require PDL::Graphics::Prima;
		PDL::Graphics::Prima->import;
		require PDL::Graphics::Prima::Simple;
		PDL::Graphics::Prima::Simple->import;
		$loaded_Prima_Graphics = 1;
	};
	print $@ if $@ and $@ !~ /^Can't locate/;
}

my $app_filename = File::Spec->catfile($FindBin::Bin, $FindBin::Script);
my $version = 0.3;

#########################
# Main Application Code #
#########################

package REPL;
my $history_output_handler = PrimaX::InputHistory::Output::REPL->new;

our @text_file_extension_list = (
		  ['Perl scripts'		=> '*.pl'	]
		, ['PDL modules'		=> '*.pdl'	]
		, ['Perl modules'		=> '*.pm'	]
		, ['POD documents'	=> '*.pod'		]
		, ['Test suite'		=> '*.t'		]
		, ['All'				=> '*'		]
);

# A dialog box that will be used for opening and saving files:
our $open_text_dialog = Prima::OpenDialog-> new(filter => \@text_file_extension_list);
our $open_dialog = Prima::OpenDialog->new(filter => [[All => '*']]);

# Very handy functions that I use throughout, but which I define later.
sub goto_page;
sub goto_output;
sub warn {
	chomp(my $text = join('', @_));
	warn $text . "\n";
	goto_output;
}

our $padding = 10;
our $window = Prima::MainWindow->new(
#	pack => { fill => 'both', expand => 1, padx => $padding, pady => $padding },
	text => 'Prima REPL',
	size => [600, 600], 
);
	# Add a notbook with output tab:
	our $notebook = $window->insert(TabbedScrollNotebook =>
		pack => { fill => 'both', expand => 1, padx => $padding, pady => $padding },
		tabs => ['Output'],
		style => tns::Simple,
	);
		our $output = $notebook->insert_to_page(0, Edit =>
			pack => { fill => 'both', expand => 1, padx => $padding, pady => $padding },
			text => '',
			cursorWrap => 1,
			wordWrap => 1,
			readOnly => 1,
			backColor => cl::LightGray,
			font => { name => 'courier new'},

		);
		# Over-ride the defaults for these:
		$output->accelTable->insert([
			  ['', '', km::Ctrl | kb::PageUp,	\&goto_prev_page	]	# previous
			, ['', '', km::Ctrl | kb::PageDown,	\&goto_next_page	]	# next
		], '', 0);

	# Add the eval line:
	our $inline = PrimaX::InputHistory->create(
		owner => $window,
		text => '',
		pack => {fill => 'both', after => $notebook, padx => $padding, pady => $padding},
		storeType => ih::NoRepeat,
		outputWidget => $history_output_handler,
		onCreate => sub {
			my $self = shift;
			
			# Open the file and set up the history:
			my @history;
			if (-f $fileName) {
				open my $fh, '<', $fileName;
				while (<$fh>) {
					chomp;
					push @history, $_;
				}
				close $fh;
			}
			
			# Store the history and revisions:
			$self->history(\@history);
		},
		onDestroy => sub {
			my $self = shift;
			
			# Save the last N lines in the history file:
			open my $fh, '>', $fileName;
			# I want to save the *last* 200 lines, so I don't necessarily start at
			# the first entry in the history:
			my $offset = 0;
			my @history = @{$self->history};
			$offset = @history - $historyLength if (@history > $historyLength);
			while ($offset < @history) {
				print $fh $history[$offset++], "\n";
			}
			close $fh;
		},
		onKeyUp => sub {
			main::my_keyup(@_);
		},
	);
# working here - a simple hack; override main::my_keyup to play with the
# keyup callback on the input line.
sub main::my_keyup {};
	# Add the special accelerators seperately:
	# Update the accelerators.
	my $accTable = $inline->accelTable;

	# Add some functions to the accelerator table
	$accTable->insert([

bin/prima-repl  view on Meta::CPAN

	push @default_widget_for, $inline;
	
	# Return the page widget and page number if they expect multiple return
	# values; or just the page widget.
	return ($page_widget, $page_no) if wantarray;
	return $page_widget if defined wantarray;
}

################################################################################
# Usage      : REPL::change_default_widget($index, $widget)
# Purpose    : changes the default widget for the tab with the given index
# Returns    : nothing
# Parameters : the tab's index (returned in list context from create_new_tab)
#            : the widget to get attention when CTRL-i is pressed
# Throws     : never
# Comments   : none
################################################################################
sub change_default_widget {
	my ($index, $widget) = @_;
	$default_widget_for[$index] = $widget;
}

################################################################################
# Usage      : REPL::get_default_widget($index)
# Purpose    : retrieves the default widget for the tab with the given index
# Returns    : the default widget
# Parameters : the tab's index (returned in list context from create_new_tab)
# Throws     : never
# Comments   : use this to modify the default widget's properties, if needed
################################################################################
sub get_default_widget {
	my ($index) = @_;
	return $default_widget_for[$index];
}

################################################################################
# Usage      : REPL::endow_editor_widget($widget)
# Purpose    : Sets the properties of an edit widget so it behaves like a
#            : multiline buffer.
# Returns    : nothing
# Parameters : the widget to endow
# Throws     : when you supply an object not derived from Prima::Edit
# Comments   : none
################################################################################
sub endow_editor_widget {
	my $widget = shift;
	
	# Verify the object
	croak("endow_editor_widget expects a Prima::Edit widget")
		unless eval{$widget->isa("Prima::Edit")};
	
	# Allow for insertions, deletions, newlines, etc
	$widget->set(
		tabIndent => 4,
		syntaxHilite => 1,
		wantTabs => 1,
		wantReturns => 1,
		wordWrap => 0,
		autoIndent => 1,
		cursorWrap => 1,
		font => { pitch => fp::Fixed, style => fs::Bold, name => 'courier new'},
	);

	# Update the accelerators.
	my $accTable = $widget->accelTable;

	# Add some functions to the accelerator table
	$accTable->insert([
		# Ctrl-Enter runs the file
		  ['CtrlReturn', '', kb::Return 	| km::Ctrl,  sub{main::run_file()}				]
		, ['CtrlEnter', '', kb::Enter  	| km::Ctrl,  sub{main::run_file()}				]
		# Ctrl-Shift-Enter runs the file and selects the output window
		, ['CtrlShiftReturn', '', kb::Return 	| km::Ctrl | km::Shift,	\&main::run_file_with_output	]
		, ['CtrlShiftEnter', '', kb::Enter  	| km::Ctrl | km::Shift,	\&main::run_file_with_output	]
		# Ctrl-PageUp/PageDown don't work by default, so add them, too:
		, ['CtrlPageUp', '', kb::PageUp 	| km::Ctrl,  \&REPL::goto_prev_page				]
		, ['CtrlPageDown', '', kb::PageDown | km::Ctrl,  \&REPL::goto_next_page				]
		]
		, ''
		, 0
	);
}

# closes the tab number, or name if provided, or current if none is supplied
# ENCOUNTERIMG TROUBLE WITH THIS, working here
sub close_tab {
	# Get the desired tab; default to current tab:
	my $to_close = shift || $notebook->pageIndex + 1;	# user counts from 1
	my @tabs = @{$notebook->tabs};
	if ($to_close =~ /^\d+$/) {
		$to_close--;	# correct user's offset by 1
		$to_close += $notebook->pageCount if $to_close < 0;
		# Check that a valid value is used:
		return REPL::warn("You cannot remove the output tab")
			if $to_close == 0;
		
		# Close the tab
		CORE::warn "Internal: Not checking if the file needs to be saved."
			if eval{$default_widget_for[$to_close]->isa('Prima::Edit')};
		splice @tabs, $to_close, 1;
		splice @default_widget_for, $to_close, 1;
		$notebook->Notebook->delete_page($to_close);
	}
	else {
		# Provided a name. Close all the tags with the given name:
		my $i = 1;	# Start at tab #2, so they can't close the Output tab
		$to_close = qr/$to_close/ unless ref($to_close) eq 'Regex';
		while ($i < @tabs) {
			if ($tabs[$i] eq $to_close) {
				CORE::warn "Internal: Not checking if the file needs to be saved."
					if eval{$default_widget_for[$to_close]->isa('Prima::Edit')};
				$notebook->Notebook->delete_page($_);
				splice @default_widget_for, $i, 1;
				splice @tabs, $i, 1;
				redo;
			}
			$i++;
		}
	}
	
	# Update the tab numbering:

bin/prima-repl  view on Meta::CPAN


package main;

eval 'require PDL::Version' if not defined $PDL::Version::VERSION;

# Print the opening message:
print "Welcome to the Prima REPL, version $version.\n";
print "Using PDL version $PDL::Version::VERSION\n" if ($loaded_PDL);
print "Using PDL::Graphics::Prima\n" if ($loaded_Prima_Graphics);
print "\n";
print join(' ', "If you don't know what you're doing, you can get help by"
				, "typing 'help' and pressing Enter, or by pressing Ctrl-h.\n");


#################################
# Run any initialization script #
#################################
sub redo_initrc {
	my $filename = $initrc_filename if -f $initrc_filename;
	$filename = "$initrc_filename.pl" if -f "$initrc_filename.pl";
	if ($filename) {
		print "Running initialization script\n";
		# Load the init script and send it to 
		open my $fh, '<', $filename;
		my $text = do { local( $/ ) ; <$fh> };
		my_eval("#line 1 \"$filename\"\n$text");
		REPL::warn("Errors encountered running the initialization script:\n$@\n")
			if $@;
		$@ = '';
	}
	else {
		print "No initialization script found\n";
	}
}
redo_initrc if -f $initrc_filename or -f "$initrc_filename.pl";

run Prima;
# Remove the logfile. This will not happen with a system failure, which means
# that the logfile is 'saved' only when there was a problem. The special case of
# the user typing 'exit' at the prompt is handled in pressed_enter().
unlink 'prima-repl.logfile';

__END__

=head1 App::Prima::REPL Help

This is the help documentation for App::Prima::REPL, a graphical run-eval-print-loop
(REPL) for perl development, targeted at pdl users. Its focus is on L<PDL>, the
Perl Data Language, but it works just fine even if you don't have PDL.

At the bottom of the App::Prima::REPL window is a single entry line for direct
command input. The main window is a set of tabs, the first of which is an output
tab. Additional tabs can contain files or any other extension that has been
written as a App::Prima::REPL tab.

If your project has project-specific notes, you should be able to find them
either here: L<prima-repl.initrc> or here: L<prima-repl.initrc.pl>.

=head1 Fixing Documentation Fonts

If your documentation fonts look bad, you can change them by going to
View->Set Font Encoding.

=head1 Basic Navigation

Before I launch into the tutorial, I want to cover some basic navigation to help
you quickly get around the REPL. The following keyboard shortcuts should be
helpful to you even as we get started:

 Normal Keyboard  Mac Laptop
 CTRL-h           CTRL-h        open or switch to the help window
 ALT-1            ??????        go to the output window
 CTRL-i           CTRL-i        put the cursor in the input line
 CTRL-PageUp      CTRL-FN-Up    go to the previous tab
 CTRL-PageDown    CTRL-FN-Down  go to the next tab

=head1 Tutorials

These are a collection of tutorials to get you started using the Prima REPL.
Except for the first tutorial, text that you should enter will be prefixed with
a prompt like C<< > >>.

=head2 Basic Output

Our first exercise will be getting basic output from the REPL. Enter the
following into the input line, but don't press enter yet:

 print "Hello!"

Take note of the last line of text in the output window, then press enter.
You should see the following appear on your output screen:

 > print "Hello!"
 Hello!

What happens if you type an expression like 1+1? If you just type the expression
in the input line, you will see this as output:

 > 1+1

Why didn't it print 2? It didn't print 2 because you didn't ask it to print 2.
You can easily accomplish that by using the C<print> function, or its
abbreviation C<p>. Type the following in the input line:

 p 1+1

The output should look like this:

 > p 1+1
 2

You may be used to REPLs that print out the result of whatever action you just
took. This REPL does not do that because it is geared towards PDL use, and
the output for PDL can get exceedingly long. Rather than always print
potentially long results to the output, the Prima REPL is quiet by default and
makes it easy to print your results if you want.

=head2 Finding Documentation

Prima REPL uses Prima's built-in pod viewer (which you may be using to view this
documentation). If you have the help window open, you can look at a particular

bin/prima-repl  view on Meta::CPAN


=head1 Customizing

There are a number of ways that you can customize your REPL, including
per-project rc files and pod notes, custom tabs, and custom commands.

=head2 RC File and Notes

App::Prima::REPL supports per-directory rc files and input logs. When you
have a file called F<prima-repl.initrc> or F<prima-repl.initrc.pl> in the
directory from which you execute C<prima-repl>, it will be executed upon 
startup (with a preference for the former). The purpose of this rc file is
to allow for per-project initialization and function definitions.

You can emulate user input with the C<REPL::simulate_run> command, which will add text
to the input line and then use the standard input lne mechanism to evaluate the
text. This can be useful because it puts the evaluated text into the user's
history. However, as this adds lines to the history file, you should use this
sparingly, only when you think the user will want to retrieve the command in
their history.

A very useful apect of initrc file is that you can add documentation by
simply inserting pod in your initrc file. The link at the top of this help file
will automatically open the documentation or give a message indicating that
there is no such documentation. This way, if you declare any useful functions
in your initrc file, you can document them easily.

The initrc file can also add tabs and add custom commands, but you can do
these from a multiline buffer as well, so I put them in their own sections.

=head2 Custom Tabs

If you wish to create custom tabs, there are two commands that will help.

=over

=item REPL::create_new_tab($name, @creation_options)

This puts a new tab at the end of the tab list with the specified name. The
C<@creation_options> are exactly what you would send to Prima's C<insert>
command, including the widget class as the first element. This function
returns the created object when called in scalar context. In list context,
it also returns the tab index as the second return value. This index is useful if
you want to specify a different default widget for your tab, which brings me
to...

=item REPL::change_default_widget($index, $widget)

This function changes the default widget for the tab with the given index.
The default widget is the widget that recieves the keyboard focus when you
first switch to the tab. This function takes the tab index (returned by
C<REPL::create_new_tab> in list context) and the desired widget. Note that
if you want C<CTRL-i> to toggle between the desired widget and the input
line, which is the behavior of the file buffers, you will need to have your
default widge properly respond to C<CTRL-i> keyboard input.

=item REPL::endow_editor_widget($widget)

Given a normal editor widget, this sets the various options to make the editor
behave like the default multiline buffer editor. For example, it turns on
syntax highlighting, sets a monospace font, and sets the default accelerator
keys. Although not strictly necessary for creating tabs, it may be useful if
you want to create a tab with both an editor and some other sort of widget,
side-by-side, as is done in L<App::Prima::REPL::Talk>.

This function does not set the widget as the tab's default widget, since you
may want the default widget to be something else. You should explicitly invoke
C<REPL::change_default_widget> (above) to do that.

The endowed editor has named key bindings that you can overwrite:

 CtrlReturn    CtrlShiftReturn
 CtrlEnter     CtrlShiftEnter
 CtrlPageDown  CtrlPageUp

You can override these commands with something like this:

 # Update ctrl-return/ctrl-edit for the editor
 $editor->accelTable->insert([
       # Ctrl-Enter runs the file
       ['CtrlReturn', '', kb::Return  | km::Ctrl, $run_code ]
     , ['CtrlEnter', '', kb::Enter    | km::Ctrl, $run_code ]
   ]
   , ''
   , 0
 );

=back

At some point I'll add an example of how to use these.

=head2 Custom Commands

Perl knows about I<functions> and I<list operators>. App::Prima::REPL
I<also> knows about I<commands>, like
the C<help> command. Remember, the help command doesn't require quotes around
the module name. How do I do this? I achieve this by hooking into the
C<PressEnter> stage of the InputHistory widget. Hooking into this stage of
the widget lets you modify what eventually gets evaluated (for example, by
applying the NiceSlice filter) and even lets you avoid the Perl eval-stage
altogether.

Here's the hook that proceses the help command:

 $REPL::inline->add_notification(PressEnter => sub {
     # See if they asked for help.
     if ($_[1] =~ /^\s*help\s*(.*)/) {
         get_help($1);
         $_[0]->clear_event;
     }
 });

Notice that the help command does not want the eventual string to be
evaluated, so it calls C<get_help> and then clears the event. If you simply
want to modify the string before it gets evaluated, you need to modify
C<$_[1]> directly without clearing the event.

The subroutine that you write for such hooks will be passed two arguments,
the InputHistory object (C<$REPL::inline>) and the text from the input line's
buffer. Note that by the time your notification receives the latter, it may
have already been modified as there are a number of built-in notifications.



( run in 0.632 second using v1.01-cache-2.11-cpan-ceb78f64989 )