Skip to content

feat: add pager to allow scrolling of long outputs in the REPL #2162

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

Merged
merged 44 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e4c1a43
feat: add pager to allow scrolling of long outputs
Snehil-Shah Apr 14, 2024
28a9304
refactor: remove setting and default
kgryte Apr 20, 2024
11a3b70
refactor: support programmatic disabling and enabling
kgryte Apr 20, 2024
1fa14b6
fix: add missing types
kgryte Apr 21, 2024
8c588ba
fix: avoid clashing with existing TAB completion behavior
kgryte Apr 21, 2024
fd3e109
refactor: rename parameter and internal variable
kgryte Apr 21, 2024
45e0660
refactor: add methods for programmatically toggling behavior
kgryte Apr 21, 2024
18bedb7
fix: ensure booleans are always returned
kgryte Apr 21, 2024
c672f48
refactor: implement paging as a transform stream
kgryte Apr 21, 2024
262131d
Merge branch 'develop' of https://github.com/stdlib-js/stdlib into pr…
kgryte Apr 21, 2024
5e02a44
fix: update API calls
kgryte Apr 21, 2024
08f25c7
refactor: close output stream on REPL close and address failing tests
kgryte Apr 21, 2024
a7446a3
refactor: move logic to private methods
kgryte Apr 21, 2024
b059a94
refactor: reintroduce setting
kgryte Apr 21, 2024
919db02
docs: document setting
kgryte Apr 21, 2024
44a8e87
refactor: add `pager` command and fix settings handling
kgryte Apr 21, 2024
c18cd25
fix: update paging check and allow paging bypass
kgryte Apr 21, 2024
4e3f5d9
fix: update conditionals
kgryte Apr 21, 2024
04f6291
refactor: apply De Morgan's law
kgryte Apr 21, 2024
556690e
refactor: batch write the pager content
kgryte Apr 21, 2024
cffb6ce
refactor: replace loops with calls to `substring`
kgryte Apr 21, 2024
530f0f8
refactor: guard against index increment errors
kgryte Apr 21, 2024
da40034
refactor: minimize the number of ops between clearing screen and disp…
kgryte Apr 21, 2024
3915bd8
fix: disable auto-paging during tests
kgryte Apr 21, 2024
ee971ca
refactor: add support for detecting terminal resize events
kgryte Apr 21, 2024
5be95e3
style: use consistent spacing
kgryte Apr 21, 2024
b19355b
fix: directly write msg to output to avoid triggering pager
Snehil-Shah Apr 22, 2024
e4914e6
fix: avoid suppressing SIGINT interrupts
Snehil-Shah Apr 22, 2024
2bea814
fix: avoid paging if viewport height too low
Snehil-Shah Apr 22, 2024
6a422fa
fix: exit pager if viewport is resized to smaller than minimum
Snehil-Shah Apr 22, 2024
4b4430f
docs: add TODO concerning implementation robustness
kgryte Apr 22, 2024
ee0be20
fix: pager not displaying the last line
Snehil-Shah Apr 22, 2024
3ab38a3
test: add tests for pager
Snehil-Shah Apr 22, 2024
7ee0b92
style: change variable case
Snehil-Shah Apr 23, 2024
1acac0c
docs: add comment
kgryte Apr 24, 2024
ebfbe26
docs: update comments
kgryte Apr 24, 2024
978190c
Merge branch 'develop' of https://github.com/stdlib-js/stdlib into pr…
kgryte Apr 24, 2024
eb9cdff
test: refactor test fixture option handling and update test fixture l…
kgryte Apr 24, 2024
849bb48
style: re-add line to be consistent with other functions
kgryte Apr 24, 2024
683d390
docs: indicate that options are optional
kgryte Apr 25, 2024
95c544f
refactor: avoid repetition
kgryte Apr 25, 2024
78a7e34
style: group operands to visually reinforce operator precedence
kgryte Apr 25, 2024
b69c99a
refactor: simplify height determination
kgryte Apr 25, 2024
6b14914
test: update descriptions
kgryte Apr 25, 2024
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
1 change: 1 addition & 0 deletions lib/node_modules/@stdlib/repl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ The function supports specifying the following settings:

- **autoClosePairs**: boolean indicating whether to automatically insert matching brackets, parentheses, and quotes. Default: `true`.
- **autoDeletePairs**: boolean indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes. Default: `true`.
- **autoPage**: boolean indicating whether to automatically page return values having a display size exceeding the visible screen. When streams are TTY, the default is `true`; otherwise, the default is `false`.
- **completionPreviews**: boolean indicating whether to display completion previews for auto-completion. When streams are TTY, the default is `true`; otherwise, the default is `false`.

