Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 44x 44x 44x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 116x 116x 116x 116x 116x 116x 116x 116x 116x 116x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 316x 316x 316x 316x 316x 316x 316x 267x 267x 267x 49x 49x 49x 52x 5x 5x 5x 44x 44x 44x 44x 44x 44x 44x 44x 52x 44x 52x 44x 316x 44x 44x 44x 44x 44x 44x 44x 44x 44x 44x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 272x 272x 272x 272x 272x 264x 264x 264x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 12x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 52x 52x 52x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 12x 12x 12x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 5011x 5011x 5011x 5011x 5011x 4721x 4721x 350x 5011x 272x 272x 84x 6x 6x 6x 6x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 84x 6x 6x 6x 6x 6x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 5245x 5245x 5245x 5245x 5245x 4929x 4929x 797x 316x 316x 316x 316x 316x 316x 316x 316x 316x 797x 44x 44x 44x 272x 272x 8x 8x 8x 264x 264x 11x 11x 11x 11x 11x 11x | /** * @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-restricted-syntax, no-invalid-this */ 'use strict'; // MODULES // var logger = require( 'debug' ); var parse = require( 'acorn-loose' ).parse; var findNodeAround = require( 'acorn-walk' ).findNodeAround; var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var isString = require( '@stdlib/assert/is-string' ).isPrimitive; var reservedCharsRegExp = require( './regexp_reserved_syntax_characters.js' ); var walk = require( './auto_close_pairs_walk.js' ); var OPEN_SYMBOLS = require( './auto_close_pairs_open_symbols.js' ); var CLOSE_SYMBOLS = require( './auto_close_pairs_close_symbols.js' ); // VARIABLES // var debug = logger( 'repl:auto_close_pairs' ); var AOPTS = { 'ecmaVersion': 'latest' }; // FUNCTIONS // /** * Tests whether a character is a quote character. * * @private * @param {string} ch - input character * @returns {boolean} boolean indicating whether a character is a quote character */ function isQuote( ch ) { return ( ch === '\'' || ch === '"' ); } // MAIN // /** * Constructor for creating an auto-closer. * * @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 * @param {MultilineHandler} multiline - multiline handler instance * @returns {AutoCloser} auto-closer instance */ function AutoCloser( rli, autoClose, autoDelete, multiline ) { if ( !(this instanceof AutoCloser) ) { return new AutoCloser( rli, autoClose, autoDelete, multiline ); } debug( 'Creating an auto-closer...' ); this._rli = rli; this._ignoreBackspace = false; this._autoClose = autoClose; this._autoDelete = autoDelete; this._multiline = multiline; return this; } /** * Processes an opening symbol when performing auto-close. * * @private * @name _autocloseOpenSymbol * @memberof AutoCloser.prototype * @param {string} line - current line content * @param {NonNegativeInteger} cursor - cursor position * @param {string} data - input data * @returns {boolean} boolean indicating whether line content was updated */ setNonEnumerableReadOnly( AutoCloser.prototype, '_autocloseOpenSymbol', function _autocloseOpenSymbol( line, cursor, data ) { var ast; var out; var ch; debug( 'Checking for an opening symbol...' ); ch = OPEN_SYMBOLS[ data ]; if ( !isString( ch ) ) { debug( 'Failed to detect an opening symbol.' ); return false; } debug( 'Detected an opening symbol.' ); // Avoid auto-closing when the input data is already present, and further account for `foo`` being valid syntax (i.e., using untagged template literals as tags); hence, we need to guard against unnecessarily inserting a closing symbol when a user types `foo<|>` and then instinctively types ` in order to close a template literal... if ( data === line[ cursor ] ) { debug( 'Failed to detect an auto-close candidate. Closing symbol is already present.' ); return false; } // Generate an AST for the current line: debug( 'Generating an AST...' ); ast = parse( line, AOPTS ); // Attempt to walk the AST to determine whether to auto-close... try { debug( 'Determining whether to auto-close...' ); out = walk( ast, cursor ); } catch ( err ) { // If parsing failed, stay conservative and don't auto-close: debug( 'Error: %s', err.message ); return false; } // If we failed to conclusively determine whether to auto-close, stay conservative and don't auto-close... if ( !out ) { debug( 'Failed to detect an auto-close candidate. AST parsing did not conclusively support auto-closing.' ); return false; } // If the auto-close candidate is a quote character, avoid auto-closing when the opening character is inserted immediately before characters which could reasonably be considered string characters... if ( isQuote( ch ) && line[ cursor+1 ] && !reservedCharsRegExp().test( line[ cursor+1 ] ) ) { // eslint-disable-line max-len debug( 'Failed to detect an auto-close candidate. Detected a quote character immediately preceding potential character belonging to a string literal.' ); return false; } debug( 'Successfully detected an auto-close candidate. Inserting closing symbol...' ); this._rli.write( ch ); // Move back one character: this._rli.write( null, { 'ctrl': true, 'name': 'b' }); debug( 'Resulting expression: %s', this._rli.line ); return true; }); /** * Processes a closing symbol when performing auto-close. * * @private * @name _autocloseCloseSymbol * @memberof AutoCloser.prototype * @param {string} line - current line content * @param {NonNegativeInteger} cursor - cursor position * @param {string} data - input data * @returns {boolean} boolean indicating whether line content was updated */ setNonEnumerableReadOnly( AutoCloser.prototype, '_autocloseCloseSymbol', function _autocloseCloseSymbol( line, cursor, data ) { var ch; debug( 'Checking for a closing symbol...' ); ch = CLOSE_SYMBOLS[ data ]; if ( !isString( ch ) ) { debug( 'Failed to detect a closing symbol.' ); return false; } debug( 'Detected a closing symbol.' ); // Support users who may instinctively add a closing symbol by skipping over the closing symbol character, and thus avoid inserting an unwanted duplicate character... debug( 'Determining whether a closing symbol already exists...' ); if ( data !== line[ cursor ] ) { // Did not detect a closing symbol to skip over... debug( 'Did not find an existing closing symbol.' ); return false; } // Set an internal flag to avoid having auto-deletion logic consider this backspace to be user input: this._ignoreBackspace = true; debug( 'Closing symbol already exists. Removing duplicate symbol...' ); this._rli.write( null, { 'name': 'backspace' }); // Move to the right one character: this._rli.write( null, { 'name': 'right' }); debug( 'Resulting expression: %s', this._rli.line ); return true; }); /** * Processes an opening symbol when performing auto-delete. * * @private * @name _autocloseOpenSymbol * @memberof AutoCloser.prototype * @param {string} line - current line content * @param {NonNegativeInteger} cursor - cursor position * @param {string} data - character to delete * @returns {boolean} boolean indicating whether line content was updated */ setNonEnumerableReadOnly( AutoCloser.prototype, '_autodeleteOpenSymbol', function _autodeleteOpenSymbol( line, cursor, data ) { var node; var ast; var ch; debug( 'Checking for an opening symbol...' ); ch = OPEN_SYMBOLS[ data ]; if ( !isString( ch ) ) { debug( 'Failed to detect an opening symbol.' ); return false; } debug( 'Detected an opening symbol.' ); debug( 'Checking if immediately followed by a corresponding closing symbol...' ); if ( ch !== line[ cursor ] ) { debug( 'Failed to detect an auto-delete candidate. Opening symbol is not followed by a corresponding closing symbol.' ); return false; } debug( 'Detected a closing symbol.' ); debug( 'Generating an AST...' ); ast = parse( line, AOPTS ); debug( 'Checking whether characters are within a string literal...' ); node = findNodeAround( ast, cursor-1, 'Literal' ); if ( node && node.node.start < cursor-1 ) { debug( 'Failed to detect an auto-delete candidate. Characters are within a string literal.' ); return false; } debug( 'Characters are not within a string literal.' ); debug( 'Successfully detected an auto-delete candidate. Deleting symbols...' ); this._rli.write( null, { 'name': 'right' }); // Set an internal flag to avoid having auto-deletion logic consider this backspace to be user input: this._ignoreBackspace = true; this._rli.write( null, { 'name': 'backspace' }); debug( 'Resulting expression: %s', this._rli.line ); 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. * * @name beforeKeypress * @memberof AutoCloser.prototype * @param {string} data - input data * @param {(Object|void)} key - key object * @returns {boolean} boolean indicating whether line content was updated */ setNonEnumerableReadOnly( AutoCloser.prototype, 'beforeKeypress', function beforeKeypress( data, key ) { var status; var cursor; var line; if ( !this._autoDelete ) { return false; } if ( this._multiline.isPasting() ) { return false; } if ( !key || key.name !== 'backspace' ) { return false; } if ( this._ignoreBackspace ) { // Reset the backspace flag to re-enable auto-deletion behavior: this._ignoreBackspace = false; return false; } cursor = this._rli.cursor; line = this._rli.line; debug( 'Expression: %s', line ); debug( 'Cursor position: %d', cursor ); data = line[ cursor-1 ]; debug( 'Character to delete: %s', data ); debug( 'Performing auto-delete...' ); status = this._autodeleteOpenSymbol( line, cursor, data ); if ( status ) { debug( 'Finished performing auto-delete.' ); return true; } debug( 'Finished performing auto-delete.' ); return false; }); /** * Callback for handling a "keypress" event. * * @name onKeypress * @memberof AutoCloser.prototype * @param {string} data - input data * @param {(Object|void)} key - key object * @returns {boolean} boolean indicating whether line content was updated */ setNonEnumerableReadOnly( AutoCloser.prototype, 'onKeypress', function onKeypress( data ) { var status; var cursor; var line; if ( !this._autoClose ) { return false; } if ( this._multiline.isPasting() ) { return false; } cursor = this._rli.cursor; line = this._rli.line; debug( 'Expression: %s', line ); debug( 'Cursor position: %d', cursor ); debug( 'Character to insert: %s', data ); debug( 'Performing auto-close...' ); status = this._autocloseOpenSymbol( line, cursor, data ); if ( status ) { debug( 'Finished performing auto-close.' ); return true; } status = this._autocloseCloseSymbol( line, cursor, data ); if ( status ) { debug( 'Finished performing auto-close.' ); return true; } debug( 'Finished performing auto-close.' ); return false; }); // EXPORTS // module.exports = AutoCloser; |