Skip to content

Commit

Permalink
Merge pull request #4623 from japhb/support-terminal-lineeditor
Browse files Browse the repository at this point in the history
Support Terminal::LineEditor in the Raku REPL
  • Loading branch information
japhb committed Nov 8, 2021
2 parents e8a4828 + 5db6a34 commit 4902299
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 20 deletions.
17 changes: 11 additions & 6 deletions docs/line-editor.pod
Expand Up @@ -9,9 +9,11 @@ provide much of the same functionality.

=head2 AVAILABLE EDITORS

Right now, there are new line editor modules available: C<Readline> and C<Linenoise>.
They have roughly the same functionality, but C<Linenoise> provides tab completion,
and C<Readline> has an easier time with multibyte input.
Right now, there are three line editor modules available: C<Readline>,
C<Linenoise>, and C<LineEditor> (installed via C<Terminal::LineEditor>). They
have roughly the same functionality, but C<Linenoise> provides tab completion,
while C<Readline> and C<LineEditor> both have an easier time with multibyte
input.

=head2 ENVIRONMENT VARIABLES

Expand All @@ -27,13 +29,16 @@ This disables multi-line input for the REPL if truthy.
=item RAKUDO_HIST

This specifies the location of the history file used by the line editor; the
default is C<~/.perl6/rakudo-history>.
default is C<~/.raku/rakudo-history>. Before Rakudo version 2020.02 the
default was C<~/.perl6/rakudo-history>. If the older default file exists and
the newer one does not, it will be automatically migrated.


=item RAKUDO_LINE_EDITOR

This specifies the preferred line editor to use; valid values are C<Readline>,
C<Linenoise>, and C<none>. A value of C<none> is useful if you want to avoid
the recommendation message upon REPL startup.
C<Linenoise>, C<LineEditor>, and C<none>. A value of C<none> is useful if you
want to avoid the recommendation message upon REPL startup.

=back

Expand Down
12 changes: 6 additions & 6 deletions docs/running.pod
Expand Up @@ -157,19 +157,19 @@ emitted.
=item C<RAKUDO_LINE_EDITOR>
This specifies the preferred line editor to use; valid values are C<Readline>,
C<Linenoise>, and none. A value of none is useful if you want to avoid the
recommendation message upon REPL startup.
C<Linenoise>, C<LineEditor>, and C<none>. A value of C<none> is useful if you
want to avoid the recommendation message upon REPL startup.

=item C<RAKUDO_DISABLE_MULTILINE>
If set to 1, will disable multiline input for the REPL.

=item C<RAKUDO_HIST>
This specifies the location of the history file used by the
line editor; the default is C<~/.raku/rakudo-history>.
Before Rakudo version 2020.02 the default was
C<~/.perl6/rakudo-history>.
This specifies the location of the history file used by the line editor; the
default is C<~/.raku/rakudo-history>. Before Rakudo version 2020.02 the
default was C<~/.perl6/rakudo-history>. If the older default file exists and
the newer one does not, it will be automatically migrated.

=back

Expand Down
53 changes: 49 additions & 4 deletions src/core.c/REPL.pm6
Expand Up @@ -100,6 +100,38 @@ do {
}
}

my role TerminalLineEditorBehavior[$WHO] {
my $cli-input = $WHO<CLIInput>;
my $cli;

method completions-for-line(Str $line, int $cursor-index) { ... }

method history-file(--> Str:D) { ... }

method init-line-editor {
my sub get-completions($contents, $pos) {
eager self.completions-for-line($contents, $pos)
}
$cli = $cli-input.new(:&get-completions);
$cli.load-history($.history-file);
}

method teardown-line-editor {
$cli.save-history($.history-file);
}

method repl-read(Mu \prompt) {
self.update-completions;
my $line = $cli.prompt(prompt);

if $line.defined && $line.match(/\S/) {
$cli.add-history($line);
}

$line
}
}