#### REPL.prototype.createContext()
Expand Down
70 changes: 68 additions & 2 deletions lib/node_modules/@stdlib/repl/lib/auto_close_pairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,19 @@ function isQuote( ch ) {
* @private
* @constructor
* @param {Object} rli - readline instance
* @param {boolean} autoClose - boolean indicating whether auto-closing should be initially enabled
* @param {boolean} autoDelete - boolean indicating whether auto-deleting should be initially enabled
* @returns {AutoCloser} auto-closer instance
*/
function AutoCloser( rli ) {
function AutoCloser( rli, autoClose, autoDelete ) {
if ( !(this instanceof AutoCloser) ) {
return new AutoCloser( rli );
return new AutoCloser( rli, autoClose, autoDelete );
}
debug( 'Creating an auto-closer...' );
this._rli = rli;
this._ignoreBackspace = false;
this._autoClose = autoClose;
this._autoDelete = autoDelete;
return this;
}

Expand Down Expand Up @@ -239,6 +243,62 @@ setNonEnumerableReadOnly( AutoCloser.prototype, '_autodeleteOpenSymbol', functio
return true;
});

/**
* Disables auto-closing pairs.
*
* @name disableAutoClose
* @memberof AutoCloser.prototype
* @type {Function}
* @returns {AutoCloser} auto-close instance
*/
setNonEnumerableReadOnly( AutoCloser.prototype, 'disableAutoClose', function disableAutoClose() {
debug( 'Disabling auto-closing pairs...' );
this._autoClose = false;
return this;
});

/**
* Enables auto-closing pairs.
*
* @name enableAutoClose
* @memberof AutoCloser.prototype
* @type {Function}
* @returns {AutoCloser} auto-close instance
*/
setNonEnumerableReadOnly( AutoCloser.prototype, 'enableAutoClose', function enableAutoClose() {
debug( 'Enabling auto-closing pairs...' );
this._autoClose = true;
return this;
});

/**
* Disables auto-deleting pairs.
*
* @name disableAutoDelete
* @memberof AutoCloser.prototype
* @type {Function}
* @returns {AutoCloser} auto-close instance
*/
setNonEnumerableReadOnly( AutoCloser.prototype, 'disableAutoDelete', function disableAutoDelete() {
debug( 'Disabling auto-deleting pairs...' );
this._autoDelete = false;
return this;
});

/**
* Enables auto-deleting pairs.
*
* @name enableAutoDelete
* @memberof AutoCloser.prototype
* @type {Function}
* @returns {AutoCloser} auto-close instance
*/
setNonEnumerableReadOnly( AutoCloser.prototype, 'enableAutoDelete', function enableAutoDelete() {
debug( 'Enabling auto-deleting pairs...' );
this._autoDelete = true;
return this;
});

/**
* Callback which should be invoked **before** a "keypress" event is processed by a readline interface.
*
Expand All @@ -253,6 +313,9 @@ setNonEnumerableReadOnly( AutoCloser.prototype, 'beforeKeypress', function befor
var cursor;
var line;

if ( !this._autoDelete ) {
return false;
}
if ( !key || key.name !== 'backspace' ) {
return false;
}
Expand Down Expand Up @@ -294,6 +357,9 @@ setNonEnumerableReadOnly( AutoCloser.prototype, 'onKeypress', function onKeypres
var cursor;
var line;

if ( !this._autoClose ) {
return false;
}
cursor = this._rli.cursor;
line = this._rli.line;

Expand Down
2 changes: 2 additions & 0 deletions lib/node_modules/@stdlib/repl/lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var isKeyword = require( './commands/is_keyword.js' );
var onLicense = require( './commands/license_text.js' );
var onLoad = require( './commands/load.js' );
var onLoadWorkspace = require( './commands/load_workspace.js' );
var onPager = require( './commands/pager.js' );
var onPresentationStart = require( './commands/presentation_start.js' );
var onPresentationStop = require( './commands/presentation_stop.js' );
var onQuit = require( './commands/quit.js' );
Expand Down Expand Up @@ -116,6 +117,7 @@ function commands( repl ) {
cmds.push( [ 'license', onLicense( repl ), false ] );
cmds.push( [ 'load', onLoad( repl ), false ] );
cmds.push( [ 'loadWorkspace', onLoadWorkspace( repl ), false ] );
cmds.push( [ 'pager', onPager( repl ), false ] );
cmds.push( [ 'presentationStart', onPresentationStart( repl ), false ] );
cmds.push( [ 'presentationStop', onPresentationStop( repl ), false ] );
cmds.push( [ 'quit', onQuit( repl ), false ] );
Expand Down
64 changes: 64 additions & 0 deletions lib/node_modules/@stdlib/repl/lib/commands/pager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license Apache-2.0
*
* Copyright (c) 2024 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/* eslint-disable no-underscore-dangle */

'use strict';

// MAIN //

/**
* Returns a callback to be invoked upon calling the `pager` command.
*
* @private
* @param {REPL} repl - REPL instance
* @returns {Function} callback
*/
function command( repl ) {
return onCommand;

/**
* Enables paging for a provided string.
*
* @private
* @param {string} value - input string
*/
function onCommand( value ) {
var ostream = repl._ostream;

// Check whether auto-paging is already enabled...
if ( repl.settings( 'autoPage' ) ) {
// Nothing needed here, as we can defer to already enabled behavior:
ostream.write( value );
return;
}
// Temporarily enable paging:
ostream.enablePaging();

// Write the input value:
ostream.write( value );

// Disable paging:
ostream.disablePaging();
}
}


// EXPORTS //

module.exports = command;
56 changes: 54 additions & 2 deletions lib/node_modules/@stdlib/repl/lib/completer_preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@ var debug = logger( 'repl:completer:preview' );
* @param {Object} rli - readline instance
* @param {Function} completer - function for generating possible completions
* @param {WritableStream} ostream - writable stream
* @param {boolean} enabled - boolean indicating whether the completer should be initially enabled
* @returns {PreviewCompleter} completer instance
*/
function PreviewCompleter( rli, completer, ostream ) {
function PreviewCompleter( rli, completer, ostream, enabled ) {
if ( !(this instanceof PreviewCompleter) ) {
return new PreviewCompleter( rli, completer, ostream );
return new PreviewCompleter( rli, completer, ostream, enabled );
}
debug( 'Creating a preview completer...' );

// Initialize a flag indicating whether the preview completer is enabled:
this._enabled = enabled;

// Cache a reference to the provided readline interface:
this._rli = rli;

Expand All @@ -76,6 +80,7 @@ function PreviewCompleter( rli, completer, ostream ) {
* @private
* @name _completionCallback
* @memberof PreviewCompleter.prototype
* @type {Function}
* @returns {Function} completion callback
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', function completionCallback() {
Expand Down Expand Up @@ -143,12 +148,16 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun
*
* @name clear
* @memberof PreviewCompleter.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() {
var preview;
var N;

if ( !this._enabled ) {
return;
}
preview = this._preview;

// If no preview currently displayed, nothing to clear...
Expand All @@ -173,16 +182,51 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear()
this._preview = '';
});

/**
* Disables the preview completer.
*
* @name disable
* @memberof PreviewCompleter.prototype
* @type {Function}
* @returns {PreviewCompleter} completer instance
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'disable', function disable() {
this.clear();

debug( 'Disabling the preview completer...' );
this._enabled = false;

return this;
});

/**
* Enables the preview completer.
*
* @name enable
* @memberof PreviewCompleter.prototype
* @type {Function}
* @returns {PreviewCompleter} completer instance
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'enable', function enable() {
debug( 'Enabling the preview completer...' );
this._enabled = true;
return this;
});

/**
* Callback for handling a "keypress" event.
*
* @name onKeypress
* @memberof PreviewCompleter.prototype
* @type {Function}
* @param {string} data - input data
* @param {(Object|void)} key - key object
* @returns {void}
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress() {
if ( !this._enabled ) {
return;
}
// Check for existing content beyond the cursor which could "collide" with a preview completion...
if ( /[^a-zA-Z0-9_$]/.test( this._rli.line.substring( this._rli.cursor ) ) ) { // FIXME: this is not robust (see https://mathiasbynens.be/notes/javascript-identifiers)
return;
Expand All @@ -199,14 +243,22 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onK
*
* @name beforeKeypress
* @memberof PreviewCompleter.prototype
* @type {Function}
* @param {string} data - input data
* @param {(Object|void)} key - key object
* @returns {void}
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function beforeKeypress( data, key ) {
if ( !this._enabled ) {
return;
}
if ( !key || this._preview === '' ) {
return;
}
// Avoid clashing with existing TAB completion behavior...
if ( key.name === 'tab' ) {
return this.clear();
}
// Handle the case where the user is not at the end of the line...
if ( this._rli.cursor !== this._rli.line.length ) {
// If a user is in the middle of a line and presses ENTER, clear the preview string, as the preview was not accepted prior to executing the expression...
Expand Down
3 changes: 3 additions & 0 deletions lib/node_modules/@stdlib/repl/lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ function defaults() {
// Flag indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes:
'autoDeletePairs': true,

// Flag indicating whether to enable automatically page return values requiring a display size exceeding the visible screen (note: default depends on whether TTY):
'autoPage': void 0,

// Flag indicating whether to enable the display of completion previews for auto-completion (note: default depends on whether TTY):
'completionPreviews': void 0
}
Expand Down
4 changes: 4 additions & 0 deletions lib/node_modules/@stdlib/repl/lib/display_prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ function displayPrompt( repl, preserveCursor ) {
var re;
var ws;

// Avoid displaying a prompt if the REPL is currently in paging mode...
if ( repl._ostream.isPaging ) {
return;
}
len = repl._cmd.length;
if ( len === 0 ) {
if ( repl._padding && repl._count >= 0 ) {
Expand Down
Loading