| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as strings from '../../../../base/common/strings.js';
- import * as stringBuilder from '../../core/stringBuilder.js';
- import { Range } from '../../core/range.js';
- /**
- * Represents a grouping of colliding bracket pairs.
- *
- * Most of the times this contains a single bracket pair,
- * but sometimes this contains multiple bracket pairs in cases
- * where the same string appears as a closing bracket for multiple
- * bracket pairs, or the same string appears an opening bracket for
- * multiple bracket pairs.
- *
- * e.g. of a group containing a single pair:
- * open: ['{'], close: ['}']
- *
- * e.g. of a group containing multiple pairs:
- * open: ['if', 'for'], close: ['end', 'end']
- */
- export class RichEditBracket {
- constructor(languageId, index, open, close, forwardRegex, reversedRegex) {
- this._richEditBracketBrand = undefined;
- this.languageId = languageId;
- this.index = index;
- this.open = open;
- this.close = close;
- this.forwardRegex = forwardRegex;
- this.reversedRegex = reversedRegex;
- this._openSet = RichEditBracket._toSet(this.open);
- this._closeSet = RichEditBracket._toSet(this.close);
- }
- /**
- * Check if the provided `text` is an open bracket in this group.
- */
- isOpen(text) {
- return this._openSet.has(text);
- }
- /**
- * Check if the provided `text` is a close bracket in this group.
- */
- isClose(text) {
- return this._closeSet.has(text);
- }
- static _toSet(arr) {
- const result = new Set();
- for (const element of arr) {
- result.add(element);
- }
- return result;
- }
- }
- /**
- * Groups together brackets that have equal open or close sequences.
- *
- * For example, if the following brackets are defined:
- * ['IF','END']
- * ['for','end']
- * ['{','}']
- *
- * Then the grouped brackets would be:
- * { open: ['if', 'for'], close: ['end', 'end'] }
- * { open: ['{'], close: ['}'] }
- *
- */
- function groupFuzzyBrackets(brackets) {
- const N = brackets.length;
- brackets = brackets.map(b => [b[0].toLowerCase(), b[1].toLowerCase()]);
- const group = [];
- for (let i = 0; i < N; i++) {
- group[i] = i;
- }
- const areOverlapping = (a, b) => {
- const [aOpen, aClose] = a;
- const [bOpen, bClose] = b;
- return (aOpen === bOpen || aOpen === bClose || aClose === bOpen || aClose === bClose);
- };
- const mergeGroups = (g1, g2) => {
- const newG = Math.min(g1, g2);
- const oldG = Math.max(g1, g2);
- for (let i = 0; i < N; i++) {
- if (group[i] === oldG) {
- group[i] = newG;
- }
- }
- };
- // group together brackets that have the same open or the same close sequence
- for (let i = 0; i < N; i++) {
- const a = brackets[i];
- for (let j = i + 1; j < N; j++) {
- const b = brackets[j];
- if (areOverlapping(a, b)) {
- mergeGroups(group[i], group[j]);
- }
- }
- }
- const result = [];
- for (let g = 0; g < N; g++) {
- const currentOpen = [];
- const currentClose = [];
- for (let i = 0; i < N; i++) {
- if (group[i] === g) {
- const [open, close] = brackets[i];
- currentOpen.push(open);
- currentClose.push(close);
- }
- }
- if (currentOpen.length > 0) {
- result.push({
- open: currentOpen,
- close: currentClose
- });
- }
- }
- return result;
- }
- export class RichEditBrackets {
- constructor(languageId, _brackets) {
- this._richEditBracketsBrand = undefined;
- const brackets = groupFuzzyBrackets(_brackets);
- this.brackets = brackets.map((b, index) => {
- return new RichEditBracket(languageId, index, b.open, b.close, getRegexForBracketPair(b.open, b.close, brackets, index), getReversedRegexForBracketPair(b.open, b.close, brackets, index));
- });
- this.forwardRegex = getRegexForBrackets(this.brackets);
- this.reversedRegex = getReversedRegexForBrackets(this.brackets);
- this.textIsBracket = {};
- this.textIsOpenBracket = {};
- this.maxBracketLength = 0;
- for (const bracket of this.brackets) {
- for (const open of bracket.open) {
- this.textIsBracket[open] = bracket;
- this.textIsOpenBracket[open] = true;
- this.maxBracketLength = Math.max(this.maxBracketLength, open.length);
- }
- for (const close of bracket.close) {
- this.textIsBracket[close] = bracket;
- this.textIsOpenBracket[close] = false;
- this.maxBracketLength = Math.max(this.maxBracketLength, close.length);
- }
- }
- }
- }
- function collectSuperstrings(str, brackets, currentIndex, dest) {
- for (let i = 0, len = brackets.length; i < len; i++) {
- if (i === currentIndex) {
- continue;
- }
- const bracket = brackets[i];
- for (const open of bracket.open) {
- if (open.indexOf(str) >= 0) {
- dest.push(open);
- }
- }
- for (const close of bracket.close) {
- if (close.indexOf(str) >= 0) {
- dest.push(close);
- }
- }
- }
- }
- function lengthcmp(a, b) {
- return a.length - b.length;
- }
- function unique(arr) {
- if (arr.length <= 1) {
- return arr;
- }
- const result = [];
- const seen = new Set();
- for (const element of arr) {
- if (seen.has(element)) {
- continue;
- }
- result.push(element);
- seen.add(element);
- }
- return result;
- }
- /**
- * Create a regular expression that can be used to search forward in a piece of text
- * for a group of bracket pairs. But this regex must be built in a way in which
- * it is aware of the other bracket pairs defined for the language.
- *
- * For example, if a language contains the following bracket pairs:
- * ['begin', 'end']
- * ['if', 'end if']
- * The two bracket pairs do not collide because no open or close brackets are equal.
- * So the function getRegexForBracketPair is called twice, once with
- * the ['begin'], ['end'] group consisting of one bracket pair, and once with
- * the ['if'], ['end if'] group consiting of the other bracket pair.
- *
- * But there could be a situation where an occurrence of 'end if' is mistaken
- * for an occurrence of 'end'.
- *
- * Therefore, for the bracket pair ['begin', 'end'], the regex will also
- * target 'end if'. The regex will be something like:
- * /(\bend if\b)|(\bend\b)|(\bif\b)/
- *
- * The regex also searches for "superstrings" (other brackets that might be mistaken with the current bracket).
- *
- */
- function getRegexForBracketPair(open, close, brackets, currentIndex) {
- // search in all brackets for other brackets that are a superstring of these brackets
- let pieces = [];
- pieces = pieces.concat(open);
- pieces = pieces.concat(close);
- for (let i = 0, len = pieces.length; i < len; i++) {
- collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
- }
- pieces = unique(pieces);
- pieces.sort(lengthcmp);
- pieces.reverse();
- return createBracketOrRegExp(pieces);
- }
- /**
- * Matching a regular expression in JS can only be done "forwards". So JS offers natively only
- * methods to find the first match of a regex in a string. But sometimes, it is useful to
- * find the last match of a regex in a string. For such a situation, a nice solution is to
- * simply reverse the string and then search for a reversed regex.
- *
- * This function also has the fine details of `getRegexForBracketPair`. For the same example
- * given above, the regex produced here would look like:
- * /(\bfi dne\b)|(\bdne\b)|(\bfi\b)/
- */
- function getReversedRegexForBracketPair(open, close, brackets, currentIndex) {
- // search in all brackets for other brackets that are a superstring of these brackets
- let pieces = [];
- pieces = pieces.concat(open);
- pieces = pieces.concat(close);
- for (let i = 0, len = pieces.length; i < len; i++) {
- collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
- }
- pieces = unique(pieces);
- pieces.sort(lengthcmp);
- pieces.reverse();
- return createBracketOrRegExp(pieces.map(toReversedString));
- }
- /**
- * Creates a regular expression that targets all bracket pairs.
- *
- * e.g. for the bracket pairs:
- * ['{','}']
- * ['begin,'end']
- * ['for','end']
- * the regex would look like:
- * /(\{)|(\})|(\bbegin\b)|(\bend\b)|(\bfor\b)/
- */
- function getRegexForBrackets(brackets) {
- let pieces = [];
- for (const bracket of brackets) {
- for (const open of bracket.open) {
- pieces.push(open);
- }
- for (const close of bracket.close) {
- pieces.push(close);
- }
- }
- pieces = unique(pieces);
- return createBracketOrRegExp(pieces);
- }
- /**
- * Matching a regular expression in JS can only be done "forwards". So JS offers natively only
- * methods to find the first match of a regex in a string. But sometimes, it is useful to
- * find the last match of a regex in a string. For such a situation, a nice solution is to
- * simply reverse the string and then search for a reversed regex.
- *
- * e.g. for the bracket pairs:
- * ['{','}']
- * ['begin,'end']
- * ['for','end']
- * the regex would look like:
- * /(\{)|(\})|(\bnigeb\b)|(\bdne\b)|(\brof\b)/
- */
- function getReversedRegexForBrackets(brackets) {
- let pieces = [];
- for (const bracket of brackets) {
- for (const open of bracket.open) {
- pieces.push(open);
- }
- for (const close of bracket.close) {
- pieces.push(close);
- }
- }
- pieces = unique(pieces);
- return createBracketOrRegExp(pieces.map(toReversedString));
- }
- function prepareBracketForRegExp(str) {
- // This bracket pair uses letters like e.g. "begin" - "end"
- const insertWordBoundaries = (/^[\w ]+$/.test(str));
- str = strings.escapeRegExpCharacters(str);
- return (insertWordBoundaries ? `\\b${str}\\b` : str);
- }
- function createBracketOrRegExp(pieces) {
- const regexStr = `(${pieces.map(prepareBracketForRegExp).join(')|(')})`;
- return strings.createRegExp(regexStr, true);
- }
- const toReversedString = (function () {
- function reverse(str) {
- if (stringBuilder.hasTextDecoder) {
- // create a Uint16Array and then use a TextDecoder to create a string
- const arr = new Uint16Array(str.length);
- let offset = 0;
- for (let i = str.length - 1; i >= 0; i--) {
- arr[offset++] = str.charCodeAt(i);
- }
- return stringBuilder.getPlatformTextDecoder().decode(arr);
- }
- else {
- const result = [];
- let resultLen = 0;
- for (let i = str.length - 1; i >= 0; i--) {
- result[resultLen++] = str.charAt(i);
- }
- return result.join('');
- }
- }
- let lastInput = null;
- let lastOutput = null;
- return function toReversedString(str) {
- if (lastInput !== str) {
- lastInput = str;
- lastOutput = reverse(lastInput);
- }
- return lastOutput;
- };
- })();
- export class BracketsUtils {
- static _findPrevBracketInText(reversedBracketRegex, lineNumber, reversedText, offset) {
- const m = reversedText.match(reversedBracketRegex);
- if (!m) {
- return null;
- }
- const matchOffset = reversedText.length - (m.index || 0);
- const matchLength = m[0].length;
- const absoluteMatchOffset = offset + matchOffset;
- return new Range(lineNumber, absoluteMatchOffset - matchLength + 1, lineNumber, absoluteMatchOffset + 1);
- }
- static findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, startOffset, endOffset) {
- // Because JS does not support backwards regex search, we search forwards in a reversed string with a reversed regex ;)
- const reversedLineText = toReversedString(lineText);
- const reversedSubstr = reversedLineText.substring(lineText.length - endOffset, lineText.length - startOffset);
- return this._findPrevBracketInText(reversedBracketRegex, lineNumber, reversedSubstr, startOffset);
- }
- static findNextBracketInText(bracketRegex, lineNumber, text, offset) {
- const m = text.match(bracketRegex);
- if (!m) {
- return null;
- }
- const matchOffset = m.index || 0;
- const matchLength = m[0].length;
- if (matchLength === 0) {
- return null;
- }
- const absoluteMatchOffset = offset + matchOffset;
- return new Range(lineNumber, absoluteMatchOffset + 1, lineNumber, absoluteMatchOffset + 1 + matchLength);
- }
- static findNextBracketInRange(bracketRegex, lineNumber, lineText, startOffset, endOffset) {
- const substr = lineText.substring(startOffset, endOffset);
- return this.findNextBracketInText(bracketRegex, lineNumber, substr, startOffset);
- }
- }
|