Inquirer.js icon indicating copy to clipboard operation
Inquirer.js copied to clipboard

Cursor positioning after line wrap

Open apowers313 opened this issue 4 months ago • 2 comments

When inquirer/prompts line wraps, it seems to frequently mis-position the cursor: https://vimeo.com/manage/videos/1110980503

I've found that it is consistently replicable if I back-space to the previous line. The mis-positioning seems to be similar to the number of characters used for the ANSI color codes around the '?' at the beginning of the line (but that's just a guess).

I'm curious if anyone else can replicate this problem or if it's just me?

Simple replication code is here:

import { input } from '@inquirer/prompts';

const result = await input({
  message: 'Type a long line that wraps to see cursor bug:',
});

apowers313 avatar Aug 18 '25 14:08 apowers313

Interesting, what's your terminal? I was able to reproduce with Warp.

On Warp, I think what happens is the terminal decides to wrap words. Once it does the correct cursor position is lost. I also see that if it's a whitespace causing the wrapping, then there's no issue; the cursor doesn't end up out of sync with the content.

I'm not 100% sure how to address that problem. Maybe we could review how breakLines is implemented so that we force the word-wrapping on all terminals. And then somehow ensure we set the cursor position in a way that'll work across terminals that force a word-wrap on line returns and those that don't.

If someone wants to research options here, help is welcomed as I don't think I'll have time to dig into this one in the coming weeks. šŸ¤žšŸ»

SBoudrias avatar Aug 30 '25 18:08 SBoudrias

I'm using Termius on iPad.

Let me see if I can figure out some options.

apowers313 avatar Aug 30 '25 19:08 apowers313

@SBoudrias, can I work on this issue?

MazenSamehR avatar Dec 14 '25 15:12 MazenSamehR

@MazenSamehR absolutely! I appreciate it šŸ’œ

SBoudrias avatar Dec 14 '25 21:12 SBoudrias

@SBoudrias I think I found the root cause: wrap-ansi is mainly a word wrapper, not a strict character wrapper. Even with { hard: true }, it still prefers breaking on spaces and only splits mid-word when a single word exceeds the terminal width.

export function breakLines(content: string, width: number): string {
  return content
    .split('\n')
    .flatMap((line) =>
      wrapAnsi(line, width, { trim: false, hard: true })
        .split('\n')
        .map((str) => str.trimEnd()),
    )
    .join('\n');
}

That means terminals that do their own wrapping can end up disagreeing with our expected wrap points.

Would you be OK if I replace wrap-ansi here with a strict character-based ANSI-safe wrapper, so we can force deterministic wrapping across terminals?

MazenSamehR avatar Dec 17 '25 14:12 MazenSamehR

@MazenSamehR okay for me, but I'd prefer not adding an extra dependency if we can save it. If we need one, let's make sure it's maintained by reputable community members.

SBoudrias avatar Dec 17 '25 14:12 SBoudrias

Totally agreed, I’d also prefer avoiding a new dependency if possible. I can implement a small internal char wrapper in breakLines() to force deterministic wrapping.

MazenSamehR avatar Dec 17 '25 15:12 MazenSamehR