Title: [279422] trunk
Revision
279422
Author
[email protected]
Date
2021-06-30 11:14:17 -0700 (Wed, 30 Jun 2021)

Log Message

Web Inspector: Styles: Autocomplete should support function completions
https://bugs.webkit.org/show_bug.cgi?id=227097

Reviewed by Devin Rousso.

Source/WebInspectorUI:

Covered by new test:
- inspector/unit-tests/css-keyword-completions.html

Add support for completing autocompleting values as part of a CSS function in the Styles sidebar panel.

* UserInterface/Models/CSSKeywordCompletions.js:
(WI.CSSKeywordCompletions.forPartialPropertyName):
(WI.CSSKeywordCompletions.forPartialPropertyValue):
Tokenize the provided partial value text to correct provide completion for functions, like `env(`.

* UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype._nameCompletionDataProvider):
(WI.SpreadsheetStyleProperty.prototype._valueCompletionDataProvider):
Moved logic to `WI.CSSKeywordCompletions`.

LayoutTests:

* inspector/unit-tests/css-keyword-completions-expected.txt: Added.
* inspector/unit-tests/css-keyword-completions.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (279421 => 279422)


--- trunk/LayoutTests/ChangeLog	2021-06-30 17:45:06 UTC (rev 279421)
+++ trunk/LayoutTests/ChangeLog	2021-06-30 18:14:17 UTC (rev 279422)
@@ -1,3 +1,13 @@
+2021-06-30  Patrick Angle  <[email protected]>
+
+        Web Inspector: Styles: Autocomplete should support function completions
+        https://bugs.webkit.org/show_bug.cgi?id=227097
+
+        Reviewed by Devin Rousso.
+
+        * inspector/unit-tests/css-keyword-completions-expected.txt: Added.
+        * inspector/unit-tests/css-keyword-completions.html: Added.
+
 2021-06-30  Eric Hutchison  <[email protected]>
 
         [MacOS wk1] fast/shadow-dom/style-resolver-sharing.html is a flaky failure

Added: trunk/LayoutTests/inspector/unit-tests/css-keyword-completions-expected.txt (0 => 279422)


--- trunk/LayoutTests/inspector/unit-tests/css-keyword-completions-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/css-keyword-completions-expected.txt	2021-06-30 18:14:17 UTC (rev 279422)
@@ -0,0 +1,53 @@
+Testing WI.CSSKeywordCompletions.
+
+
+== Running test suite: WI.CSSKeywordCompletions
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyName.emptyTextDisallowsEmptyPrefix
+PASS: Expected exactly 0 completion results.
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyName.emptyTextAllowsEmptyPrefix
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyName.singleCharacterMatches
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyName.multipleCharacterMatches
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyTop
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyColor
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.PartialColor
+PASS: Expected result prefix to be "papaya"
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyWordSpacing
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.MidLineBorder
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.EnvironmentFunctionCompletion
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.MidLineEnvironmentFunction
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.PartialFunctionValue
+PASS: Expected result prefix to be "au"
+PASS: All expected completions were present.
+
+-- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.ComplexMultiValueFunction
+PASS: Expected result prefix to be ""
+PASS: All expected completions were present.
+

Added: trunk/LayoutTests/inspector/unit-tests/css-keyword-completions.html (0 => 279422)


