Search code examples
node.jsautocompleteconsole-applicationcommand-line-interface

NodeJS readline autocomplete several words


I'm making a simple CLI app with NodeJS using the Readline module. I want to autocomplete the user's input. For this, I'm using the autocompletion function of the module:

function completer(line) {
   const completions = '.help .error .exit .quit .q'.split(' ');
   const hits = completions.filter((c) => c.startsWith(line));
   // show all completions if none found
   return [hits.length ? hits : completions, line];
}

With this function, I'm able to complete one command but no several commands in the same line:

For example:

(CLI App) > .e<tab>
            .error .exit

(CLI App) > .err<tab>
(CLI App) > .error

(CLI App) > .error .ex<tab>
            .help .error .exit .quit .q

I modified the completer function to get only the autocompletion's suggestions of the current command that the user is writing:

function completer(line) {
   const completions = '.help .error .exit .quit .q'.split(' ');
   const hits = completions.filter((c) => c.startsWith(line.split(' ').slice(-1)));

   return [hits.length ? hits : completions, line];
}

and I get the correct suggestions but the user input doesn't change:

(CLI App) > .e<tab>
            .error .exit

(CLI App) > .err<tab>
(CLI App) > .error

(CLI App) > .error .ex<tab>
            .exit
(CLI App) > .error .ex

Is there any way to solve this? Any assistance you can give would be very appreciated.

Thanks.


Solution

  • Using the Chris's tip I got a solution: replace the last part of the line with the hit (when I only have one).

    I calculate the length of the last part of the line (the actual command that I want to autocomplete) to move the cursor to the begin of this command. Then, I get all the line minus the current command and I concat the hit. Finally, I set the cursor at the end of the line.

    I tried to use methods from the docs without luck: readline.cursorTo(stream, x, y) and readline.moveCursor(stream, dx, dy) don't work for me.

    Thereadline.clearLine(stream, dir) method clear all the line and no 'to the right from cursor' (the behaviour that I want), despite of it's present in the doc.

    function completer(line) {
        const completions = '.help .error .exit .quit .q'.split(' ');
        let cmds = line.split(' ');
        const hits = completions.filter((c) => c.startsWith(cmds.slice(-1)));
    
        if ((cmds.length > 1) && (hits.length === 1)) {
            let lastCmd = cmds.slice(-1)[0];
            let pos = lastCmd.length;
            rl.line = line.slice(0, -pos).concat(hits[0]);
            rl.cursor = rl.line.length + 1;
        }
    
        return [hits.length ? hits.sort() : completions.sort(), line];
    }