my role FallbackBehavior {
method repl-read(Mu \prompt) {
print prompt;
Expand Down Expand Up @@ -171,7 +203,8 @@ do {
has $!need-more-input = {};
has $!control-not-allowed = {};

sub do-mixin($self, Str $module-name, $behavior, Str :$fallback) {
sub do-mixin($self, Str $module-name, $behavior, :@extra-modules,
Str :$fallback, Bool :$classlike) {
my Bool $problem = False;
try {
CATCH {
Expand All @@ -190,8 +223,10 @@ do {
}
}

(require ::($_)) for @extra-modules;
my $module = do require ::($module-name);
my $new-self = $self but $behavior.^parameterize($module.WHO<EXPORT>.WHO<ALL>.WHO);
my $who = $classlike ?? $module.WHO !! $module.WHO<EXPORT>.WHO<ALL>.WHO;
my $new-self = $self but $behavior.^parameterize($who);
$new-self.?init-line-editor();
return ( $new-self, False );
}
Expand All @@ -207,10 +242,17 @@ do {
do-mixin($self, 'Linenoise', LinenoiseBehavior, |c)
}

sub mixin-terminal-lineeditor($self, |c) {
do-mixin($self, 'Terminal::LineEditor', TerminalLineEditorBehavior,
:extra-modules('Terminal::LineEditor::RawTerminalInput',),
:classlike, |c)
}

sub mixin-line-editor($self) {
my %editor-to-mixin = (
:Linenoise(&mixin-linenoise),
:Readline(&mixin-readline),
:LineEditor(&mixin-terminal-lineeditor),
:none(-> $self { ( $self but FallbackBehavior, False ) }),
);

Expand All @@ -231,15 +273,18 @@ do {
my ( $new-self, $problem ) = mixin-readline($self, :fallback<Linenoise>);
return $new-self if $new-self;

( $new-self, $problem ) = mixin-linenoise($self);
( $new-self, $problem ) = mixin-linenoise($self, :fallback<LineEditor>);
return $new-self if $new-self;

( $new-self, $problem ) = mixin-terminal-lineeditor($self);
return $new-self if $new-self;

if $problem {
say 'Continuing without tab completions or line editor';
say 'You may want to consider using rlwrap for simple line editor functionality';
}
elsif !Rakudo::Internals.IS-WIN and !( %*ENV<_>:exists and %*ENV<_>.ends-with: 'rlwrap' ) {
say 'You may want to `zef install Readline` or `zef install Linenoise` or use rlwrap for a line editor';
say 'You may want to `zef install Readline`, `zef install Linenoise`, or `zef install Terminal::LineEditor` or use rlwrap for a line editor';
}
say '';

Expand Down
10 changes: 6 additions & 4 deletions t/02-rakudo/repl.t
Expand Up @@ -8,7 +8,8 @@ plan 47;
my $eof = $*DISTRO.is-win ?? "'^Z'" !! "'^D'";
my $*REPL-SCRUBBER = -> $_ is copy {
$_ = .lines.skip(4).join("\n");
s/^^ "You may want to `zef install Readline` or `zef install Linenoise`"
s/^^ "You may want to `zef install Readline`, `zef install Linenoise`,"
" or `zef install Terminal::LineEditor`"
" or use rlwrap for a line editor\n\n"//;
s/^^ "To exit type 'exit' or $eof\n"//;
s:g/ ^^ "> " //; # Strip out the prompts
Expand Down Expand Up @@ -164,12 +165,13 @@ is-run-repl ['Nil'], /Nil/, 'REPL outputs Nil as a Nil';
skip 'Result differs on OSX';
# is-run-repl ['say "hi"'], {
# .subst(:g, /\W+/, '') eq
# 'YoumaywanttozefinstallReadlineorzefinstallLinenoise'
# 'YoumaywanttozefinstallReadlinezefinstallLinenoise'
# ~ 'orzefinstallTerminalLineEditor'
# ~ 'oruserlwrapforalineeditor' ~ 'ToexittypeexitorD' ~ 'hi'
# }, 'REPL session does not have unexpected stuff';

## XXX TODO: need to write tests that exercise the REPL with Linenoise
# and Readline installed. Particular things to check:
## XXX TODO: need to write tests that exercise the REPL with Linenoise,
# Readline, and Terminal::LineEditor installed. Particular things to check:
# 1. History file can be made on all OSes:
# https://github.com/rakudo/rakudo/commit/b4fa6d6792dd02424d2182b73c31a071cddc0b8e
# 2. Test REPL does not show errors when $*HOME is not set:
Expand Down

0 comments on commit 4902299

Please sign in to comment.