diff --git a/addons/middle-click-popup/WorkspaceQuerier.js b/addons/middle-click-popup/WorkspaceQuerier.js index 8351a12f6f..16500fb2b7 100644 --- a/addons/middle-click-popup/WorkspaceQuerier.js +++ b/addons/middle-click-popup/WorkspaceQuerier.js @@ -154,11 +154,13 @@ class TokenProvider { * Return the tokens found by this token provider in `query` at character `idx`. * @param {QueryInfo} query The query to search * @param {number} idx The index to start the search at + * @param {number} depth The number of blocks this token is inside of. + * For the query 1 + 1, the `+` block token would have a depth of 0 and the `1` tokens would have a depth of 1. * @yields {Token} All the tokens found * @abstract */ // eslint-disable-next-line require-yield - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { throw new Error("Sub-class must override abstract method."); } } @@ -181,9 +183,9 @@ class TokenProviderOptional extends TokenProvider { this.inner = inner; } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { yield TokenTypeBlank.INSTANCE.createToken(idx); - yield* this.inner.parseTokens(query, idx); + yield* this.inner.parseTokens(query, idx, depth); } } @@ -207,9 +209,9 @@ class TokenProviderSingleCache extends TokenProvider { } } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { if (!this.inner.shouldCache) { - yield* this.inner.parseTokens(); + yield* this.inner.parseTokens(query, idx, depth); return; } if (this.cacheQueryID !== query.id) { @@ -222,7 +224,7 @@ class TokenProviderSingleCache extends TokenProvider { return; } this.cache[idx] = cacheEntry = []; - for (const token of this.inner.parseTokens(query, idx)) { + for (const token of this.inner.parseTokens(query, idx, depth)) { cacheEntry.push(token); yield token; } @@ -272,10 +274,10 @@ class TokenProviderGroup extends TokenProvider { else this.illegalProviders.push(...providers); } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { // If none of our providers are cacheable, just parse all the tokens again if (!this.hasCacheable) { - for (const provider of this.providers) yield* provider.parseTokens(query, idx, false); + for (const provider of this.providers) yield* provider.parseTokens(query, idx, depth); return; } @@ -293,7 +295,7 @@ class TokenProviderGroup extends TokenProvider { for (let i = 0; i < tokenCaches.length; i++) { const tokenCache = tokenCaches[i]; const providerCache = providerCaches[i]; - for (const provider of providerCache) yield* provider.parseTokens(query, idx, false); + for (const provider of providerCache) yield* provider.parseTokens(query, idx, depth); yield* tokenCache; } return; @@ -312,7 +314,7 @@ class TokenProviderGroup extends TokenProvider { for (const provider of this.providers) { if (provider.shouldCache) { - for (const token of provider.parseTokens(query, idx, false)) { + for (const token of provider.parseTokens(query, idx, depth)) { tokenCache.push(token); yield token; } @@ -324,11 +326,11 @@ class TokenProviderGroup extends TokenProvider { providerCaches.push(providerCache); } providerCache.push(provider); - yield* provider.parseTokens(query, idx, false); + yield* provider.parseTokens(query, idx, depth); } } for (const provider of this.illegalProviders) { - for (let token of provider.parseTokens(query, idx, false)) { + for (let token of provider.parseTokens(query, idx, depth)) { token = { ...token, isLegal: false }; tokenCache.push(token); yield token; @@ -403,7 +405,7 @@ class TokenTypeBlank extends TokenType { this.isConstant = true; } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { yield this.createToken(idx); } @@ -464,7 +466,7 @@ class TokenTypeStringEnum extends TokenType { } } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { for (let valueIdx = 0; valueIdx < this.values.length; valueIdx++) { const valueInfo = this.values[valueIdx]; let yieldedToken = false; @@ -520,7 +522,7 @@ class TokenTypeStringLiteral extends TokenType { * Hello World for 10 seconds', but that's just the price we pay for trying to enumerate every * interpretation. */ - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { // First, look for strings in quotes let quoteEnd = -1; if (query.str[idx] === '"' || query.str[idx] === "'") { @@ -568,7 +570,7 @@ class TokenTypeNumberLiteral extends TokenType { return !isNaN(+str) || !isNaN(parseFloat(+str)); } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { for (let i = idx; i <= query.length; i++) { if (TokenTypeStringLiteral.isTerminator(query.str[i]) && i !== idx) { const value = query.str.substring(idx, i); @@ -592,7 +594,7 @@ class TokenTypeColor extends TokenType { static INSTANCE = new TokenProviderOptional(new TokenTypeColor()); static HEX_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]; - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { if (!query.str.startsWith("#", idx)) return; for (let i = 0; i < 6; i++) { if (TokenTypeColor.HEX_CHARS.indexOf(query.lowercase[idx + i + 1]) === -1) return; @@ -618,11 +620,11 @@ class TokenTypeBrackets extends TokenType { this.tokenProvider = tokenProvider; } - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { const start = idx; if (query.str[idx++] !== "(") return; idx = query.skipIgnorable(idx); - for (const token of this.tokenProvider.parseTokens(query, idx)) { + for (const token of this.tokenProvider.parseTokens(query, idx, depth)) { if (token.type instanceof TokenTypeBlank) continue; // Do not allow empty brackets like '()' var tokenEnd = query.skipIgnorable(token.end); let isTruncated = token.isTruncated; @@ -750,12 +752,14 @@ class TokenTypeBlock extends TokenType { } /** - * * @param {QueryInfo} query - * @param {*} idx + * @param {number} idx + * @param {number} depth * @returns */ - *parseTokens(query, idx) { + *parseTokens(query, idx, depth) { + if (depth !== 0 && !query.canCreateMoreNestedBlocks()) return; + let yieldedTokens = false; for (const subtokens of this._parseSubtokens(query, idx, this.fullTokenProviders)) { @@ -849,17 +853,20 @@ class TokenTypeBlock extends TokenType { * @param {QueryInfo} query * @param {number} idx * @param {TokenProvider[]} subtokenProviders + * @param {number} depth * @param {number} tokenProviderIdx - * @param {boolean} parseSubSubTokens + * @param {boolean} parseNextToken * @yields {Token[]} */ - *_parseSubtokens(query, idx, subtokenProviders, tokenProviderIdx = 0, parseSubSubTokens = true) { + *_parseSubtokens(query, idx, subtokenProviders, depth, tokenProviderIdx = 0, parseNextToken = true) { idx = query.skipIgnorable(idx); let tokenProvider = subtokenProviders[tokenProviderIdx]; - for (const token of tokenProvider.parseTokens(query, idx)) { + for (const token of tokenProvider.parseTokens(query, idx, depth + 1)) { ++query.tokenCount; + if (!query.canCreateMoreTokens()) break; + if (depth !== 0 && !query.canCreateMoreNestedBlocks()) break; if (this.block.precedence !== -1) { if ( @@ -883,13 +890,14 @@ class TokenTypeBlock extends TokenType { } } - if (!parseSubSubTokens || !token.isLegal || tokenProviderIdx === subtokenProviders.length - 1) { + if (!parseNextToken || !token.isLegal || tokenProviderIdx === subtokenProviders.length - 1) { yield [token]; } else { for (const subTokenArr of this._parseSubtokens( query, token.end, subtokenProviders, + depth, tokenProviderIdx + 1, !token.isTruncated )) { @@ -1069,8 +1077,10 @@ class QueryInfo { this.lowercase = this.str.toLowerCase(); /** @type {number} A unique identifier for this query */ this.id = id; - /** @type{number} The number of tokens we've found so far */ + /** @type {number} The number of tokens we've found so far */ this.tokenCount = 0; + /** @type {number} The number of query results we've found so far */ + this.resultCount = 0; } /** @@ -1117,6 +1127,10 @@ class QueryInfo { canCreateMoreTokens() { return this.tokenCount < WorkspaceQuerier.MAX_TOKENS; } + + canCreateMoreNestedBlocks() { + return this.canCreateMoreTokens() && this.resultCount < WorkspaceQuerier.MAX_RESULTS; + } } /** @@ -1143,7 +1157,7 @@ export default class WorkspaceQuerier { ]; /** - * The maximum number of results to find before giving up. + * The maximum number of results to find before we give up searching sub-blocks. */ static MAX_RESULTS = 1000; @@ -1180,7 +1194,7 @@ export default class WorkspaceQuerier { let bestIllegalResult = null; let bestIllegalResultText = ""; - for (const option of this.tokenGroupBlocks.parseTokens(query, 0)) { + for (const option of this.tokenGroupBlocks.parseTokens(query, 0, 0)) { if (option.end >= queryStr.length) { if (option.isLegal) { results.push(new QueryResult(query, option)); @@ -1192,12 +1206,12 @@ export default class WorkspaceQuerier { } } } - ++foundTokenCount; - if (foundTokenCount > WorkspaceQuerier.MAX_RESULTS) { + ++query.resultCount; + if (!limited && query.resultCount >= WorkspaceQuerier.MAX_RESULTS) { console.log("Warning: Workspace query exceeded maximum result count."); limited = true; - break; } + if (!query.canCreateMoreTokens()) { console.log("Warning: Workspace query exceeded maximum token count."); limited = true;