blob: f338a1311fe3999194f06a291cd1ff4f6f2e8f48 [file] [log] [blame]
/*
* 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",
]);