--- trunk/LayoutTests/inspector/unit-tests/css-keyword-completions.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/css-keyword-completions.html	2021-06-30 18:14:17 UTC (rev 279422)
@@ -0,0 +1,186 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createSyncSuite("WI.CSSKeywordCompletions");
+
+    function addTestForPartialPropertyName({name, description, text, allowEmptyPrefix, expectedCompletions, expectedCompletionCount}) {
+        suite.addTestCase({
+            name,
+            description,
+            test() {
+                allowEmptyPrefix ??= false;
+                expectedCompletions ??= [];
+                expectedCompletionCount ??= -1;
+
+                // FIXME: <webkit.org/b/227157> Styles: Support completions mid-token.
+                let caretPosition = text.length;
+                let completionResults = WI.CSSKeywordCompletions.forPartialPropertyName(text, {caretPosition, allowEmptyPrefix});
+
+                if (expectedCompletionCount >= 0)
+                    InspectorTest.expectEqual(completionResults.completions.length, expectedCompletionCount, `Expected exactly ${expectedCompletionCount} completion results.`);
+
+                // Because expected completions could be added at any time, just make sure the list contains our expected completions, instead of enforcing an exact match between expectations and reality.
+                let expectedCompletionsPresent = expectedCompletions.every((expectedCompletion) => {
+                    if (!completionResults.completions.includes(expectedCompletion)) {
+                        InspectorTest.fail(`Expected completion "${expectedCompletion}" in completions.`);
+                        return false;
+                    }
+                    return true;
+                });
+                InspectorTest.expectThat(expectedCompletionsPresent, "All expected completions were present.");
+            }
+        });
+    }
+
+    addTestForPartialPropertyName({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyName.emptyTextDisallowsEmptyPrefix",
+        description: "Test that for empty text, there should be no completions when `allowEmptyPrefix` is `false`.",
+        text: "",
+        allowEmptyPrefix: false,
+        expectedCompletionCount: 0,
+    });
+
+    addTestForPartialPropertyName({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyName.emptyTextAllowsEmptyPrefix",
+        description: "Test that for empty text, there should be completions when `allowEmptyPrefix` is `true`.",
+        text: "",
+        allowEmptyPrefix: true,
+        expectedCompletions: ["border", "color", "margin", "padding"],
+    });
+
+    addTestForPartialPropertyName({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyName.singleCharacterMatches",
+        description: "Test that for a common single character, there will be multiple completions available.",
+        text: "r",
+        expectedCompletions: ["r", "range", "resize", "right", "rotate", "row-gap", "rx", "ry"],
+    });
+
+    addTestForPartialPropertyName({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyName.multipleCharacterMatches",
+        description: "Test that for a more specific set of characters, there will be some number of matches.",
+        text: "rang",
+        expectedCompletions: ["range"],
+    });
+
+    function addTestForPartialPropertyValue({name, description, propertyName, text, caretPosition, expectedPrefix, expectedCompletions}) {
+        suite.addTestCase({
+            name,
+            description,
+            test() {
+                caretPosition ??= text.length;
+                expectedPrefix ??= text;
+                expectedCompletions ??= [];
+
+                let completionResults = WI.CSSKeywordCompletions.forPartialPropertyValue(text, propertyName, {caretPosition});
+                InspectorTest.expectEqual(completionResults.prefix, expectedPrefix, `Expected result prefix to be "${expectedPrefix}"`);
+
+                // Because expected completions could be added at any time, just make sure the list contains our expected completions, instead of enforcing an exact match between expectations and reality.
+                let expectedCompletionsPresent = expectedCompletions.every((expectedCompletion) => {
+                    if (!completionResults.completions.includes(expectedCompletion)) {
+                        InspectorTest.fail(`Expected completion "${expectedCompletion}" in completions.`);
+                        return false;
+                    }
+                    return true;
+                });
+                InspectorTest.expectThat(expectedCompletionsPresent, "All expected completions were present.");
+            }
+        });
+    }
+
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyTop",
+        description: "Test that an empty value provides the expected default completions.",
+        propertyName: "top",
+        text: "",
+        expectedCompletions: ["env()", "initial", "revert", "unset", "var()"],
+    });
+
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyColor",
+        description: "Test that color completions provide the color functions as completions.",
+        propertyName: "color",
+        text: "",
+        expectedCompletions: ["black", "blue", "green", "red", "white", "papayawhip", "color()", "color-contrast()", "color-mix()", "env()", "hsl()", "hsla()", "hwb()", "lab()", "lch()", "rgb()", "rgba()", "var()"],
+    });
+
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.PartialColor",
+        description: "Test that a partial color name that also matches a full color name still provides the other completion.",
+        propertyName: "color",
+        text: "papaya",
+        expectedCompletions: ["papayawhip"],
+    });
+
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.EmptyWordSpacing",
+        description: "Test that the `word-spacing` property provides its expected completions.",
+        propertyName: "word-spacing",
+        text: "",
+        expectedCompletions: ["normal", "calc()"],
+    });
+
+    // `border: 1px | red`
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.MidLineBorder",
+        description: "Test that a completion can be performed between two other values.",
+        propertyName: "border",
+        text: "1px  red",
+        caretPosition: 4,
+        expectedPrefix: "",
+        expectedCompletions: ["solid"],
+    });
+
+    // `top: env(|`
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.EnvironmentFunctionCompletion",
+        description: "Test that a function completion can be performed inside a function without a closing parenthesis.",
+        propertyName: "top",
+        text: "env(",
+        expectedPrefix: "",
+        expectedCompletions: ["safe-area-inset-bottom", "safe-area-inset-left", "safe-area-inset-right", "safe-area-inset-top", "var()"],
+    });
+
+    // `top: env(|)`
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.MidLineEnvironmentFunction",
+        description: " Test that a function completion can be performed inside a function with a closing parenthesis.",
+        propertyName: "top",
+        text: "env()",
+        caretPosition: 4,
+        expectedPrefix: "",
+        expectedCompletions: ["safe-area-inset-bottom", "safe-area-inset-left", "safe-area-inset-right", "safe-area-inset-top", "var()"],
+    });
+
+    // `grid-template-columns: repeat(au|`
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.PartialFunctionValue",
+        description: "Test that completing a partial value inside a function produces expected completions.",
+        propertyName: "grid-template-columns",
+        text: "repeat(au",
+        expectedPrefix: "au",
+        expectedCompletions: ["auto-fill", "auto-fit"],
+    });
+
+    // `grid-template-columns: [linename1] 100px repeat(|, [linename2 linename3] 150px) [linename3] 100px [linename4]`
+    addTestForPartialPropertyValue({
+        name: "WI.CSSKeywordCompletions.forPartialPropertyValue.ComplexMultiValueFunction",
+        description: " Test that performing a completion mid-line inside a function right before a separator character, like a comma, produces expected completions.", 
+        propertyName: "grid-template-columns",
+        text: "[linename1] 100px repeat(, [linename2 linename3] 150px) [linename3] 100px [linename4]",
+        caretPosition: 25,
+        expectedPrefix: "",
+        expectedCompletions: ["auto-fill", "auto-fit", "var()"],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Testing WI.CSSKeywordCompletions.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (279421 => 279422)


--- trunk/Source/WebInspectorUI/ChangeLog	2021-06-30 17:45:06 UTC (rev 279421)
+++ trunk/Source/WebInspectorUI/ChangeLog	2021-06-30 18:14:17 UTC (rev 279422)
@@ -1,3 +1,25 @@
+2021-06-30  Patrick Angle  <[email protected]>
+
+        Web Inspector: Styles: Autocomplete should support function completions
+        https://bugs.webkit.org/show_bug.cgi?id=227097
+
+        Reviewed by Devin Rousso.
+
+        Covered by new test:
+        - inspector/unit-tests/css-keyword-completions.html
+
+        Add support for completing autocompleting values as part of a CSS function in the Styles sidebar panel.
+        
+        * UserInterface/Models/CSSKeywordCompletions.js:
+        (WI.CSSKeywordCompletions.forPartialPropertyName):
+        (WI.CSSKeywordCompletions.forPartialPropertyValue):
+        Tokenize the provided partial value text to correct provide completion for functions, like `env(`.
+
+        * UserInterface/Views/SpreadsheetStyleProperty.js:
+        (WI.SpreadsheetStyleProperty.prototype._nameCompletionDataProvider):
+        (WI.SpreadsheetStyleProperty.prototype._valueCompletionDataProvider):
+        Moved logic to `WI.CSSKeywordCompletions`.
+
 2021-06-25  Patrick Angle  <[email protected]>
 
         Web Inspector: Sources: Scope Chain sidebar panel is stripping repeating whitespace from strings

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js (279421 => 279422)


--- trunk/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js	2021-06-30 17:45:06 UTC (rev 279421)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js	2021-06-30 18:14:17 UTC (rev 279422)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2011 Google Inc. All rights reserved.
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2021 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
@@ -31,6 +31,90 @@
 
 WI.CSSKeywordCompletions = {};
 
+WI.CSSKeywordCompletions.forPartialPropertyName = function(text, {caretPosition, allowEmptyPrefix} = {})
+{
+    caretPosition ??= text.length;
+    allowEmptyPrefix ??= false;
+
+    // FIXME: <webkit.org/b/227157> Styles: Support completions mid-token.
+    if (caretPosition !== caretPosition)
+        return {prefix: text, completions: []};
+
+    if (!text.length && allowEmptyPrefix)
+        return {prefix: text, completions: WI.CSSCompletions.cssNameCompletions.values};
+    return {prefix: text, completions:WI.CSSCompletions.cssNameCompletions.startsWith(text)};
+};
+
+WI.CSSKeywordCompletions.forPartialPropertyValue = function(text, propertyName, {caretPosition} = {})
+{
+    caretPosition ??= text.length;
+
+    console.assert(caretPosition >= 0 && caretPosition <= text.length, text, caretPosition);
+    if (caretPosition < 0 || caretPosition > text.length)
+        return {prefix: "", completions: []};
+
+    if (!text.length)
+        return {prefix: "", completions: WI.CSSKeywordCompletions.forProperty(propertyName).values};
+
+    let tokens = WI.tokenizeCSSValue(text);
+
+    // Find the token that the cursor is either in or at the end of.
+    let indexOfTokenAtCaret = -1;
+    let passedCharacters = 0;
+    for (let i = 0; i < tokens.length; ++i) {
+        passedCharacters += tokens[i].value.length;
+        if (passedCharacters >= caretPosition) {
+            indexOfTokenAtCaret = i;
+            break;
+        }
+    }
+
+    let tokenAtCaret = tokens[indexOfTokenAtCaret];
+    console.assert(tokenAtCaret, text, caretPosition);
+    if (!tokenAtCaret)
+        return {prefix: "", completions: []};
+
+    if (tokenAtCaret.type && /\b(comment|string)\b/.test(tokenAtCaret.type))
+        return {prefix: "", completions: []};
+
+    let currentTokenValue = tokenAtCaret.value.trim();
+    let caretIsInMiddleOfToken = caretPosition !== passedCharacters;
+
+    // FIXME: <webkit.org/b/227157 Styles: Support completions mid-token.
+    // If the cursor was in middle of a token or the next token starts with a valid character for a value, we are effectively mid-token.
+    let tokenAfterCaret = tokens[indexOfTokenAtCaret + 1];
+    if ((caretIsInMiddleOfToken && currentTokenValue.length) || (!caretIsInMiddleOfToken && tokenAfterCaret && /[a-zA-Z0-9-]/.test(tokenAfterCaret.value[0])))
+        return {prefix: "", completions: []};
+
+    // If the current token value is a comma or opening parenthesis, treat it as if we are at the start of a new token.
+    if (currentTokenValue === "(" || currentTokenValue === ",")
+        currentTokenValue = "";
+
+    let functionName = null;
+    let preceedingFunctionDepth = 0;
+    for (let i = indexOfTokenAtCaret; i >= 0; --i) {
+        let value = tokens[i].value;
+
+        // There may be one or more complete functions between the cursor and the current scope's functions name.
+        if (value === ")")
+            ++preceedingFunctionDepth;
+        else if (value === "(") {
+            if (preceedingFunctionDepth)
+                --preceedingFunctionDepth;
+            else {
+                functionName = tokens[i - 1]?.value;
+                break;
+            }
+        }
+    }
+
+    // FIXME: <webkit.org/b/227098> Styles sidebar panel should autocomplete `var()` values.
+    if (functionName)
+        return {prefix: currentTokenValue, completions: WI.CSSKeywordCompletions.forFunction(functionName).startsWith(currentTokenValue)};
+
+    return {prefix: currentTokenValue, completions: WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(currentTokenValue)};
+};
+
 WI.CSSKeywordCompletions.forProperty = function(propertyName)
 {
     let acceptedKeywords = ["initial", "unset", "revert", "var()", "env()"];

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (279421 => 279422)


--- trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2021-06-30 17:45:06 UTC (rev 279421)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js	2021-06-30 18:14:17 UTC (rev 279422)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -886,14 +886,9 @@
         }
     }
 
-    _nameCompletionDataProvider(prefix, options = {})
+    _nameCompletionDataProvider(text, {allowEmptyPrefix} = {})
     {
-        let completions;
-        if (!prefix && options.allowEmptyPrefix)
-            completions = WI.CSSCompletions.cssNameCompletions.values;
-        else
-            completions = WI.CSSCompletions.cssNameCompletions.startsWith(prefix);
-        return {prefix, completions};
+        return WI.CSSKeywordCompletions.forPartialPropertyName(text, {allowEmptyPrefix});
     }
 
     _handleValueBeforeInput(event)
@@ -915,21 +910,11 @@
         this.spreadsheetTextFieldDidCommit(this._valueTextField, {direction: "forward"});
     }
 
-    _valueCompletionDataProvider(prefix)
+    _valueCompletionDataProvider(text, {allowEmptyPrefix} = {})
     {
-        // For "border: 1px so|", we want to suggest "solid" based on "so" prefix.
-        let match = prefix.match(/[a-z0-9()-]+$/i);
-
-        // Clicking on the value of `height: 100%` shouldn't show any completions.
-        if (!match && prefix)
-            return {completions: [], prefix: ""};
-
-        prefix = match ? match[0] : "";
-        let propertyName = this._nameElement.textContent.trim();
-        return {
-            prefix,
-            completions: WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(prefix)
-        };
+        // FIXME: <webkit.org/b/227098> Styles sidebar panel should autocomplete `var()` values.
+        // FIXME: <webkit.org/b/227411> Styles sidebar panel should support midline and multiline completions.
+        return WI.CSSKeywordCompletions.forPartialPropertyValue(text, this._nameElement.textContent.trim());
     }
 
     _setupJumpToSymbol(element)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to