iutils-raku/lib/IUtils/IDEMode.rakumod

168 lines
4.5 KiB
Raku
Raw Permalink Normal View History

2024-12-30 14:10:05 +00:00
#| Interaction wtih Idris 2's IDE Mode
2024-12-30 13:43:26 +00:00
unit class IUtils::IDEMode;
2024-12-30 14:10:05 +00:00
#| The underlying idris process
2024-12-30 13:43:26 +00:00
has $!process;
2024-12-30 14:10:05 +00:00
#| The port number we are connected via
2024-12-30 13:43:26 +00:00
has $!port;
2024-12-30 14:10:05 +00:00
#| The socket we are connected via
2024-12-30 13:43:26 +00:00
has $!socket;
2024-12-30 14:10:05 +00:00
#| The next request id
2024-12-30 13:43:26 +00:00
has $!request-id = 0;
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Grammar for S-Expressions
2024-12-30 13:32:52 +00:00
grammar SExp {
rule TOP { <sexp> }
proto rule sexp {*}
rule sexp:sym<list> { '(' <sexp>* ')' }
rule sexp:sym<nil> { 'nil' }
rule sexp:sym<num> { \d+ }
rule sexp:sym<symbol> { ':' <[\w\-]>+ }
rule sexp:sym<string> { '"' <str-content>* '"' }
token str-content {
| <-[\"\\]>+ # Any char except " or \
| '\\"' # Escaped quote
| '\\' # Escaped backslash
}
}
2024-12-30 14:10:05 +00:00
#| Convert a parsed S-Expression into a list of lists
2024-12-30 13:32:52 +00:00
class SExp::Actions {
method TOP($/) { make $<sexp>.made }
method sexp:sym<list>($/) {
make $<sexp>».made.List
}
method sexp:sym<nil>($/) { make List }
method sexp:sym<num>($/) { make $/.Int }
method sexp:sym<symbol>($/) { make $/.Str.trim }
method sexp:sym<string>($/) {
make $<str-content>».made.join
}
method str-content($/) {
2024-12-30 14:14:19 +00:00
make $/.Str.subst(/\\(.)/, {$0}, :g)
2024-12-30 13:32:52 +00:00
}
}
2024-12-30 13:43:26 +00:00
submethod TWEAK {
# Start idris2 in IDE mode
my $ret = Promise.new;
$!process = Proc::Async.new('idris2', '--ide-mode-socket');
start {
react {
whenever $!process.stdout.lines {
$!port = $_.Int;
$!socket = IO::Socket::INET.new(:host<localhost>, :port($!port));
$ret.keep;
}
whenever $!process.start {
say 'Idris 2 exited, exitcode=', .exitcode, ' signal=', .signal;
done;
}
whenever $!process.ready {
say 'Idris 2 online, PID=', $_;
2024-12-30 13:32:52 +00:00
}
}
}
2024-12-30 13:43:26 +00:00
await $ret;
my ($major, $minor) = self.process-protocol-version;
my @ret = self.version;
my @version = @ret[0][1][1][0];
my $commit = @ret[0][1][1][1][0];
say "Idris 2 Version: ", @version[0], ".", @version[1], ".", @version[2],
" (", $commit, ")";
say "IDE Protocol Version: $major.$minor";
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Capture and parse the initial protocol version message
2024-12-30 13:43:26 +00:00
method process-protocol-version() {
my @resp = self.read-sexp();
if @resp[0] eq ':protocol-version' {
return (@resp[1], @resp[2]);
2024-12-30 13:32:52 +00:00
}
2024-12-30 13:43:26 +00:00
die "Expected protocol version, got: ", @resp;
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Read one sexp from the IDE server
2024-12-30 13:43:26 +00:00
method read-sexp() {
my $len = $!socket.read(6).decode('utf8');
my $msg = $!socket.read(:bin, $len.parse-base(16)).decode('utf8');
SExp.parse($msg, actions => SExp::Actions).made
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Send a command to the IDE server, and collect all the responses to that
#| command
#|
#| For convinence, automatically symbolizes the first argument, and will wrap
#| the command in parens if there is more than one argument
2024-12-30 13:43:26 +00:00
method send-command(*@cmd) {
my $id = ++$!request-id;
my $cmd-str;
if @cmd.elems > 1 {
$cmd-str = "((" ~ (":" ~ @cmd[0]) ~ " " ~ @cmd[1..*].join(" ") ~ ") $id)";
} else {
$cmd-str = "(" ~ (":" ~ @cmd[0]) ~ " $id)";
2024-12-30 13:32:52 +00:00
}
2024-12-30 13:43:26 +00:00
my $len = sprintf("%06x", $cmd-str.chars);
$!socket.print($len ~ $cmd-str);
2024-12-30 13:32:52 +00:00
2024-12-30 13:43:26 +00:00
my @responses;
loop {
my $resp = self.read-sexp();
@responses.push($resp);
2024-12-30 14:14:19 +00:00
if $resp[1][0] eq ':error' && $resp[2] == $id {
die "Idris error: ", $resp[1][1];
}
2024-12-30 13:43:26 +00:00
if $resp[0] eq ':return' && $resp[2] == $id {
return @responses;
2024-12-30 13:32:52 +00:00
}
}
2024-12-30 13:43:26 +00:00
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :load-file command
2024-12-30 13:43:26 +00:00
method load-file($filename, $line-number?){
if $line-number {
self.send-command('load-file', "\"$filename\"", $line-number.Str)
} else {
self.send-command('load-file', "\"$filename\"")
2024-12-30 13:32:52 +00:00
}
2024-12-30 13:43:26 +00:00
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :cd command
2024-12-30 13:43:26 +00:00
method cd($filepath) {
self.send-command('cd', "\"$filepath\"")
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :interpret command
2024-12-30 13:43:26 +00:00
method interpret($cmd) {
self.send-command('interpret', "\"$cmd\"")
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :type-of command
2024-12-30 13:43:26 +00:00
method type-of($item) {
self.send-command('type-of', "\"$item\"")
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :docs-for command
2024-12-30 13:43:26 +00:00
method docs-for($item) {
self.send-command('docs-for', "\"$item\"")
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :browse-namespace command
#|
#| Will import $namespace before browsing it to ensure we get results
2024-12-30 13:43:26 +00:00
method browse-namespace($namespace) {
self.interpret: ":import $namespace";
self.send-command('browse-namespace', "\"$namespace\"")
}
2024-12-30 13:32:52 +00:00
2024-12-30 14:10:05 +00:00
#| Wrapper for :version command
2024-12-30 13:43:26 +00:00
method version() {
self.send-command('version')
2024-12-30 13:32:52 +00:00
}