oven-sh / bun

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one
https://bun.sh
Other
74.05k stars 2.76k forks source link

`node:readline` fails to correctly handle Ctrl+D signals, `rl.on('close')` and `completer` tab completions #2333

Closed jhmaster2000 closed 10 months ago

jhmaster2000 commented 1 year ago

What version of Bun is running?

0.6.3

What platform is your computer?

Linux 5.15.90.1-microsoft-standard-WSL2 x86_64 x86_64

What steps can reproduce the bug?

There are multiple issues with the readline polyfill at the moment, given the following sample file test.ts:

import readline from 'readline/promises';
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    terminal: true,
    tabSize: 4,
    prompt: '> ',
    historySize: 10,
    history: ['test history 1', 'test history 2', 'test history 3', 'test history 4', 'test history 5'],
    completer(line: string) {
        const completions = ['hello', 'world'];
        const hits = completions.filter(c => c.startsWith(line));
        return [hits.length ? hits : completions, line];
    }
});
rl.prompt();
rl.on('line', (line: string) => {
    console.log(`Line: ${line}`);
    rl.prompt();
});
rl.on('close', () => {
    console.log('Exiting...');
    process.exit(0);
});

Ran on Bun with: bun run test.ts Ran on Node.js for comparison with: ts-node-esm test.ts (npm i -g ts-node)

What is the expected behavior?

Running the file in Node.js as indicated above and performing the following sequence of inputs:

  1. Press Arrow Up 3 times (it should cycle through the preset history from test history 1 to test history 3 on the prompt)
  2. Press Enter (it should print Line: test history 3)
  3. Type "sample text" into the prompt and press Enter (it should print Line: sample text)
  4. Press Arrow Up (it should show sample text from the updated history)
  5. Press Arrow Down (it should go back to the blank prompt)
  6. Type h and press Tab (it should auto-complete to hello) then press Enter (it should print Line: hello)
  7. Type w and press Tab (it should auto-complete to world) then press Tab again (it should list matching completions)
  8. Press Enter (it should print Line: world)
  9. Press Tab twice (it should print all completions hello world
  10. Press Ctrl + D to exit (It should print Exiting... and then terminate the program)

    Resulting in the below final output:

    
    > test history 3
    Line: test history 3
    > sample text
    Line: sample text
    > hello
    Line: hello
    > world
    world

world Line: world

hello world

Exiting...

What do you see instead?

Running the same file in Bun following the same inputs as above:

Results in the following output: (this output is outdated and no longer relevant)

> ^[[A^[[A^[[A
687 |       // Parse the key modifier
688 |       keyCtrl = !!(modifier & 4);
689 |       keyMeta = !!(modifier & 10);
690 |       keyShift = !!(modifier & 1);
691 | 
692 |       keyCode = code;
         ^
ReferenceError: Can't find variable: keyCode
      at node:readline:692:6
      at onData (node:readline:1192:12)
      at addChunk (node:stream:2713:8)
      at readableAddChunk (node:stream:2694:12)
sample text
sample text
Line: sample text
> ^[[A^[[Bh
687 |       // Parse the key modifier
688 |       keyCtrl = !!(modifier & 4);
689 |       keyMeta = !!(modifier & 10);
690 |       keyShift = !!(modifier & 1);
691 | 
692 |       keyCode = code;
         ^
ReferenceError: Can't find variable: keyCode
      at node:readline:692:6
      at onData (node:readline:1192:12)
      at addChunk (node:stream:2713:8)
      at readableAddChunk (node:stream:2694:12)
w
w
Line: w
>                       1918 |     this[kBeforeEdit](this.line, this.cursor);
1919 | 
1920 |     // Apply/show completions.
1921 |     var completionsWidth = ArrayPrototypeMap.call(completions, e => getStringWidth(e));
1922 | 
1923 |     var width = MathMaxApply(completionsWidth) + 2; // 2 space padding
                   ^
TypeError: undefined is not a function
      at [_tabCompleter] (node:readline:1923:16)
      at node:readline:1890:4
^C

Going over everything that went wrong here:

Striked out issues have been fixed.

Issue at steps 1, 2, 4, 5, 6:
Issue at steps 3 & 8:
Issue at steps 7 & 9: (Remains in Bun 0.8.2)
Issue at step 10:
Bonus Issue

Additional information

Updated for Bun v0.6.3 (issue persists)

birkskyum commented 1 year ago

What I experience here is that some keys get typed double. This appear similar to:

birkskyum commented 1 year ago

Update with Canary after #3099 was fixed. In step 7 it still breaks for me, because it seems like Tab is clicked twice even though I'm sure I only click it one. The result is that w "Tab+Tab" gives:

> worldLine: world
> Exiting...

So I can't get beyond step 7 due to this.

birkskyum commented 1 year ago

@dylan-conway , after looking at the clack issue, do you have any idea why "Tab" might be executed twice?

birkskyum commented 1 year ago

The issue with double letters met when writing "sample text" and the issue with Ctrl+D to exit appear to have been resolved

dylan-conway commented 1 year ago

@dylan-conway , after looking at the clack issue, do you have any idea why "Tab" might be executed twice?

I'm not sure yet, I tried to fix this yesterday but failed. I'll try again today. Seems to be related to the readline exit event

jhmaster2000 commented 1 year ago

On current 0.8.2 canary on Linux all the issues are resolved for me, except for completions which hitting Tab makes the entire stdin freeze and become completely unresponsive.

Without a completer function specified Tab behaves normally, so at least readline is now usable without completions.

Subwaytime commented 1 year ago

Seems to relate to my issue as well: #4835

Electroid commented 1 year ago

In Bun v1.0.7, it appears to exit after you get to the w step and press Tab.

❯ bun rl.ts
> test history 3
Line: test history 3
> sample text
Line: sample text
> sample text
Line: sample text
> hello
Line: hello
> worldLine: world
> Exiting...
jhmaster2000 commented 1 year ago

Yep, can reproduce, to put it in general terms, it seems to succeed at a first completion and then crashes on a second one, likely something not being properly cleaned up/freed for re-use in the completion handling code.

paperdave commented 10 months ago

👀 i think i fixed this