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 | import * as Platform from '../../core/platform/platform.js';
|
32 |
|
33 | import {SearchMatch} from './ContentProvider.js';
|
34 | import {Text} from './Text.js';
|
35 |
|
36 | export const Utils = {
|
37 |
|
38 |
|
39 | get _keyValueFilterRegex(): RegExp {
|
40 | return /(?:^|\s)(\-)?([\w\-]+):([^\s]+)/;
|
41 | },
|
42 |
|
43 |
|
44 | get _regexFilterRegex(): RegExp {
|
45 | return /(?:^|\s)(\-)?\/([^\s]+)\//;
|
46 | },
|
47 |
|
48 |
|
49 | get _textFilterRegex(): RegExp {
|
50 | return /(?:^|\s)(\-)?([^\s]+)/;
|
51 | },
|
52 |
|
53 |
|
54 | get _SpaceCharRegex(): RegExp {
|
55 | return /\s/;
|
56 | },
|
57 | |
58 |
|
59 |
|
60 |
|
61 |
|
62 | get Indent(): {TwoSpaces: ' ', FourSpaces: ' ', EightSpaces: ' ', TabCharacter: '\t'} {
|
63 | return {TwoSpaces: ' ', FourSpaces: ' ', EightSpaces: ' ', TabCharacter: '\t'};
|
64 | },
|
65 |
|
66 | isStopChar: function(char: string): boolean {
|
67 | return (char > ' ' && char < '0') || (char > '9' && char < 'A') || (char > 'Z' && char < '_') ||
|
68 | (char > '_' && char < 'a') || (char > 'z' && char <= '~');
|
69 | },
|
70 |
|
71 | isWordChar: function(char: string): boolean {
|
72 | return !Utils.isStopChar(char) && !Utils.isSpaceChar(char);
|
73 | },
|
74 |
|
75 | isSpaceChar: function(char: string): boolean {
|
76 | return Utils._SpaceCharRegex.test(char);
|
77 | },
|
78 |
|
79 | isWord: function(word: string): boolean {
|
80 | for (let i = 0; i < word.length; ++i) {
|
81 | if (!Utils.isWordChar(word.charAt(i))) {
|
82 | return false;
|
83 | }
|
84 | }
|
85 | return true;
|
86 | },
|
87 |
|
88 | isOpeningBraceChar: function(char: string): boolean {
|
89 | return char === '(' || char === '{';
|
90 | },
|
91 |
|
92 | isClosingBraceChar: function(char: string): boolean {
|
93 | return char === ')' || char === '}';
|
94 | },
|
95 |
|
96 | isBraceChar: function(char: string): boolean {
|
97 | return Utils.isOpeningBraceChar(char) || Utils.isClosingBraceChar(char);
|
98 | },
|
99 |
|
100 | textToWords: function(text: string, isWordChar: (arg0: string) => boolean, wordCallback: (arg0: string) => void):
|
101 | void {
|
102 | let startWord = -1;
|
103 | for (let i = 0; i < text.length; ++i) {
|
104 | if (!isWordChar(text.charAt(i))) {
|
105 | if (startWord !== -1) {
|
106 | wordCallback(text.substring(startWord, i));
|
107 | }
|
108 | startWord = -1;
|
109 | } else if (startWord === -1) {
|
110 | startWord = i;
|
111 | }
|
112 | }
|
113 | if (startWord !== -1) {
|
114 | wordCallback(text.substring(startWord));
|
115 | }
|
116 | },
|
117 |
|
118 | lineIndent: function(line: string): string {
|
119 | let indentation = 0;
|
120 | while (indentation < line.length && Utils.isSpaceChar(line.charAt(indentation))) {
|
121 | ++indentation;
|
122 | }
|
123 | return line.substr(0, indentation);
|
124 | },
|
125 |
|
126 | isUpperCase: function(text: string): boolean {
|
127 | return text === text.toUpperCase();
|
128 | },
|
129 |
|
130 | isLowerCase: function(text: string): boolean {
|
131 | return text === text.toLowerCase();
|
132 | },
|
133 |
|
134 | splitStringByRegexes(text: string, regexes: RegExp[]): {
|
135 | value: string,
|
136 | position: number,
|
137 | regexIndex: number,
|
138 | captureGroups: Array<string|undefined>,
|
139 | }[] {
|
140 | const matches: {
|
141 | value: string,
|
142 | position: number,
|
143 | regexIndex: number,
|
144 | captureGroups: (string|undefined)[],
|
145 | }[] = [];
|
146 | const globalRegexes: RegExp[] = [];
|
147 | for (let i = 0; i < regexes.length; i++) {
|
148 | const regex = regexes[i];
|
149 | if (!regex.global) {
|
150 | globalRegexes.push(new RegExp(regex.source, regex.flags ? regex.flags + 'g' : 'g'));
|
151 | } else {
|
152 | globalRegexes.push(regex);
|
153 | }
|
154 | }
|
155 | doSplit(text, 0, 0);
|
156 | return matches;
|
157 |
|
158 | function doSplit(text: string, regexIndex: number, startIndex: number): void {
|
159 | if (regexIndex >= globalRegexes.length) {
|
160 |
|
161 | matches.push({value: text, position: startIndex, regexIndex: -1, captureGroups: []});
|
162 | return;
|
163 | }
|
164 | const regex = globalRegexes[regexIndex];
|
165 | let currentIndex = 0;
|
166 | let result;
|
167 | regex.lastIndex = 0;
|
168 | while ((result = regex.exec(text)) !== null) {
|
169 | const stringBeforeMatch = text.substring(currentIndex, result.index);
|
170 | if (stringBeforeMatch) {
|
171 | doSplit(stringBeforeMatch, regexIndex + 1, startIndex + currentIndex);
|
172 | }
|
173 | const match = result[0];
|
174 | matches.push({
|
175 | value: match,
|
176 | position: startIndex + result.index,
|
177 | regexIndex: regexIndex,
|
178 | captureGroups: result.slice(1),
|
179 | });
|
180 | currentIndex = result.index + match.length;
|
181 | }
|
182 | const stringAfterMatches = text.substring(currentIndex);
|
183 | if (stringAfterMatches) {
|
184 | doSplit(stringAfterMatches, regexIndex + 1, startIndex + currentIndex);
|
185 | }
|
186 | }
|
187 | },
|
188 | };
|
189 |
|
190 | export class FilterParser {
|
191 | private readonly keys: string[];
|
192 | constructor(keys: string[]) {
|
193 | this.keys = keys;
|
194 | }
|
195 |
|
196 | static cloneFilter(filter: ParsedFilter): ParsedFilter {
|
197 | return {key: filter.key, text: filter.text, regex: filter.regex, negative: filter.negative};
|
198 | }
|
199 |
|
200 | parse(query: string): ParsedFilter[] {
|
201 | const splitFilters = Utils.splitStringByRegexes(
|
202 | query, [Utils._keyValueFilterRegex, Utils._regexFilterRegex, Utils._textFilterRegex]);
|
203 | const parsedFilters: ParsedFilter[] = [];
|
204 | for (const {regexIndex, captureGroups} of splitFilters) {
|
205 | if (regexIndex === -1) {
|
206 | continue;
|
207 | }
|
208 | if (regexIndex === 0) {
|
209 | const startsWithMinus = captureGroups[0];
|
210 | const parsedKey = captureGroups[1];
|
211 | const parsedValue = captureGroups[2];
|
212 | if (this.keys.indexOf((parsedKey as string)) !== -1) {
|
213 | parsedFilters.push({
|
214 | key: parsedKey,
|
215 | regex: undefined,
|
216 | text: parsedValue,
|
217 | negative: Boolean(startsWithMinus),
|
218 | });
|
219 | } else {
|
220 | parsedFilters.push({
|
221 | key: undefined,
|
222 | regex: undefined,
|
223 | text: `${parsedKey}:${parsedValue}`,
|
224 | negative: Boolean(startsWithMinus),
|
225 | });
|
226 | }
|
227 | } else if (regexIndex === 1) {
|
228 | const startsWithMinus = captureGroups[0];
|
229 | const parsedRegex = captureGroups[1];
|
230 | try {
|
231 | parsedFilters.push({
|
232 | key: undefined,
|
233 | regex: new RegExp((parsedRegex as string), 'i'),
|
234 | text: undefined,
|
235 | negative: Boolean(startsWithMinus),
|
236 | });
|
237 | } catch (e) {
|
238 | parsedFilters.push({
|
239 | key: undefined,
|
240 | regex: undefined,
|
241 | text: `/${parsedRegex}/`,
|
242 | negative: Boolean(startsWithMinus),
|
243 | });
|
244 | }
|
245 | } else if (regexIndex === 2) {
|
246 | const startsWithMinus = captureGroups[0];
|
247 | const parsedText = captureGroups[1];
|
248 | parsedFilters.push({
|
249 | key: undefined,
|
250 | regex: undefined,
|
251 | text: parsedText,
|
252 | negative: Boolean(startsWithMinus),
|
253 | });
|
254 | }
|
255 | }
|
256 | return parsedFilters;
|
257 | }
|
258 | }
|
259 |
|
260 | export class BalancedJSONTokenizer {
|
261 | private readonly callback: (arg0: string) => void;
|
262 | private index: number;
|
263 | private balance: number;
|
264 | private buffer: string;
|
265 | private findMultiple: boolean;
|
266 | private closingDoubleQuoteRegex: RegExp;
|
267 | private lastBalancedIndex?: number;
|
268 | constructor(callback: (arg0: string) => void, findMultiple?: boolean) {
|
269 | this.callback = callback;
|
270 | this.index = 0;
|
271 | this.balance = 0;
|
272 | this.buffer = '';
|
273 | this.findMultiple = findMultiple || false;
|
274 | this.closingDoubleQuoteRegex = /[^\\](?:\\\\)*"/g;
|
275 | }
|
276 |
|
277 | write(chunk: string): boolean {
|
278 | this.buffer += chunk;
|
279 | const lastIndex = this.buffer.length;
|
280 | const buffer = this.buffer;
|
281 | let index;
|
282 | for (index = this.index; index < lastIndex; ++index) {
|
283 | const character = buffer[index];
|
284 | if (character === '"') {
|
285 | this.closingDoubleQuoteRegex.lastIndex = index;
|
286 | if (!this.closingDoubleQuoteRegex.test(buffer)) {
|
287 | break;
|
288 | }
|
289 | index = this.closingDoubleQuoteRegex.lastIndex - 1;
|
290 | } else if (character === '{') {
|
291 | ++this.balance;
|
292 | } else if (character === '}') {
|
293 | --this.balance;
|
294 | if (this.balance < 0) {
|
295 | this.reportBalanced();
|
296 | return false;
|
297 | }
|
298 | if (!this.balance) {
|
299 | this.lastBalancedIndex = index + 1;
|
300 | if (!this.findMultiple) {
|
301 | break;
|
302 | }
|
303 | }
|
304 | } else if (character === ']' && !this.balance) {
|
305 | this.reportBalanced();
|
306 | return false;
|
307 | }
|
308 | }
|
309 | this.index = index;
|
310 | this.reportBalanced();
|
311 | return true;
|
312 | }
|
313 |
|
314 | private reportBalanced(): void {
|
315 | if (!this.lastBalancedIndex) {
|
316 | return;
|
317 | }
|
318 | this.callback(this.buffer.slice(0, this.lastBalancedIndex));
|
319 | this.buffer = this.buffer.slice(this.lastBalancedIndex);
|
320 | this.index -= this.lastBalancedIndex;
|
321 | this.lastBalancedIndex = 0;
|
322 | }
|
323 |
|
324 | remainder(): string {
|
325 | return this.buffer;
|
326 | }
|
327 | }
|
328 |
|
329 | export interface TokenizerFactory {
|
330 |
|
331 |
|
332 | createTokenizer(mimeType: string):
|
333 | (arg0: string, arg1: (arg0: string, arg1: string|null, arg2: number, arg3: number) => void) => void;
|
334 | }
|
335 |
|
336 | export function isMinified(text: string): boolean {
|
337 | const kMaxNonMinifiedLength = 500;
|
338 | let linesToCheck = 10;
|
339 | let lastPosition = 0;
|
340 | do {
|
341 | let eolIndex = text.indexOf('\n', lastPosition);
|
342 | if (eolIndex < 0) {
|
343 | eolIndex = text.length;
|
344 | }
|
345 | if (eolIndex - lastPosition > kMaxNonMinifiedLength && text.substr(lastPosition, 3) !== '//#') {
|
346 | return true;
|
347 | }
|
348 | lastPosition = eolIndex + 1;
|
349 | } while (--linesToCheck >= 0 && lastPosition < text.length);
|
350 |
|
351 |
|
352 | linesToCheck = 10;
|
353 | lastPosition = text.length;
|
354 | do {
|
355 | let eolIndex = text.lastIndexOf('\n', lastPosition);
|
356 | if (eolIndex < 0) {
|
357 | eolIndex = 0;
|
358 | }
|
359 | if (lastPosition - eolIndex > kMaxNonMinifiedLength && text.substr(lastPosition, 3) !== '//#') {
|
360 | return true;
|
361 | }
|
362 | lastPosition = eolIndex - 1;
|
363 | } while (--linesToCheck >= 0 && lastPosition > 0);
|
364 | return false;
|
365 | }
|
366 |
|
367 | export const performSearchInContent = function(
|
368 | content: string, query: string, caseSensitive: boolean, isRegex: boolean): SearchMatch[] {
|
369 | const regex = Platform.StringUtilities.createSearchRegex(query, caseSensitive, isRegex);
|
370 |
|
371 | const text = new Text(content);
|
372 | const result = [];
|
373 | for (let i = 0; i < text.lineCount(); ++i) {
|
374 | const lineContent = text.lineAt(i);
|
375 | regex.lastIndex = 0;
|
376 | const match = regex.exec(lineContent);
|
377 | if (match) {
|
378 | result.push(new SearchMatch(i, lineContent, match.index));
|
379 | }
|
380 | }
|
381 | return result;
|
382 | };
|
383 | export interface ParsedFilter {
|
384 | key?: string;
|
385 | text?: string|null;
|
386 | regex?: RegExp;
|
387 | negative: boolean;
|
388 | }
|