265 lines
7.1 KiB
JavaScript
265 lines
7.1 KiB
JavaScript
/*
|
|
* 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",
|
|
]);
|