Skip to content

Commit

Permalink
readline: improve Unicode handling
Browse files Browse the repository at this point in the history
Fixes: #25693
  • Loading branch information
Avi-D-coder committed Jan 26, 2019
1 parent f40778e commit 26f5fc5
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 10 deletions.
45 changes: 35 additions & 10 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,12 @@ Interface.prototype._insertString = function(c) {
var beg = this.line.slice(0, this.cursor);
var end = this.line.slice(this.cursor, this.line.length);
this.line = beg + c + end;
this.cursor += c.length;
// Count Unicode code points
this.cursor += Array.from(c).length;
this._refreshLine();
} else {
this.line += c;
this.cursor += c.length;
this.cursor += Array.from(c).length;

if (this._getCursorPos().cols === 0) {
this._refreshLine();
Expand Down Expand Up @@ -580,27 +581,37 @@ Interface.prototype._wordLeft = function() {
Interface.prototype._wordRight = function() {
if (this.cursor < this.line.length) {
var trailing = this.line.slice(this.cursor);
var match = trailing.match(/^(?:\s+|\W+|\w+)\s*/);
var match = trailing.match(/^(?:\s+|[^\w\s]+|\w+)\s*/);
this._moveCursor(match[0].length);
}
};


Interface.prototype._deleteLeft = function() {
if (this.cursor > 0 && this.line.length > 0) {
this.line = this.line.slice(0, this.cursor - 1) +
// The Unicode code points prior to the cursor
const leading = Array.from(this.line.slice(0, this.cursor));
// The number of UTF-16 units comprising the character to the left
const charSize = leading[leading.length - 1].length;
this.line = this.line.slice(0, this.cursor - charSize) +
this.line.slice(this.cursor, this.line.length);

this.cursor--;
this.cursor -= charSize;
this._refreshLine();
}
};


Interface.prototype._deleteRight = function() {
this.line = this.line.slice(0, this.cursor) +
this.line.slice(this.cursor + 1, this.line.length);
this._refreshLine();
if (this.cursor < this.line.length) {
// The Unicode code points after the cursor
const ending = Array.from(this.line.slice(this.cursor, this.line.length));
// The number of UTF-16 units comprising the character to the left
const charSize = ending[0].length;
this.line = this.line.slice(0, this.cursor) +
this.line.slice(this.cursor + charSize, this.line.length);
this._refreshLine();
}
};


Expand Down Expand Up @@ -952,11 +963,25 @@ Interface.prototype._ttyWrite = function(s, key) {
break;

case 'left':
this._moveCursor(-1);
if (this.cursor > 0) {
// The Unicode code points prior to the cursor
const leading = Array.from(this.line.slice(0, this.cursor));
// The number of UTF-16 units comprising the character to the left
const charSize = leading[leading.length - 1].length;
this._moveCursor(-charSize);
}
break;

case 'right':
this._moveCursor(+1);
if (this.cursor < this.line.length) {
// The Unicode code points after the cursor
const ending = Array.from(
this.line.slice(this.cursor, this.line.length)
);
// The number of UTF-16 units comprising the character to the left
const charSize = ending[0].length;
this._moveCursor(+charSize);
}
break;

case 'home':
Expand Down
84 changes: 84 additions & 0 deletions test/parallel/test-readline-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,36 @@ function isWarned(emitter) {
rli.close();
}

// Back and Forward one astral character
{
const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
prompt: '',
terminal: terminal
});
fi.emit('data', '💻');

// move left one character/code point
fi.emit('keypress', '.', { name: 'left' });
let cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 0);

// move right one character/code point
fi.emit('keypress', '.', { name: 'right' });
cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 2);

rli.on('line', common.mustCall((line) => {
assert.strictEqual(line, '💻');
}));
fi.emit('data', '\n');
rli.close();
}

{
// `wordLeft` and `wordRight`
const fi = new FakeInput();
Expand Down Expand Up @@ -791,6 +821,32 @@ function isWarned(emitter) {
rli.close();
}

// deleteLeft astral character
{
const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
prompt: '',
terminal: terminal
});
fi.emit('data', '💻');
let cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 2);

// Delete left character
fi.emit('keypress', '.', { ctrl: true, name: 'h' });
cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 0);
rli.on('line', common.mustCall((line) => {
assert.strictEqual(line, '');
}));
fi.emit('data', '\n');
rli.close();
}

// deleteRight
{
const fi = new FakeInput();
Expand Down Expand Up @@ -820,6 +876,34 @@ function isWarned(emitter) {
rli.close();
}

// deleteRight astral character
{
const fi = new FakeInput();
const rli = new readline.Interface({
input: fi,
output: fi,
prompt: '',
terminal: terminal
});
fi.emit('data', '💻');

// Go to the start of the line
fi.emit('keypress', '.', { ctrl: true, name: 'a' });
let cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 0);

// Delete right character
fi.emit('keypress', '.', { ctrl: true, name: 'd' });
cursorPos = rli._getCursorPos();
assert.strictEqual(cursorPos.rows, 0);
assert.strictEqual(cursorPos.cols, 0);
rli.on('line', common.mustCall((line) => {
assert.strictEqual(line, '');
}));
fi.emit('data', '\n');
rli.close();
}

// deleteLineLeft
{
Expand Down

0 comments on commit 26f5fc5

Please sign in to comment.