A node shell based on gnu readline

2020-03-30 06:18发布

问题:

Is there a shell for node which uses gnu readline internally?

As you know node shell sucks in 2 ways (among others): It doesn't have search for history. This can be solved by using something like https://github.com/danielgtaylor/nesh It's so poor. Shells that use gnu readline (like psql, ipython, python, bash, etc) has lots of features out of the box and all share a configuration you set in your ~/.inputrc. For example I have good vim-mode in all above shells I listed because they all use gnu readline internally. If there's a shell for js that uses gnu readline internally too, then it'll be in harmony with my other shells.

回答1:

that other guy is right: rlwrap will work. Unfortunately, it throws away nodes own completion. How to avoid this is a FAQ, so here follows a way to restore completion: not by feeding a TAB to the wrapped command and then somehow parsing the resulting mess, but using a filter.

Filters are small scripts that act as rlwrap plugins. The can re-write user input, command output, prompts, history and completion word lists. They can be written in perl or python and be combined in a pipeline.

Filters can do one more trick: interact with the wrapped command behind the user's back (the cloak_and_dagger() method)

So if we teach node a new command rlwrap_complete(prefix) that prints a list of completions of prefix we can use cloak_and_dagger("rlwrap_complete($prefix)") to get hold of all possibe completions, and use those for rlwraps own completer.

Here is the filter, written for node, in perl, but a python version for a different command would look very similar:

#!/usr/bin/env perl

use lib ($ENV{RLWRAP_FILTERDIR} or ".");
use RlwrapFilter;
use strict;

my $filter = new RlwrapFilter;

$filter -> completion_handler( sub {
  my($line, $prefix, @completions) = @_;
  my $command = "rlwrap_complete('$prefix')";
  my $completion_list = $filter -> cloak_and_dagger($command, "> ", 0.1); # read until we see a new prompt "> "
  my @new_completions =  grep /^$prefix/, split /\r\n/, $completion_list; # split on CRNL and weed out rubbish
  return (@completions, @new_completions);                                                  
 });

$filter -> run;

Now we have to teach node the command rlwrap_complete(). As node doesn't use an init file like .noderc we have to create a REPL instance and extend it:

#!/usr/bin/env node

// terminal:false disables readline (just like env NODE_NO_READLINE=1): 
var myrepl = require("repl").start({terminal:false}); 

// add REPL command rlwrap_complete(prefix) that prints a simple list of completions of prefix
myrepl.context['rlwrap_complete'] =  function(prefix) {
  myrepl.complete(prefix, function(err,data) { for (x of data[0]) {console.log(x)}});
}    

Move the filter code to $RLWRAP_FILTERDIR/node_complete, save the above code as myrepl.js and make it executable. Then call:

$ rlwrap -z node_complete ./myrepl.js

... and enjoy a REPL with searchable history and TAB completion! Any time you press TAB rlwrap will have an (invisible) chat with node to come up with the right completions.

Other rlwrap goodies (coloured prompts, vi mode, additional filters) can be added if you want them.

Of course, any REPL needs to be able to do a bit of metaprogramming to access its own namespace as data before we can use the same solution as for node



回答2:

rlwrap will let you use readline for arbitrary programs.

It works best if the program doesn't (or can be convinced not to) use its own line editing. For node in particular, you can use:

NODE_NO_READLINE=1 rlwrap node


标签: node.js shell