pretty-repl
Advanced tools
Comparing version 3.0.2 to 3.1.0
@@ -50,4 +50,7 @@ 'use strict'; | ||
} | ||
const colorSheet = sheet(new chalk.Instance({ level })); | ||
return (s) => emphasize.highlight('js', s, colorSheet).value; | ||
const chalkInstance = new chalk.Instance({ level }); | ||
const colorSheet = sheet(chalkInstance); | ||
const highlight = (s) => emphasize.highlight('js', s, colorSheet).value; | ||
highlight.colorizeMatchingBracket = (s) => chalkInstance.bgBlue(s); | ||
return highlight; | ||
}; |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const memoizeStringTransformerMethod = require('./memoize-string-transformer'); | ||
const findAllMatchingBrackets = require('./find-all-matching-brackets'); | ||
const ansiRegex = require('ansi-regex'); | ||
@@ -14,2 +15,6 @@ const stripAnsi = require('strip-ansi'); | ||
// Every open/close pair that should be matched against its counterpart for | ||
// highlighting. | ||
const allBrackets = '()[]{}\'"`$'; | ||
// Compute the length of the longest common prefix of 'before' and 'after', | ||
@@ -57,3 +62,6 @@ // taking ANSI escape sequences into account. For example: | ||
this.colorize = (options && options.colorize) || highlight(options.output); | ||
this.colorizeMatchingBracket = (options && options.colorizeMatchingBracket) || | ||
highlight(options.output).colorizeMatchingBracket; | ||
this.lineBeforeInsert = undefined; | ||
this.highlightBracketPosition = -1; | ||
@@ -67,2 +75,26 @@ // For some reason, tests fail if we don't initialize line to be the empty string. | ||
// If the cursor is moved onto or off a bracket, refresh the whole line so that | ||
// we can mark the matching bracket. | ||
_moveCursor (dx) { | ||
const cursorWasOnBracket = allBrackets.includes(this.line[this.cursor]); | ||
super._moveCursor(dx); | ||
const cursorIsOnBracket = allBrackets.includes(this.line[this.cursor]); | ||
if (cursorWasOnBracket || cursorIsOnBracket) { | ||
this._refreshLine(); | ||
} | ||
} | ||
// When refreshinng the whole line, find matching brackets and keep the position | ||
// of the matching one in mind (if there is any). | ||
_refreshLine () { | ||
try { | ||
if (this.colorizeMatchingBracket && allBrackets.includes(this.line[this.cursor])) { | ||
this.highlightBracketPosition = this._findMatchingBracket(this.line, this.cursor); | ||
} | ||
return super._refreshLine(); | ||
} finally { | ||
this.highlightBracketPosition = -1; | ||
} | ||
} | ||
_writeToOutput (stringToWrite) { | ||
@@ -148,3 +180,18 @@ // Skip false-y values, and if we print only whitespace or have not yet | ||
stringToWrite = stringToWrite.substring(this._prompt.length); | ||
this.output.write(this._prompt + this._doColorize(stringToWrite)); | ||
if (this.highlightBracketPosition !== -1) { | ||
// If there is a matching bracket, we mark it in the string before | ||
// highlighting using BOM characters (because it seems safe to assume | ||
// that they are ignored by highlighting) so that we can remember where | ||
// the bracket was. | ||
stringToWrite = | ||
stringToWrite.substring(0, this.highlightBracketPosition) + | ||
'\ufeff' + stringToWrite[this.highlightBracketPosition] + '\ufeff' + | ||
stringToWrite.substring(this.highlightBracketPosition + 1); | ||
stringToWrite = this._doColorize(stringToWrite); | ||
// Then remove the BOM characters again and colorize the bracket in between. | ||
stringToWrite = stringToWrite.replace(/\ufeff(.+)\ufeff/, (_, bracket) => this.colorizeMatchingBracket(bracket)); | ||
} else { | ||
stringToWrite = this._doColorize(stringToWrite); | ||
} | ||
this.output.write(this._prompt + stringToWrite); | ||
} | ||
@@ -165,43 +212,41 @@ | ||
_stripCompleteJSStructures(str) { | ||
_stripCompleteJSStructures = memoizeStringTransformerMethod(1000, function(str) { | ||
// Remove substructures of the JS input string `str` in order to simplify it, | ||
// by repeatedly removing matching pairs of quotes and parentheses/brackets. | ||
let before; | ||
do { | ||
before = str; | ||
str = this._stripCompleteJSStructuresStep(before); | ||
} while (before !== str); | ||
return str; | ||
} | ||
// by removing matching pairs of quotes and parentheses/brackets. | ||
_stripCompleteJSStructuresStep = memoizeStringTransformerMethod(10000, function(str) { | ||
// This regular expression matches: | ||
// - matching pairs of (), without any of ()[]{}`'" in between | ||
// - matching pairs of [], without any of ()[]{}`'" in between | ||
// - matching pairs of {}, without any of ()[]{}`'" in between | ||
// - matching pairs of '', with only non-'\, \\, and \' preceded by an even number of \ in between | ||
// - matching pairs of "", with only non-"\, \\, and \" preceded by an even number of \ in between | ||
// - matching pairs of ``, with only non-`{}\, \\ and \` preceded by an even number of \ in between | ||
const re = /\([^\(\)\[\]\{\}`'"]*\)|\[[^\(\)\[\]\{\}`'"]*\]|\{[^\(\)\[\]\{\}`'"]*\}|'([^'\\]|(?<=[^\\](\\\\)*)\\'|\\\\)*'|"([^"\\]|(?<=[^\\](\\\\)*)\\"|\\\\)*"|`([^\{\}`\\]|(?<=[^\\](\\\\)*)\\`|\\\\)*`/g; | ||
// Match the regexp against the input. If there are no matches, we can just return. | ||
const matches = [...str.matchAll(re)]; | ||
if (matches.length === 0) { | ||
return str; | ||
} | ||
// Remove all but the last, non-nested pair of (), because () can affect | ||
// whether the previous word is seen as a keyword. | ||
// Specifically, remove all but the last, non-nested pair of (), because () | ||
// can affect whether the previous word is seen as a keyword. | ||
// E.g.: When input is `function() {`, do not replace the (). | ||
// When input is `{ foo(); }`, do replace the `()`, then afterwards the `{ ... }`. | ||
let startsReplaceIndex = matches.length - 1; | ||
const lastMatch = matches[matches.length - 1]; | ||
if (lastMatch[0].startsWith('(') && !str.substr(lastMatch.index + lastMatch[0].length).match(/[\)\]\}`'"]/)) { | ||
startsReplaceIndex--; | ||
const brackets = this._findAllMatchingBracketsIgnoreMismatches(str); | ||
if (brackets.length > 0) { | ||
const last = brackets[brackets.length - 1]; | ||
if (last.kind === '(' && (last.parent == null || last.parent.end === -1)) | ||
brackets.pop(); | ||
} | ||
for (let i = startsReplaceIndex; i >= 0; i--) { | ||
// Replace str1<match>str2 with str1str2. Go backwards so that the match | ||
// indices into the string remain valid. | ||
str = str.substr(0, matches[i].index) + str.substr(matches[i].index + matches[i][0].length); | ||
// Remove brackets in reverse order, so that their indices remain valid. | ||
for (let i = brackets.length - 1; i >= 0; i--) { | ||
str = str.substr(0, brackets[i].start) + str.substr(brackets[i].end + 1); | ||
} | ||
return str; | ||
}); | ||
_findAllMatchingBracketsIgnoreMismatches = memoizeStringTransformerMethod(1000, function(str) { | ||
return findAllMatchingBrackets(str, true); | ||
}); | ||
_findAllMatchingBracketsIncludeMismatches = memoizeStringTransformerMethod(1000, function(str) { | ||
return findAllMatchingBrackets(str, false); | ||
}); | ||
// Find the matching bracket opposite of the one at `position`. | ||
_findMatchingBracket(line, position) { | ||
const brackets = this._findAllMatchingBracketsIncludeMismatches(line); | ||
for (const bracket of brackets) { | ||
if (bracket.start === position) | ||
return bracket.end; | ||
if (bracket.end === position) | ||
return bracket.start; | ||
} | ||
return -1; | ||
} | ||
} | ||
@@ -208,0 +253,0 @@ |
@@ -9,3 +9,3 @@ { | ||
], | ||
"version": "3.0.2", | ||
"version": "3.1.0", | ||
"description": "Extends repl.REPLServer to allow for a colorize function", | ||
@@ -24,3 +24,3 @@ "main": "index.js", | ||
"ansi-regex": "^5.0.0", | ||
"chalk": "^4.0.0", | ||
"chalk": "^4.1.1", | ||
"emphasize": "^3.0.0", | ||
@@ -27,0 +27,0 @@ "strip-ansi": "^6.0.0" |
@@ -42,8 +42,5 @@ # Pretty REPL | ||
## Known issues | ||
In order to highlighting matching pairs of brackets, a `colorizeMatchingBracket` | ||
is also available. | ||
* The implementation in Node.js versions 11 and 12, this module works by monkey-patching the Interface prototype (`readline` module). | ||
If you use `readline` (or a module that depends on it) somewhere else, you may want to test everything thoroughly. In theory, there should be no side effects. | ||
* For Node.js versions older than 11, this module does nothing. | ||
## Credits | ||
@@ -50,0 +47,0 @@ |
@@ -128,2 +128,24 @@ const test = require('tape'); | ||
test('findMatchingBracket', t => { | ||
t.plan(10); | ||
const { stdin } = stdio(); | ||
const output = new PassThrough(); | ||
output.isTTY = true; | ||
output.getColorDepth = () => 8; | ||
const prettyRepl = repl.start({ | ||
input: stdin, | ||
output: output | ||
}); | ||
t.equal(prettyRepl._findMatchingBracket('abc { def }', 4), 10); | ||
t.equal(prettyRepl._findMatchingBracket('abc { def }', 10), 4); | ||
t.equal(prettyRepl._findMatchingBracket('abc {( def }', 4), 11); | ||
t.equal(prettyRepl._findMatchingBracket('abc {( def }', 5), -1); | ||
t.equal(prettyRepl._findMatchingBracket('abc {( def }', 0), -1); | ||
t.equal(prettyRepl._findMatchingBracket('abc {( def }', 11), 4); | ||
t.equal(prettyRepl._findMatchingBracket('"(")', 0), 2); | ||
t.equal(prettyRepl._findMatchingBracket('"(")', 1), -1); | ||
t.equal(prettyRepl._findMatchingBracket('`${foo}`', 1), 6); | ||
t.equal(prettyRepl._findMatchingBracket('(`${")"}`', 0), -1); | ||
}); | ||
test('full pass-through test', t => { | ||
@@ -130,0 +152,0 @@ t.plan(1); |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
126173
13
577
0
50
Updatedchalk@^4.1.1