/* * Copyright (C) 2010 Nikita Vasilyev. All rights reserved. * Copyright (C) 2010 Joseph Pecoraro. All rights reserved. * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ WI.CSSCompletions = class CSSCompletions { constructor(values, {acceptEmptyPrefix} = {}) { console.assert(Array.isArray(values), values); console.assert(!values.length || typeof values[0] === "string", "Expected an array of string values or an empty array", values); this._values = values.slice(); this._values.sort(); this._acceptEmptyPrefix = !!acceptEmptyPrefix; this._queryController = null; } // Static static completeUnbalancedValue(value) { const State = { Data: 0, SingleQuoteString: 1, DoubleQuoteString: 2, Comment: 3 }; let state = State.Data; let unclosedParenthesisCount = 0; let trailingBackslash = false; let length = value.length; for (let i = 0; i < length; ++i) { switch (value[i]) { case "'": if (state === State.Data) state = State.SingleQuoteString; else if (state === State.SingleQuoteString) state = State.Data; break; case "\"": if (state === State.Data) state = State.DoubleQuoteString; else if (state === State.DoubleQuoteString) state = State.Data; break; case "(": if (state === State.Data) ++unclosedParenthesisCount; break; case ")": if (state === State.Data && unclosedParenthesisCount) --unclosedParenthesisCount; break; case "/": if (state === State.Data) { if (value[i + 1] === "*") state = State.Comment; } break; case "\\": if (i === length - 1) trailingBackslash = true; else ++i; // Skip next character. break; case "*": if (state === State.Comment) { if (value[i + 1] === "/") state = State.Data; } break; } } let suffix = ""; if (trailingBackslash) suffix += "\\"; switch (state) { case State.SingleQuoteString: suffix += "'"; break; case State.DoubleQuoteString: suffix += "\""; break; case State.Comment: suffix += "*/"; break; } suffix += ")".repeat(unclosedParenthesisCount); return suffix; } static getCompletionText(completion) { console.assert(typeof completion === "string" || completion instanceof WI.QueryResult, completion); if (typeof completion === "string") return completion; if (completion instanceof WI.QueryResult) return completion.value; return ""; } // Public get values() { return this._values; } executeQuery(query) { if (!query) return this._acceptEmptyPrefix ? this._values.slice() : []; this._queryController ||= new WI.CSSQueryController(this._values); return this._queryController.executeQuery(query); } startsWith(prefix) { if (!prefix) return this._acceptEmptyPrefix ? this._values.slice() : []; let firstIndex = this._firstIndexOfPrefix(prefix); if (firstIndex === -1) return []; let results = []; while (firstIndex < this._values.length && this._values[firstIndex].startsWith(prefix)) results.push(this._values[firstIndex++]); return results; } // Protected replaceValues(values) { console.assert(Array.isArray(values), values); console.assert(typeof values[0] === "string", "Expect an array of string values", values); this._values = values; this._values.sort(); this._queryController?.reset(); this._queryController?.addValues(values); } // Private _firstIndexOfPrefix(prefix) { if (!this._values.length) return -1; if (!prefix) return this._acceptEmptyPrefix ? 0 : -1; var maxIndex = this._values.length - 1; var minIndex = 0; var foundIndex; do { var middleIndex = (maxIndex + minIndex) >> 1; if (this._values[middleIndex].startsWith(prefix)) { foundIndex = middleIndex; break; } if (this._values[middleIndex] < prefix) minIndex = middleIndex + 1; else maxIndex = middleIndex - 1; } while (minIndex <= maxIndex); if (foundIndex === undefined) return -1; while (foundIndex && this._values[foundIndex - 1].startsWith(prefix)) foundIndex--; return foundIndex; } }; WI.CSSCompletions.lengthUnits = new Set([ "ch", "cm", "dvb", "dvh", "dvi", "dvmax", "dvmin", "dvw", "em", "ex", "in", "lvb", "lvh", "lvi", "lvmax", "lvmin", "lvw", "mm", "pc", "pt", "px", "q", "rem", "svb", "svh", "svi", "svmax", "svmin", "svw", "vb", "vh", "vi", "vmax", "vmin", "vw", ]);