Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

readline: make tab size configurable #31318

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/api/readline.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
16 changes: 12 additions & 4 deletions lib/readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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);
BridgeAR marked this conversation as resolved.
Show resolved Hide resolved
this.tabSize = input.tabSize;
}
removeHistoryDuplicates = input.removeHistoryDuplicates;
if (input.prompt !== undefined) {
prompt = input.prompt;
Expand Down Expand Up @@ -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);
BridgeAR marked this conversation as resolved.
Show resolved Hide resolved
continue;
}
const width = getStringWidth(char);
Expand Down
52 changes: 52 additions & 0 deletions test/parallel/test-readline-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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({
Expand Down