blob: 1d4769301f5652449a99f394091f47ea007f27f9 [file] [log] [blame]
/*
* Copyright (C) 2015 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.
*/
function createCodeMirrorTextMarkers({codeMirror, range, type, pattern, matchFunction, allowedTokens, callback})
{
var createdMarkers = [];
var start = range instanceof WI.TextRange ? range.startLine : 0;
var end = range instanceof WI.TextRange ? range.endLine + 1 : codeMirror.lineCount();
for (var lineNumber = start; lineNumber < end; ++lineNumber) {
var lineContent = codeMirror.getLine(lineNumber);
var match = pattern.exec(lineContent);
while (match) {
if (typeof matchFunction === "function" && !matchFunction(lineContent, match)) {
match = pattern.exec(lineContent);
continue;
}
var from = {line: lineNumber, ch: match.index};
var to = {line: lineNumber, ch: match.index + match[0].length};
var foundMarker = false;
var markers = codeMirror.findMarksAt(to);
for (var j = 0; j < markers.length; ++j) {
if (WI.TextMarker.textMarkerForCodeMirrorTextMarker(markers[j]).type === WI.TextMarker.Type[type]) {
foundMarker = true;
break;
}
}
if (foundMarker) {
match = pattern.exec(lineContent);
continue;
}
// We're not interested if the value is not inside a keyword.
var tokenType = codeMirror.getTokenTypeAt(from);
if (tokenType && !tokenType.includes("keyword") && (!allowedTokens || !allowedTokens.test(tokenType))) {
match = pattern.exec(lineContent);
continue;
}
let valueString = match[1];
let value = WI[type] ? WI[type].fromString(valueString) : null;
if (WI[type] && !value) {
match = pattern.exec(lineContent);
continue;
}
var marker = codeMirror.markText(from, to);
marker = new WI.TextMarker(marker, WI.TextMarker.Type[type]);
createdMarkers.push(marker);
if (typeof callback === "function")
callback(marker, value, valueString);
match = pattern.exec(lineContent);
}
}
return createdMarkers;
}
function createCodeMirrorColorTextMarkers(codeMirror, range, options = {})
{
// Matches rgba(0, 0, 0, 0.5), rgb(0, 0, 0), hsl(), hsla(), #fff, #ffff, #ffffff, #ffffffff, white
const pattern = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{8}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,4}|\b\w+\b(?![-.]))/g;
function matchFunction(lineContent, match) {
if (!lineContent || !lineContent.length)
return false;
// In order determine if the matched color is inside a gradient, first
// look before the text to find the first unmatched open parenthesis.
// This parenthesis, if it exists, will be immediately after the CSS
// funciton whose name can be checked to see if it matches a gradient.
let openParenthesis = 0;
let index = match.index;
let c = null;
while (c = lineContent[index]) {
if (c === "(")
++openParenthesis;
if (c === ")")
--openParenthesis;
if (openParenthesis > 0)
break;
--index;
if (index < 0)
break;
}
if (/(repeating-)?(linear|radial)-gradient$/.test(lineContent.substring(0, index)))
return false;
// Act as a negative look-behind and disallow the color from being prefixing with certain characters.
return !(match.index > 0 && /[-.\"\']/.test(lineContent[match.index - 1]));
}
return createCodeMirrorTextMarkers({codeMirror, range, type: "Color", pattern, matchFunction, ...options});
}
function createCodeMirrorGradientTextMarkers(codeMirror, range, options = {})
{
var createdMarkers = [];
var start = range instanceof WI.TextRange ? range.startLine : 0;
var end = range instanceof WI.TextRange ? range.endLine + 1 : codeMirror.lineCount();
var gradientRegex = /(repeating-)?(linear|radial)-gradient\s*\(\s*/g;
for (var lineNumber = start; lineNumber < end; ++lineNumber) {
var lineContent = codeMirror.getLine(lineNumber);
var match = gradientRegex.exec(lineContent);
while (match) {
var startLine = lineNumber;
var startChar = match.index;
var endChar = match.index + match[0].length;
var openParentheses = 0;
var c = null;
while (c = lineContent[endChar]) {
if (c === "(")
openParentheses++;
if (c === ")")
openParentheses--;
if (openParentheses === -1) {
endChar++;
break;
}
endChar++;
if (endChar >= lineContent.length) {
lineNumber++;
endChar = 0;
lineContent = codeMirror.getLine(lineNumber);
if (!lineContent)
break;
}
}
if (openParentheses !== -1) {
match = gradientRegex.exec(lineContent);
continue;
}
var from = {line: startLine, ch: startChar};
var to = {line: lineNumber, ch: endChar};
var gradientString = codeMirror.getRange(from, to);
var gradient = WI.Gradient.fromString(gradientString);
if (!gradient) {
match = gradientRegex.exec(lineContent);
continue;
}
var marker = new WI.TextMarker(codeMirror.markText(from, to), WI.TextMarker.Type.Gradient);
createdMarkers.push(marker);
if (typeof callback === "function")
options.callback(marker, gradient, gradientString);
match = gradientRegex.exec(lineContent);
}
}
return createdMarkers;
}
function createCodeMirrorCubicBezierTextMarkers(codeMirror, range, options = {})
{
const pattern = /(cubic-bezier\([^)]+\)|\b\w+\b(?:-\b\w+\b){0,2})/g;
return createCodeMirrorTextMarkers({codeMirror, range, type: "CubicBezier", pattern, ...options});
}
function createCodeMirrorSpringTextMarkers(codeMirror, range, options = {})
{
const pattern = /(spring\([^)]+\))/g;
return createCodeMirrorTextMarkers({codeMirror, range, type: "Spring", pattern, ...options});
}