Search code examples
node.jsterminalcjkcaret

terminal stdin moveCursor misalignment with wide characters


I need to programmatically move terminal's caret position with NodeJS. I am using process.stdout.moveCursor(x, y) which works fine on normal ASCII characters with 1 character width.

However, this doesn't work well with characters that have longer EAST ASIAN WIDTH, such as Chinese Characters, Japanese Characters, and full width English Charachters. For example, width versus width, if I move the caret 1 unit from the right, the caret position will be in the middle of , causing the caret to not even appear in Windows' terminals.

Even though I could keep track of the width for every single character and do calculations like below, I feel like there should be a way to move the caret based on whole characters, not based on whatever unit x is in moveCursor (since natually pressing the left arrow in the terminal will move the caret accross correctly.)

process.stdout.write("Test data! 測試資料123テスト");
/* to move caret 5 characters from the right requires
 the following value to get the correct result */
const offsetRight = 3 * -1 + 2 * -2;
process.stdout.moveCursor(offsetRight, 0);

Note: I'm using the latest nodeJS version and the program is mainly on Windows 10/11 command promt. I've however tested it on Ubunto and WSL and both have the same issue.


Solution

  • One solution I found is to be creative with the Save/Restore Current Cursor Position ANSI escape code . This solution however only works for ANSI terminals I suppose.

    Cursor Forward (\x1b[nC) and Cursor Back (\x1b[n) have the same problem as described in the question since it still depends on converting X characters to Y width cell units. Instead, Saved cursor position works correctly because it reflects the cursor's absolute cells position no matter there are wider characters with wide multiple cell units or not.

    const test = "Test data! 測試資料123テスト";
    const offsetRight = 3; //Cursor should stop right after '123'
    process.stdout.write(test.slice(0, test.length - offsetRight));
    process.stdout.write('\x1b[s'); //Save cursor position
    process.stdout.write(test.slice(test.length - offsetRight));
    process.stdout.write('\x1b[u'); //Restore cursor position