| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 12bb03b commit fdef072
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -847,6 +847,11 @@ stream.write('With ES6'); | |||
| 847 | 847 | <!-- YAML | |
| 848 | 848 | added: v0.3.0 | |
| 849 | 849 | changes: | |
| 850 | + - version: | ||
| 851 | + - REPLACEME | ||
| 852 | + pr-url: https://github.com/nodejs/node/pull/59710 | ||
| 853 | + description: The util.inspect.styles.regexp style is now a method that is | ||
| 854 | + invoked for coloring the stringified regular expression. | ||
| 850 | 855 | - version: | |
| 851 | 856 | - v17.3.0 | |
| 852 | 857 | - v16.14.0 | |
@@ -1297,7 +1302,12 @@ The default styles and associated colors are: | |||
| 1297 | 1302 | * `name`: (no styling) | |
| 1298 | 1303 | * `null`: `bold` | |
| 1299 | 1304 | * `number`: `yellow` | |
| 1300 | - * `regexp`: `red` | ||
| 1305 | + * `regexp`: A method that colors character classes, groups, assertions, and | ||
| 1306 | + other parts for improved readability. To customize the coloring, change the | ||
| 1307 | + `colors` property. It is set to | ||
| 1308 | + `['red', 'green', 'yellow', 'cyan', 'magenta']` by default and may be | ||
| 1309 | + adjusted as needed. The array is repetitively iterated through depending on | ||
| 1310 | + the "depth". | ||
| 1301 | 1311 | * `special`: `cyan` (e.g., `Proxies`) | |
| 1302 | 1312 | * `string`: `green` | |
| 1303 | 1313 | * `symbol`: `green` | |
@@ -1309,6 +1319,17 @@ terminals. To verify color support use [`tty.hasColors()`][]. | |||
| 1309 | 1319 | Predefined control codes are listed below (grouped as "Modifiers", "Foreground | |
| 1310 | 1320 | colors", and "Background colors"). | |
| 1311 | 1321 | ||
| 1322 | + #### Complex custom coloring | ||
| 1323 | + | ||
| 1324 | + It is possible to define a method as style. It receives the stringified value | ||
| 1325 | + of the input. It is invoked in case coloring is active and the type is | ||
| 1326 | + inspected. | ||
| 1327 | + | ||
| 1328 | + Example: `util.inspect.styles.regexp(value)` | ||
| 1329 | + | ||
| 1330 | + * `value` {string} The string representation of the input type. | ||
| 1331 | + * Returns: {string} The adjusted representation of `object`. | ||
| 1332 | + | ||
| 1312 | 1333 | #### Modifiers | |
| 1313 | 1334 | ||
| 1314 | 1335 | Modifier support varies throughout different terminals. They will mostly be | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -502,7 +502,6 @@ defineColorAlias('inverse', 'swapColors'); | |||
| 502 | 502 | defineColorAlias('inverse', 'swapcolors'); | |
| 503 | 503 | defineColorAlias('doubleunderline', 'doubleUnderline'); | |
| 504 | 504 | ||
| 505 | - // TODO(BridgeAR): Add function style support for more complex styles. | ||
| 506 | 505 | // Don't use 'blue' not visible on cmd.exe | |
| 507 | 506 | inspect.styles = ObjectAssign({ __proto__: null }, { | |
| 508 | 507 | special: 'cyan', | |
@@ -515,11 +514,259 @@ inspect.styles = ObjectAssign({ __proto__: null }, { | |||
| 515 | 514 | symbol: 'green', | |
| 516 | 515 | date: 'magenta', | |
| 517 | 516 | // "name": intentionally not styling | |
| 518 | - // TODO(BridgeAR): Highlight regular expressions properly. | ||
| 519 | - regexp: 'red', | ||
| 517 | + regexp: highlightRegExp, | ||
| 520 | 518 | module: 'underline', | |
| 521 | 519 | }); | |
| 522 | 520 | ||
| 521 | + // Define the palette for RegExp group depth highlighting. Can be changed by users. | ||
| 522 | + inspect.styles.regexp.colors = ['green', 'red', 'yellow', 'cyan', 'magenta']; | ||
| 523 | + | ||
| 524 | + const highlightRegExpColors = inspect.styles.regexp.colors.slice(); | ||
| 525 | + | ||
| 526 | + /** | ||
| 527 | + * Colorize a JavaScript RegExp pattern per ECMAScript grammar. | ||
| 528 | + * This is a tolerant single-pass highlighter using heuristics in some cases. | ||
| 529 | + * It supports: groups (named/unnamed, lookaround), assertions, alternation, | ||
| 530 | + * quantifiers, escapes (incl. Unicode properties), character classes and | ||
| 531 | + * backreferences. | ||
| 532 | + * @param {string} regexpString | ||
| 533 | + * @returns {string} | ||
| 534 | + */ | ||
| 535 | + function highlightRegExp(regexpString) { | ||
| 536 | + let out = ''; | ||
| 537 | + let i = 0; | ||
| 538 | + let depth = 0; | ||
| 539 | + let inClass = false; | ||
| 540 | + | ||
| 541 | + // TODO(BridgeAR): Add group type tracking. That allows to increase the depth | ||
| 542 | + // in case the same type is next to each other. | ||
| 543 | + // let groupType = 0; | ||
| 544 | + | ||
| 545 | + // Verify palette and update cache if user changed colors | ||
| 546 | + const paletteNames = highlightRegExp.colors?.length > 0 ? | ||
| 547 | + highlightRegExp.colors : | ||
| 548 | + highlightRegExpColors; | ||
| 549 | + | ||
| 550 | + const palette = paletteNames.reduce((acc, name) => { | ||
| 551 | + const color = inspect.colors[name]; | ||
| 552 | + if (color) acc.push([`\u001b[${color[0]}m`, `\u001b[${color[1]}m`]); | ||
| 553 | + return acc; | ||
| 554 | + }, []); | ||
| 555 | + | ||
| 556 | + function writeGroup(start, end, decreaseDepth = 1) { | ||
| 557 | + let seq = ''; | ||
| 558 | + i++; | ||
| 559 | + // Only checking for the closing delimiter is a fast heuristic for regular | ||
| 560 | + // expressions without the u or v flag. A safer check would verify that the | ||
| 561 | + // read characters are all alphanumeric. | ||
| 562 | + while (i < regexpString.length && regexpString[i] !== end) { | ||
| 563 | + seq += regexpString[i++]; | ||
| 564 | + } | ||
| 565 | + if (i < regexpString.length) { | ||
| 566 | + depth -= decreaseDepth; | ||
| 567 | + write(start); | ||
| 568 | + writeDepth(seq, 1, 1); | ||
| 569 | + write(end); | ||
| 570 | + depth += decreaseDepth; | ||
| 571 | + } else { | ||
| 572 | + // The group is not closed which would lead to mistakes in the output. | ||
| 573 | + // This is a workaround to prevent output from being corrupted. | ||
| 574 | + writeDepth(start, 1, -seq.length); | ||
| 575 | + } | ||
| 576 | + } | ||
| 577 | + | ||
| 578 | + const write = (str) => { | ||
| 579 | + const idx = depth % palette.length; | ||
| 580 | + // Safeguard against bugs in the implementation. | ||
| 581 | + const color = palette[idx] ?? palette[0]; | ||
| 582 | + out += color[0] + str + color[1]; | ||
| 583 | + return idx; | ||
| 584 | + }; | ||
| 585 | + | ||
| 586 | + function writeDepth(str, incDepth, incI) { | ||
| 587 | + depth += incDepth; | ||
| 588 | + write(str); | ||
| 589 | + depth -= incDepth; | ||
| 590 | + i += incI; | ||
| 591 | + } | ||
| 592 | + | ||
| 593 | + // Opening '/' | ||
| 594 | + write('/'); | ||
| 595 | + depth++; | ||
| 596 | + i = 1; | ||
| 597 | + | ||
| 598 | + // Parse pattern until next unescaped '/' | ||
| 599 | + while (i < regexpString.length) { | ||
| 600 | + const ch = regexpString[i]; | ||
| 601 | + | ||
| 602 | + if (inClass) { | ||
| 603 | + if (ch === '\\') { | ||
| 604 | + let seq = '\\'; | ||
| 605 | + i++; | ||
| 606 | + if (i < regexpString.length) { | ||
| 607 | + seq += regexpString[i++]; | ||
| 608 | + const next = seq[1]; | ||
| 609 | + if (next === 'u' && regexpString[i] === '{') { | ||
| 610 | + writeGroup(`${seq}{`, '}', 0); | ||
| 611 | + continue; | ||
| 612 | + } else if ((next === 'p' || next === 'P') && regexpString[i] === '{') { | ||
| 613 | + writeGroup(`${seq}{`, '}', 0); | ||
| 614 | + continue; | ||
| 615 | + } else if (seq[1] === 'x') { | ||
| 616 | + seq += regexpString.slice(i, i + 2); | ||
| 617 | + i += 2; | ||
| 618 | + } | ||
| 619 | + } | ||
| 620 | + write(seq); | ||
| 621 | + } else if (ch === ']') { | ||
| 622 | + depth--; | ||
| 623 | + write(']'); | ||
| 624 | + i++; | ||
| 625 | + inClass = false; | ||
| 626 | + } else if (ch === '-' && regexpString[i - 1] !== '[' && | ||
| 627 | + i + 1 < regexpString.length && regexpString[i + 1] !== ']') { | ||
| 628 | + writeDepth('-', 1, 1); | ||
| 629 | + } else { | ||
| 630 | + write(ch); | ||
| 631 | + i++; | ||
| 632 | + } | ||
| 633 | + } else if (ch === '[') { | ||
| 634 | + // Enter class | ||
| 635 | + write('['); | ||
| 636 | + depth++; | ||
| 637 | + i++; | ||
| 638 | + inClass = true; | ||
| 639 | + } else if (ch === '(') { | ||
| 640 | + write('('); | ||
| 641 | + depth++; | ||
| 642 | + i++; | ||
| 643 | + if (i < regexpString.length && regexpString[i] === '?') { | ||
| 644 | + // Assertions and named groups | ||
| 645 | + i++; | ||
| 646 | + const a = i < regexpString.length ? regexpString[i] : ''; | ||
| 647 | + if (a === ':' || a === '=' || a === '!') { | ||
| 648 | + writeDepth(`?${a}`, -1, 1); | ||
| 649 | + } else { | ||
| 650 | + const b = i + 1 < regexpString.length ? regexpString[i + 1] : ''; | ||
| 651 | + if (a === '<' && (b === '=' || b === '!')) { | ||
| 652 | + writeDepth(`?<${b}`, -1, 2); | ||
| 653 | + } else if (a === '<') { | ||
| 654 | + // Named capture: write '?<name>' as a single colored token | ||
| 655 | + i++; // consume '<' | ||
| 656 | + const start = i; | ||
| 657 | + while (i < regexpString.length && regexpString[i] !== '>') { | ||
| 658 | + i++; | ||
| 659 | + } | ||
| 660 | + const name = regexpString.slice(start, i); | ||
| 661 | + if (i < regexpString.length && regexpString[i] === '>') { | ||
| 662 | + depth--; | ||
| 663 | + write('?<'); | ||
| 664 | + writeDepth(name, 1, 0); | ||
| 665 | + write('>'); | ||
| 666 | + depth++; | ||
| 667 | + i++; | ||
| 668 | + } else { | ||
| 669 | + writeDepth('?<', -1, 0); | ||
| 670 | + write(name); | ||
| 671 | + } | ||
| 672 | + } else { | ||
| 673 | + write('?'); | ||
| 674 | + } | ||
| 675 | + } | ||
| 676 | + } | ||
| 677 | + } else if (ch === ')') { | ||
| 678 | + depth--; | ||
| 679 | + write(')'); | ||
| 680 | + i++; | ||
| 681 | + } else if (ch === '\\') { | ||
| 682 | + let seq = '\\'; | ||
| 683 | + i++; | ||
| 684 | + if (i < regexpString.length) { | ||
| 685 | + seq += regexpString[i++]; | ||
| 686 | + const next = seq[1]; | ||
| 687 | + if (i < regexpString.length) { | ||
| 688 | + if (next === 'u' && regexpString[i] === '{') { | ||
| 689 | + writeGroup(`${seq}{`, '}', 0); | ||
| 690 | + continue; | ||
| 691 | + } else if (next === 'x') { | ||
| 692 | + seq += regexpString.slice(i, i + 2); | ||
| 693 | + i += 2; | ||
| 694 | + } else if (next >= '0' && next <= '9') { | ||
| 695 | + while (i < regexpString.length && regexpString[i] >= '0' && regexpString[i] <= '9') { | ||
| 696 | + seq += regexpString[i++]; | ||
| 697 | + } | ||
| 698 | + } else if (next === 'k' && regexpString[i] === '<') { | ||
| 699 | + writeGroup(`${seq}<`, '>'); | ||
| 700 | + continue; | ||
| 701 | + } else if ((next === 'p' || next === 'P') && regexpString[i] === '{') { | ||
| 702 | + // Unicode properties | ||
| 703 | + writeGroup(`${seq}{`, '}', 0); | ||
| 704 | + continue; | ||
| 705 | + } | ||
| 706 | + } | ||
| 707 | + } | ||
| 708 | + writeDepth(seq, 1, 0); | ||
| 709 | + } else if (ch === '|' || ch === '+' || ch === '*' || ch === '?' || ch === ',' || ch === '^' || ch === '$') { | ||
| 710 | + writeDepth(ch, 3, 1); | ||
| 711 | + } else if (ch === '{') { | ||
| 712 | + i++; | ||
| 713 | + let digits = ''; | ||
| 714 | + while (i < regexpString.length && regexpString[i] >= '0' && regexpString[i] <= '9') { | ||
| 715 | + digits += regexpString[i++]; | ||
| 716 | + } | ||
| 717 | + if (digits) { | ||
| 718 | + write('{'); | ||
| 719 | + depth++; | ||
| 720 | + writeDepth(digits, 1, 0); | ||
| 721 | + } | ||
| 722 | + if (i < regexpString.length) { | ||
| 723 | + if (regexpString[i] === ',') { | ||
| 724 | + if (!digits) { | ||
| 725 | + write('{'); | ||
| 726 | + depth++; | ||
| 727 | + } | ||
| 728 | + write(','); | ||
| 729 | + i++; | ||
| 730 | + } else if (!digits) { | ||
| 731 | + depth += 1; | ||
| 732 | + write('{'); | ||
| 733 | + depth -= 1; | ||
| 734 | + continue; | ||
| 735 | + } | ||
| 736 | + } | ||
| 737 | + let digits2 = ''; | ||
| 738 | + while (i < regexpString.length && regexpString[i] >= '0' && regexpString[i] <= '9') { | ||
| 739 | + digits2 += regexpString[i++]; | ||
| 740 | + } | ||
| 741 | + if (digits2) { | ||
| 742 | + writeDepth(digits2, 1, 0); | ||
| 743 | + } | ||
| 744 | + if (i < regexpString.length && regexpString[i] === '}') { | ||
| 745 | + depth--; | ||
| 746 | + write('}'); | ||
| 747 | + i++; | ||
| 748 | + } | ||
| 749 | + if (i < regexpString.length && regexpString[i] === '?') { | ||
| 750 | + writeDepth('?', 3, 1); | ||
| 751 | + } | ||
| 752 | + } else if (ch === '.') { | ||
| 753 | + writeDepth(ch, 2, 1); | ||
| 754 | + } else if (ch === '/') { | ||
| 755 | + // Stop at closing delimiter (unescaped, outside of character class) | ||
| 756 | + break; | ||
| 757 | + } else { | ||
| 758 | + writeDepth(ch, 1, 1); | ||
| 759 | + } | ||
| 760 | + } | ||
| 761 | + | ||
| 762 | + // Closing delimiter and flags | ||
| 763 | + writeDepth('/', -1, 1); | ||
| 764 | + if (i < regexpString.length) { | ||
| 765 | + write(regexpString.slice(i)); | ||
| 766 | + } | ||
| 767 | + return out; | ||
| 768 | + } | ||
| 769 | + | ||
| 523 | 770 | function addQuotes(str, quotes) { | |
| 524 | 771 | if (quotes === -1) { | |
| 525 | 772 | return `"${str}"`; | |
@@ -606,8 +853,12 @@ function stylizeWithColor(str, styleType) { | |||
| 606 | 853 | const style = inspect.styles[styleType]; | |
| 607 | 854 | if (style !== undefined) { | |
| 608 | 855 | const color = inspect.colors[style]; | |
| 609 | - if (color !== undefined) | ||
| 856 | + if (color !== undefined) { | ||
| 610 | 857 | return `\u001b[${color[0]}m${str}\u001b[${color[1]}m`; | |
| 858 | + } | ||
| 859 | + if (typeof style === 'function') { | ||
| 860 | + return style(str); | ||
| 861 | + } | ||
| 611 | 862 | } | |
| 612 | 863 | return str; | |
| 613 | 864 | } | |
@@ -1048,9 +1299,10 @@ function formatRaw(ctx, value, recurseTimes, typedArray) { | |||
| 1048 | 1299 | const prefix = getPrefix(constructor, tag, 'RegExp'); | |
| 1049 | 1300 | if (prefix !== 'RegExp ') | |
| 1050 | 1301 | base = `${prefix}${base}`; | |
| 1302 | + base = ctx.stylize(base, 'regexp'); | ||
| 1051 | 1303 | if ((keys.length === 0 && protoProps === undefined) || | |
| 1052 | 1304 | (recurseTimes > ctx.depth && ctx.depth !== null)) { | |
| 1053 | - return ctx.stylize(base, 'regexp'); | ||
| 1305 | + return base; | ||
| 1054 | 1306 | } | |
| 1055 | 1307 | } else if (isDate(value)) { | |
| 1056 | 1308 | // Make dates with properties first say the date | |
| Back | FazBrowse Home | New Git URL |
0 commit comments