Skip to content
Permalink
Browse files
Web Inspector: CSS autocomplete: most commonly used property should a…
…ccount for fuzzy-matching

https://bugs.webkit.org/show_bug.cgi?id=242354

Reviewed by Patrick Angle.

If the `completions` are all `WI.QueryResult, only use `WI.CSSProperty.sortByPropertyNameUsageCount`
on items that have the highest `rank` (e.g. if the developer has typed `min-h` then `min-height`
should have a higher rank than `min-width`, and therefore only the former should care about its
usage count).

* Source/WebInspectorUI/UserInterface/Models/CSSProperty.js:
(WI.CSSProperty.indexOfCompletionForMostUsedPropertyName): Added.
* Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype.spreadsheetTextFieldInitialCompletionIndex):
* Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js:
(WI.SpreadsheetTextField.prototype._updateCompletions):

* Source/WebInspectorUI/UserInterface/Base/Utilities.js:
(Array.prototype.min): Added.
(Array.prototype.minIndex):

* Source/WebInspectorUI/UserInterface/Models/QueryResult.js
(WI.QueryResult.prototype.set rankForTesting): Added.
* LayoutTests/inspector/css/css-property.html:
* LayoutTests/inspector/css/css-property-expected.txt:

* LayoutTests/inspector/unit-tests/array-utilities.html:
* LayoutTests/inspector/unit-tests/array-utilities-expected.txt:

Canonical link: https://commits.webkit.org/252194@main
  • Loading branch information
dcrousso committed Jul 6, 2022
1 parent bf1f57b commit 72de9692cf328ad3d3f71c2b632cbd03835db242
Showing 9 changed files with 97 additions and 11 deletions.
@@ -8,6 +8,8 @@ PASS: "background-repeat" should have at least 1 count.
PASS: "background-repeat-x" should not be counted.
PASS: "background-repeat-y" should not be counted.
PASS: "background-repeat-invalid" should not be counted.

Sorted by usage:
[
"Fake Property 200 Seen",
"Fake Property 150 Seen",
@@ -26,6 +28,8 @@ PASS: "background-repeat-invalid" should not be counted.
"Fake Property 50 Seen",
"Fake Property 200 Unseen"
]
Initially selected completion when ranked by `count % 2`: "Fake Property 101 Seen"
Initially selected completion when ranked by `count % 3`: "Fake Property 200 Seen"

-- Running test case: CSSProperty.prototype.get valid
PASS: "background-repeat" is a valid property.
@@ -18,12 +18,36 @@
InspectorTest.expectThat(!("background-repeat-y" in WI.CSSProperty._cachedNameCounts), `"background-repeat-y" should not be counted.`);
InspectorTest.expectThat(!("background-repeat-invalid" in WI.CSSProperty._cachedNameCounts), `"background-repeat-invalid" should not be counted.`);

InspectorTest.newline();

const counts = [99, 1, 100, 0, 101, 150, 50, 200];
let seenProperty = (count) => `Fake Property ${count} Seen`;
let unseenProperty = (count) => `Fake Property ${count} Unseen`;
let createQueryResults = (count, rank) => {
let unseen = new WI.QueryResult(unseenProperty(count), ["not reached"]);
unseen.rankForTesting = rank;

let seen = new WI.QueryResult(seenProperty(count), ["not reached"]);
seen.rankForTesting = rank;

return [unseen, seen];
}
for (const count of counts)
WI.CSSProperty._cachedNameCounts[seenProperty(count)] = count;

InspectorTest.log("Sorted by usage:");
InspectorTest.json(counts.flatMap((count) => [unseenProperty(count), seenProperty(count)]).sort(WI.CSSProperty.sortByPropertyNameUsageCount))

{
let completions = counts.flatMap((count) => createQueryResults(count, count % 2));
InspectorTest.log(`Initially selected completion when ranked by \`count % 2\`: "${completions[WI.CSSProperty.indexOfCompletionForMostUsedPropertyName(completions)].value}"`);
}

{
let completions = counts.flatMap((count) => createQueryResults(count, count % 3));
InspectorTest.log(`Initially selected completion when ranked by \`count % 3\`: "${completions[WI.CSSProperty.indexOfCompletionForMostUsedPropertyName(completions)].value}"`);
}

for (const count of counts)
delete WI.CSSProperty._cachedNameCounts[seenProperty(count)];
}
@@ -1,10 +1,19 @@

== Running test suite: ArrayUtilities
-- Running test case: Array.prototype.min
PASS: min of an empty array should be undefined.
PASS: min of an array with one item should be the item.
PASS: min of an increasing array should be the smallest item.
PASS: min of an decreasing array should be the smallest item.
PASS: min of a mixed array should be the smallest item.
PASS: min of an array with duplicates should be the smallest item.
PASS: min with a comparator of a mixed array should be the smallest item.

