Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some tab-completion support. #11

Merged
merged 6 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
language: perl6
env:
- MVM_SPESH_DISABLE=1
perl6:
- latest
- 2017.08
before_install:
- wget https://github.com/zeromq/libzmq/releases/download/v4.2.2/zeromq-4.2.2.tar.gz
- tar -xzvf zeromq-4.2.2.tar.gz
Expand Down
14 changes: 13 additions & 1 deletion lib/Jupyter/Kernel.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,26 @@ method run($spec-file!) {
}
when 'is_complete_request' {
my $code = ~ $msg<content><code>;
my $result = $sandbox.eval($code);
my $result = $sandbox.eval($code, :no-persist);
my $status = 'complete';
debug "exception from sandbox: { .gist }" with $result.exception;
$status = 'invalid' if $result.exception;
$status = 'incomplete' if $result.incomplete;
debug "sending is_complete_reply: $status";
$shell.send: 'is_complete_reply', { :$status };
}
when 'complete_request' {
my $code = ~$msg<content><code>;
my $cursor_end = $msg<content><cursor_pos>;
my (Int $cursor_start, $completions) = $sandbox.completions($code);
$shell.send: 'complete_reply',
{ matches => $completions,
:$cursor_end,
:$cursor_start,
metadata => {},
status => 'ok'
}
}
when 'shutdown_request' {
my $restart = $msg<content><restart>;
$restart = False;
Expand Down
71 changes: 59 additions & 12 deletions lib/Jupyter/Kernel/Sandbox.pm6
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#!perl6

use Log::Async;
use nqp;

%*ENV<RAKUDO_LINE_EDITOR> = 'none';
%*ENV<RAKUDO_DISABLE_MULTILINE> = 0;

my class Result {
has $.output;
has Str $.output;
has $.output-raw;
has $.exception;
has Bool $.incomplete;
has $.stdout;
Expand Down Expand Up @@ -36,26 +39,70 @@ class Jupyter::Kernel::Sandbox is export {
$!repl = REPL.new($!compiler, {});
}

method eval(Str $code) {
method eval(Str $code, Bool :$no-persist) {
my $stdout;
my $*CTXSAVE = $!repl;
my $*MAIN_CTX;
my $*OUT = class { method print(*@args) { $stdout ~= @args.join }
method flush { } }
my $output = $!repl.repl-eval(
$code,
my $exception,
:outer_ctx($!save_ctx),
:interactive(1)
);

if $*MAIN_CTX {
my $exception;
my $output =
try $!repl.repl-eval(
$code,
$exception,
:outer_ctx($!save_ctx),
:interactive(1)
);
my $caught;
$caught = $! if $!;

if $*MAIN_CTX and !$no-persist {
$!save_ctx := $*MAIN_CTX;
}

$output = ~$exception with $exception;
$output = ~$_ with $exception // $caught;
my $incomplete = so $!repl.input-incomplete($output);
return Result.new(:output($output.gist),:$stdout,:$exception, :$incomplete);
return Result.new(:output($output.gist),:output-raw($output),:$stdout,:$exception, :$incomplete);
}

sub extract-last-word(Str $line) {
# based on src/core/REPL.pm
my $m = $line ~~ /^ $<prefix>=[.*?] <|w>$<last_word>=[ [\w | '-' | '_' ]* ]$/;
return ( $line, '') unless $m;
( ~$m<prefix>, ~$m<last_word> )
}

#! returns offset and list of completions
method completions($str) {
my ($prefix,$last) = extract-last-word($str);

# Handle methods ourselves.
if $prefix and substr($prefix,*-1,1) eq '.' {
my ($pre,$what) = extract-last-word(substr($prefix,0,*-1));
my $var = $what;
if $pre ~~ /$<sigil>=<[&$@%]>$/ {
my $sigil = ~$<sigil>;
$var = $sigil ~ $what;
}
my $res = self.eval($var ~ '.^methods(:all).map({.name}).join(" ")', :no-persist );
if !$res.exception && !$res.incomplete {
my @methods = $res.output-raw.split(' ').unique;
return $prefix.chars, @methods.grep( { / ^ "$last" / } ).sort;
}
}

# Also handle variables
# todo. REPL doesn't preserve ::.keys in context.
# if $prefix and substr($prefix,*-1,1) eq any('$','%','@','&') {
# my $res = self.eval('::.keys.join(" ")');
# say "want ----------- { $res.output-raw.perl } ";
# my @possible = $res.output-raw.split(' ');
# my @found = ( |@possible, |( CORE::.keys ) ).grep( { /^ "$last" / } ).sort;
# return $prefix.chars, @found;
# }

my @completions = $!repl.completions-for-line($str,$str.chars-1).map({ .subst(/^ "$prefix" /,'') });
return $prefix.chars, @completions;
}
}

12 changes: 10 additions & 2 deletions t/02-sandbox.t
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use lib 'lib';
use Test;
use Jupyter::Kernel::Sandbox;

plan 24;
plan 27;

my $r = Jupyter::Kernel::Sandbox.new;

Expand Down Expand Up @@ -39,7 +39,7 @@ $res = $r.eval('my @bound := <1 2 3>;');
ok !$res.exception, 'bound an array';
$res = $r.eval('@bound[1]');
is $res.output, "2", 'bound array';
is $res.output-mime-type, 'text/plain';
is $res.output-mime-type, 'text/plain', 'mime type';

$res = $r.eval('say "<svg></svg>"');
is $res.stdout, "<svg></svg>\n", 'generated svg on stdout';
Expand All @@ -51,3 +51,11 @@ is $res.output-mime-type, 'image/svg+xml', 'svg output mime type';

$res = $r.eval('Any');
is $res.output.perl, '"(Any)"', 'Any works';

$res = $r.eval('die');
is $res.output, 'Died', 'Die trapped';

$res = $r.eval('sub foo { ... }; foo;');
is $res.output, 'Stub code executed', 'trapped sub call that died';

ok 1, 'still here';
43 changes: 43 additions & 0 deletions t/04-completions.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env perl6
use lib 'lib';
use Test;
use Jupyter::Kernel::Sandbox;

plan 13;

my $r = Jupyter::Kernel::Sandbox.new;

my ($pos, $completions) = $r.completions('sa');
is-deeply $completions, [<samecase samemark samewith say>], 'completions for "sa"';
is $pos, 0, 'offset';

($pos, $completions) = $r.completions(' sa');
is-deeply $completions, [<samecase samemark samewith say>], 'completions for "sa"';
is $pos, 1, 'offset';

my $res = $r.eval(q[my $x = 'hello'; $x]);
is $res.output, 'hello', 'output';
($pos,$completions) = $r.completions('$x.pe');
is-deeply $completions, <perl perlseen permutations>, 'autocomplete for a string';

$res = $r.eval(q|class Foo { method barglefloober { ... } }; my $y = Foo.new;|);
is $res.output, 'Foo.new', 'declared class';
($pos,$completions) = $r.completions('$y.barglefl');
is-deeply $completions, $( 'barglefloober', ) , 'Declared a class and completion was a method';

$res = $r.eval('my $abc = 12;');
($pos,$completions) = $r.completions('$abc.is-prim');
is-deeply $completions, $('is-prime', ), 'method with a -';

($pos,$completions) = $r.completions('if 15.is-prim');
is-deeply $completions, $( 'is-prime', ), 'is-prime for a number';

($pos,$completions) = $r.completions('if "hello world".sa');
is-deeply $completions, $( 'say', ), 'say for a string';

$res = $r.eval('my $ghostbusters = 99');
is $res.output, 99, 'made a var';
($pos,$completions) = $r.completions('say $ghost');
todo 'autocomplete variables';
is-deeply $completions, $( '$ghostbusters', ), 'completed a variable';