3

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 and it is not using gnu readline. Shells that use gnu readline (like psql, ipython, python, bash, etc) has lots of features out of the box and all share a single configuration file 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.

Sassan
  • 2,187
  • 2
  • 24
  • 43
  • 2
    Have you tried `rlwrap`? It lets you use readline for input in any program – that other guy Apr 19 '17 at 20:40
  • Thanks, it opened a whole new door for me. But unfortunately even though I have `set editing-mode vi` and `set keymap vi` in my .inputrc `rlwrap` doesn't respect it and there's no `vi-mode`. I tried socat but it has some problems too. – Sassan Apr 19 '17 at 22:31
  • vi-mode works in `rlwrap nc localhost 8000` but it doesn't work with `node`, `nesh` nor `bc` – Sassan Apr 19 '17 at 22:32
  • ok I needed `-a --` consider adding it as an answer and I'll accept it. – Sassan Apr 19 '17 at 22:34
  • It doesn't support `set show-mode-in-prompt on` very well. – Sassan Apr 19 '17 at 23:14
  • @Sassan: What exactly goes wrong when using `rlwrap` with `set show-mode-in-prompt on`? – Hans Lub Apr 23 '17 at 20:54
  • It wasn't working with `NODE_NO_READLINE=1` but it works nicely with `NODE_NO_READLINE=1`. – Sassan Apr 23 '17 at 21:32

2 Answers2

10

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

Community
  • 1
  • 1
Hans Lub
  • 5,513
  • 1
  • 23
  • 43
  • 1
    Thanks for this in detail answer. I guess we should add `-c` to rlwrap command so that completion for `fs.` works too, otherwise prefix will be empty string in case of `fs.`. – Sassan Apr 29 '17 at 09:26
  • 1
    @Sassan: removing the dot `.` from the list of word-breaking characters is just a _side effect_ of the `-c` a.k.a `--complete-filenames` option (the general method is by using `--break-chars`) but you are right: for Javascript you want to complete prefixes like `fs.` Your comment made me aware that the completion handler needs to _add_ the original `@completions` to `@new_completions` - if not, `rlwrap -c` will not complete on filenames. I edited my answer accordingly. – Hans Lub Apr 29 '17 at 09:42
  • 1
    thanks for your detailed information, I also use `-e ''` to avoid extra space after completion. – Sassan Apr 29 '17 at 15:36
  • I haven't been able to get this to work. Under Debian bullseye, rlwrap 0.43, and Node v12.22.5 pressing tab after "foo".s, just displays a tab. Under CygWin (CYGWIN_NT-10.0-19043), Node v18.5.0, and rlwrap 0.37, pressing tab after "foo".s, just beeps. – Diomidis Spinellis Oct 11 '22 at 08:17
3

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
that other guy
  • 116,971
  • 11
  • 170
  • 194
  • `NODE_NO_READLINE=1` made it much better. I know `rlwrap` accepts autocompletion via `-f` but is it possible to use the underlying dynamic repl's autocompletion? For example when I enter `fs.` and press tab is there anyway it brings the autocompletion node suggests? – Sassan Apr 20 '17 at 20:02
  • 1
    @Sassan: No, alas. See the answer to [Is there a way to run rlwrap with tab completion disabled?](http://stackoverflow.com/questions/9210931/is-there-a-way-of-running-rlwrap-with-tab-completion-disabled) – Hans Lub Apr 23 '17 at 20:42
  • @HansLub Yeah I read your answer there. Thanks for mentioning it here. Regarding this comment: http://stackoverflow.com/questions/9210931/is-there-a-way-of-running-rlwrap-with-tab-completion-disabled#comment11958744_9219349 I was wondering if it's possible to write specific codes to handle specific REPLs. I'm talking about handling tab completion case by case at least for popular REPLs. The problem is there's not much hope for the community around node to use readline or any standard thing at all. – Sassan Apr 23 '17 at 21:37
  • Because for some unknown reason they just love new things and hate old things like readline no matter what. So I have more hope handling it in rlwrap than I have in node to use readline. Node prints all completion suggestions as plain text with no pagination or magic. It should be the same for many other REPLs so if we handle this case maybe we can have auto-completion in many REPLs wrapped by rlwrap. – Sassan Apr 23 '17 at 21:40
  • 1
    Feeding a TAB to the underlying command is not difficult, but interpreting what comes back is a problem (this needs the "virtual terminal emulator" mentioned in [my comment](http://stackoverflow.com/questions/9210931/is-there-a-way-of-running-rlwrap-with-tab-completion-disabled#comment11958744_9219349)) A much more elegant solution would be to write a `node.js` function `complete(text)` that prints a list of completions, followed by e.g. `"END_OF_COMPLETIONS"` and use that in a filter, using the `cloak_and_dagger()` method that interacts with the underlying command without bothering the user. – Hans Lub Apr 23 '17 at 21:47
  • While your answer was really helpful, Hans provided a much completer answer covering auto complete. So I thought maybe it's better to have his answer as the accepted answer. – Sassan Apr 29 '17 at 08:39