-- Running test case: Array.prototype.minIndex
PASS: minIndex of an empty array should be 0.
PASS: minIndex of an empty array should be -1.
PASS: minIndex of an array with one item should be the index of the item.
PASS: minIndex of an increasing array should be 0.
PASS: minIndex of an decreasing array should be length - 1.
PASS: minIndex of an increasing array should be the index of the smallest item.
PASS: minIndex of an decreasing array should be the index of the smallest item.
PASS: minIndex of a mixed array should be the index of the smallest item.
PASS: minIndex of an array with duplicates should be the index of the smallest item.
PASS: minIndex with a comparator of a mixed array should be the index of the smallest item.
@@ -7,13 +7,31 @@
{
let suite = InspectorTest.createSyncSuite("ArrayUtilities");

suite.addTestCase({
name: "Array.prototype.min",
test() {
InspectorTest.expectEqual([].min(), undefined, "min of an empty array should be undefined.");
InspectorTest.expectEqual([1].min(), 1, "min of an array with one item should be the item.");
InspectorTest.expectEqual([1, 2, 3, 4].min(), 1, "min of an increasing array should be the smallest item.");
InspectorTest.expectEqual([4, 3, 2, 1].min(), 1, "min of an decreasing array should be the smallest item.");
InspectorTest.expectEqual([3, 4, 1, 2].min(), 1, "min of a mixed array should be the smallest item.");
InspectorTest.expectEqual([3, 1, 1, 3].min(), 1, "min of an array with duplicates should be the smallest item.");

let objs = [{value: 3}, {value: 4}, {value: 1}, {value: 2}];
let comparator = (a, b) => a.value - b.value;
InspectorTest.expectEqual(objs.min(comparator), objs[2], "min with a comparator of a mixed array should be the smallest item.");

return true;
}
});

suite.addTestCase({
name: "Array.prototype.minIndex",
test() {
InspectorTest.expectEqual([].minIndex(), 0, "minIndex of an empty array should be 0.");
InspectorTest.expectEqual([].minIndex(), -1, "minIndex of an empty array should be -1.");
InspectorTest.expectEqual([1].minIndex(), 0, "minIndex of an array with one item should be the index of the item.");
InspectorTest.expectEqual([1, 2, 3, 4].minIndex(), 0, "minIndex of an increasing array should be 0.");
InspectorTest.expectEqual([4, 3, 2, 1].minIndex(), 3, "minIndex of an decreasing array should be length - 1.");
InspectorTest.expectEqual([1, 2, 3, 4].minIndex(), 0, "minIndex of an increasing array should be the index of the smallest item.");
InspectorTest.expectEqual([4, 3, 2, 1].minIndex(), 3, "minIndex of an decreasing array should be the index of the smallest item.");
InspectorTest.expectEqual([3, 4, 1, 2].minIndex(), 2, "minIndex of a mixed array should be the index of the smallest item.");
InspectorTest.expectEqual([3, 1, 1, 3].minIndex(), 1, "minIndex of an array with duplicates should be the index of the smallest item.");

@@ -1592,6 +1592,14 @@ function simpleGlobStringToRegExp(globString, regExpFlags)
return new RegExp(regexString, regExpFlags);
}

Object.defineProperty(Array.prototype, "min",
{
value(comparator)
{
return this[this.minIndex(comparator)];
},
});

Object.defineProperty(Array.prototype, "minIndex",
{
value(comparator)
@@ -1602,9 +1610,9 @@ Object.defineProperty(Array.prototype, "minIndex",
}
comparator = comparator || defaultComparator;

let minIndex = 0;
for (let i = 1; i < this.length; ++i) {
if (comparator(this[minIndex], this[i]) > 0)
let minIndex = -1;
for (let i = 0; i < this.length; ++i) {
if (minIndex === -1 || comparator(this[minIndex], this[i]) > 0)
minIndex = i;
}
return minIndex;
@@ -112,6 +112,25 @@ WI.CSSProperty = class CSSProperty extends WI.Object
return 0;
}

static indexOfCompletionForMostUsedPropertyName(completions)
{
let highestRankCompletions = completions;
if (highestRankCompletions.every((completion) => completion instanceof WI.QueryResult)) {
let highestRankValue = -1;
for (let completion of completions) {
if (completion.rank > highestRankValue) {
highestRankValue = completion.rank;
highestRankCompletions = [];
}

if (completion.rank === highestRankValue)
highestRankCompletions.push(completion);
}
}
let mostUsedHighestRankCompletion = highestRankCompletions.min((a, b) => WI.CSSProperty.sortByPropertyNameUsageCount(WI.CSSCompletions.getCompletionText(a), WI.CSSCompletions.getCompletionText(b)));
return completions.indexOf(mostUsedHighestRankCompletion);
}

static _initializePropertyNameCounts()
{
if (WI.CSSProperty._cachedNameCounts)
@@ -124,4 +124,8 @@ WI.QueryResult = class QueryResult
ranges.push(new WI.TextRange(0, startIndex, 0, endIndex + 1));
return ranges;
}

// Testing

set rankForTesting(rank) { this._rank = rank; }
};
@@ -371,7 +371,7 @@ WI.SpreadsheetStyleProperty = class SpreadsheetStyleProperty extends WI.Object
spreadsheetTextFieldInitialCompletionIndex(textField, completions)
{
if (textField === this._nameTextField && WI.settings.experimentalCSSSortPropertyNameAutocompletionByUsage.value)
return completions.minIndex(WI.CSSProperty.sortByPropertyNameUsageCount);
return WI.CSSProperty.indexOfCompletionForMostUsedPropertyName(completions);
return 0;
}

@@ -463,7 +463,7 @@ WI.SpreadsheetTextField = class SpreadsheetTextField
this._suggestionsView.selectedIndex = NaN;
if (this._completionPrefix) {
if (this._delegate?.spreadsheetTextFieldInitialCompletionIndex)
this._suggestionsView.selectedIndex = this._delegate.spreadsheetTextFieldInitialCompletionIndex(this, completions.map((completion) => WI.CSSCompletions.getCompletionText(completion)));
this._suggestionsView.selectedIndex = this._delegate.spreadsheetTextFieldInitialCompletionIndex(this, completions);
else
this._suggestionsView.selectNext();
} else

0 comments on commit 72de969

Please sign in to comment.