Skip to content

Commit

Permalink
readline: make tab size configurable
Browse files Browse the repository at this point in the history
This adds the `tabSize` option to readline to allow different tab
sizes.

PR-URL: #31318
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Anto Aravinth <anto.aravinth.cse@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
BridgeAR authored and codebytere committed Feb 17, 2020
1 parent 2991e7c commit 148dfde
Showing 3 changed files with 69 additions and 4 deletions.
5 changes: 5 additions & 0 deletions doc/api/readline.md
Original file line number Diff line number Diff line change
@@ -456,6 +456,9 @@ the current position of the cursor down.
<!-- YAML
added: v0.1.98
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/31318
description: The `tabSize` option is supported now.
- version: v8.3.0, 6.11.4
pr-url: https://github.com/nodejs/node/pull/13497
description: Remove max limit of `crlfDelay` option.
@@ -499,6 +502,8 @@ changes:
can both form a complete key sequence using the input read so far and can
take additional input to complete a longer key sequence).
**Default:** `500`.
* `tabSize` {integer} The number of spaces a tab is equal to (minimum 1).
**Default:** `8`.

The `readline.createInterface()` method creates a new `readline.Interface`
instance.
16 changes: 12 additions & 4 deletions lib/readline.js
Original file line number Diff line number Diff line change
@@ -45,7 +45,10 @@ const {
ERR_INVALID_CURSOR_POS,
ERR_INVALID_OPT_VALUE
} = require('internal/errors').codes;
const { validateString } = require('internal/validators');
const {
validateString,
validateUint32,
} = require('internal/validators');
const {
inspect,
getStringWidth,
@@ -105,6 +108,7 @@ function Interface(input, output, completer, terminal) {
this._sawKeyPress = false;
this._previousKey = null;
this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT;
this.tabSize = 8;

EventEmitter.call(this);
let historySize;
@@ -118,6 +122,11 @@ function Interface(input, output, completer, terminal) {
completer = input.completer;
terminal = input.terminal;
historySize = input.historySize;
if (input.tabSize !== undefined) {
const positive = true;
validateUint32(input.tabSize, 'tabSize', positive);
this.tabSize = input.tabSize;
}
removeHistoryDuplicates = input.removeHistoryDuplicates;
if (input.prompt !== undefined) {
prompt = input.prompt;
@@ -718,10 +727,9 @@ Interface.prototype._getDisplayPos = function(str) {
offset = 0;
continue;
}
// Tabs must be aligned by an offset of 8.
// TODO(BridgeAR): Make the tab size configurable.
// Tabs must be aligned by an offset of the tab size.
if (char === '\t') {
offset += 8 - (offset % 8);
offset += this.tabSize - (offset % this.tabSize);
continue;
}
const width = getStringWidth(char);
52 changes: 52 additions & 0 deletions test/parallel/test-readline-interface.js
Original file line number Diff line number Diff line change
@@ -138,6 +138,39 @@ function assertCursorRowsAndCols(rli, rows, cols) {
name: 'RangeError',
code: 'ERR_INVALID_OPT_VALUE'
});

// Check for invalid tab sizes.
assert.throws(
() => new readline.Interface({
input,
tabSize: 0
}),
{
message: 'The value of "tabSize" is out of range. ' +
'It must be >= 1 && < 4294967296. Received 0',
code: 'ERR_OUT_OF_RANGE'
}
);

assert.throws(
() => new readline.Interface({
input,
tabSize: '4'
}),
{ code: 'ERR_INVALID_ARG_TYPE' }
);

assert.throws(
() => new readline.Interface({
input,
tabSize: 4.5
}),
{
code: 'ERR_OUT_OF_RANGE',
message: 'The value of "tabSize" is out of range. ' +
'It must be an integer. Received 4.5'
}
);
}

// Sending a single character with no newline
@@ -632,6 +665,25 @@ function assertCursorRowsAndCols(rli, rows, cols) {
rli.close();
}

// Multi-line input cursor position and long tabs
{
const [rli, fi] = getInterface({ tabSize: 16, terminal: true, prompt: '' });
fi.columns = 10;
fi.emit('data', 'multi-line\ttext \t');
assert.strictEqual(rli.cursor, 17);
assertCursorRowsAndCols(rli, 3, 2);
rli.close();
}

// Check for the default tab size.
{
const [rli, fi] = getInterface({ terminal: true, prompt: '' });
fi.emit('data', 'the quick\tbrown\tfox');
assert.strictEqual(rli.cursor, 19);
// The first tab is 7 spaces long, the second one 3 spaces.
assertCursorRowsAndCols(rli, 0, 27);
}

// Multi-line prompt cursor position
{
const [rli, fi] = getInterface({

0 comments on commit 148dfde

Please sign in to comment.