diff --git a/lib/node_modules/@stdlib/repl/lib/output_stream.js b/lib/node_modules/@stdlib/repl/lib/output_stream.js index fc0ab1d4e056..328021c3c659 100644 --- a/lib/node_modules/@stdlib/repl/lib/output_stream.js +++ b/lib/node_modules/@stdlib/repl/lib/output_stream.js @@ -473,6 +473,23 @@ setNonEnumerableReadOnlyAccessor( OutputStream.prototype, 'isPaging', function i return this._isPaging; }); +/** +* Number of columns (i.e., the viewport width). +* +* ## Notes +* +* - A readline interface resolves the terminal width from its output stream in order to determine when a line wraps. As this stream sits between a readline interface and the underlying output stream, we must forward the viewport width; otherwise, a readline interface assumes an infinitely wide terminal and fails to account for wrapped lines when rendering. +* - We resolve `undefined` when a viewport width is unavailable (e.g., when not a TTY), as a readline interface only falls back to assuming an infinitely wide terminal for falsy values. +* +* @name columns +* @memberof OutputStream.prototype +* @type {(integer|void)} +*/ +setNonEnumerableReadOnlyAccessor( OutputStream.prototype, 'columns', function columns() { + var w = this._repl.viewportWidth(); + return ( w > 0 ) ? w : void 0; +}); + /** * Callback which should be invoked **before** a "keypress" event is processed by a readline interface. * @@ -510,6 +527,8 @@ setNonEnumerableReadOnly( OutputStream.prototype, 'beforeKeypress', function bef */ setNonEnumerableReadOnly( OutputStream.prototype, 'onResize', function onResize() { if ( !this._isPaging ) { + // Notify the readline interface in order to allow it to re-render (possibly wrapped) input according to the new viewport width: + this.emit( 'resize' ); return; } if ( !this._isScrollable( this._indices.length ) ) { diff --git a/lib/node_modules/@stdlib/repl/lib/syntax_highlighter.js b/lib/node_modules/@stdlib/repl/lib/syntax_highlighter.js index 0e09f00cb332..110e8a5b3e69 100644 --- a/lib/node_modules/@stdlib/repl/lib/syntax_highlighter.js +++ b/lib/node_modules/@stdlib/repl/lib/syntax_highlighter.js @@ -75,6 +75,39 @@ function removeDuplicateTokens( tokens ) { return out; } +/** +* Resolves the display position of a character offset within a (possibly wrapped) input line. +* +* ## Notes +* +* - The returned row and column offsets are relative to the beginning of the input line (i.e., relative to the first character of the prompt). +* - This mirrors the arithmetic performed by a readline interface when resolving cursor positions, thus ensuring that we remain in agreement with a readline interface when rendering. +* - If a viewport width is unknown (e.g., when the output stream is not a TTY), a line is assumed to never wrap. +* +* @private +* @param {NonNegativeInteger} offset - character offset +* @param {integer} columns - viewport width +* @returns {Object} display position +* +* @example +* var pos = displayPosition( 45, 40 ); +* // returns { 'cols': 5, 'rows': 1 } +*/ +function displayPosition( offset, columns ) { + var cols; + if ( columns <= 0 ) { + return { + 'cols': offset, + 'rows': 0 + }; + } + cols = offset % columns; + return { + 'cols': cols, + 'rows': ( offset-cols ) / columns + }; +} + // MAIN // @@ -313,6 +346,10 @@ setNonEnumerableReadOnly( SyntaxHighlighter.prototype, 'enable', function enable * @returns {void} */ setNonEnumerableReadOnly( SyntaxHighlighter.prototype, 'onKeypress', function onKeypress() { + var cursorPos; + var promptLen; + var columns; + var endPos; var tokens; if ( !this._enabled ) { @@ -345,10 +382,33 @@ setNonEnumerableReadOnly( SyntaxHighlighter.prototype, 'onKeypress', function on } // Replace: debug( 'Replacing current line with the highlighted line...' ); - readline.moveCursor( this._ostream, -1 * this._rli.cursor, 0 ); - readline.clearLine( this._ostream, 1 ); + + // Resolve the viewport width (note: the width is negative when the output stream is not a TTY): + columns = this._repl.viewportWidth(); + promptLen = this._repl.promptLength(); + + // Resolve the display positions of the cursor and of the end of the input line in order to support lines which wrap across multiple rows: + cursorPos = displayPosition( promptLen+this._rli.cursor, columns ); + endPos = displayPosition( promptLen+this._line.length, columns ); + + // Move the cursor to the row on which the prompt is displayed and to the column immediately following the prompt: + readline.moveCursor( this._ostream, 0, -cursorPos.rows ); + readline.cursorTo( this._ostream, promptLen ); + + // Rewrite the line: this._ostream.write( this._highlightedLine ); - readline.moveCursor( this._ostream, this._rli.cursor - this._line.length, 0 ); // eslint-disable-line max-len + + // If the line ends exactly at the end of a row, force the terminal to allocate the next row: + if ( endPos.cols === 0 && endPos.rows > 0 ) { + this._ostream.write( ' ' ); + } + + // Move the cursor back to its original position: + readline.cursorTo( this._ostream, cursorPos.cols ); + if ( cursorPos.rows < endPos.rows ) { + readline.moveCursor( this._ostream, 0, cursorPos.rows-endPos.rows ); + } + this._multilineHandler.updateLine( this._highlightedLine ); // save displayed line }); diff --git a/lib/node_modules/@stdlib/repl/test/integration/test.syntax_highlighting.js b/lib/node_modules/@stdlib/repl/test/integration/test.syntax_highlighting.js index 489b37711b5d..9b74c9ddd50a 100644 --- a/lib/node_modules/@stdlib/repl/test/integration/test.syntax_highlighting.js +++ b/lib/node_modules/@stdlib/repl/test/integration/test.syntax_highlighting.js @@ -128,7 +128,8 @@ function assert( t, fixture, done ) { t.fail( error.message ); return; } - actual = data[ data.length - 1 ]; + // NOTE: after rewriting the highlighted line, the syntax highlighter repositions the cursor (to support wrapped lines), so the highlighted line is the penultimate chunk, followed by a trailing cursor-repositioning escape sequence... + actual = data[ data.length - 2 ]; t.strictEqual( actual, expected, 'returns expected value' ); done(); }