Skip to content

Commit

Permalink
Web Inspector: console's code completion should be case-insensitive
Browse files Browse the repository at this point in the history
rdar://124544458
https://bugs.webkit.org/show_bug.cgi?id=270925

Reviewed by Devin Rousso.

Convert both strings into lowercase when trying to match prefixes for
function, variable, or property names.

Also add an experimental setting item for this feature enhancement.

* Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js:
(WI.CodeMirrorCompletionController.prototype._applyCompletionHint.update):
(WI.CodeMirrorCompletionController.prototype._applyCompletionHint):
   - Since case-insensitive matching may be used, only show the
     watermark text on the prompt if the typed text exactly,
     case-sensitively matches the prefix.

* Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js:
(WI.CodeMirrorCompletionController.prototype._generateJavaScriptCompletions.):
(WI.CodeMirrorCompletionController.prototype._generateJavaScriptCompletions):
* Source/WebInspectorUI/UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:
(WI.JavaScriptRuntimeCompletionProvider.prototype.completionControllerCompletionsNeeded.receivedPropertyNames):
(WI.JavaScriptRuntimeCompletionProvider.prototype.completionControllerCompletionsNeeded):
(WI.JavaScriptRuntimeCompletionProvider.prototype.completionControllerCompletionsNeeded.receivedPropertyNames.compare): Deleted.
   - Optionally perform case-insensitive prefix matching when filtering
     the code completion suggestions.

* Source/WebInspectorUI/UserInterface/Base/Setting.js:
* Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js:
* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:
   - Add the experimental setting item for this change in the Settings
     tab's Experimental sub-tab.

* LayoutTests/inspector/console/js-completions-expected.txt:
* LayoutTests/inspector/console/js-completions.html:
   - Add a test case for the code completions' ordering since now it
     gets slightly more involved with case-insensitive matching.

Canonical link: https://commits.webkit.org/279195@main
  • Loading branch information
the-chenergy authored and rcaliman-apple committed May 23, 2024
1 parent 5e0f9b3 commit 14ea1c1
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 36 deletions.
142 changes: 142 additions & 0 deletions LayoutTests/inspector/console/js-completions-expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,145 @@ PASS: Completions should still contain myVariable before cache is cleared.
PASS: Completions should change after cache is cleared.
PASS: Completions should not contain myVariable after it's deleted by code run in console.

-- Running test case: console.jsCompletions.completionOrdering
Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":""} and experimentalShowCaseSensitiveAutocomplete=false:
[
"aa",
"Aa",
"ab",
"Ab",
"__aa__",
"__ab__"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"a"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"aa",
"ab",
"Aa",
"Ab"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"A"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"Aa",
"Ab",
"aa",
"ab"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"_"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"__aa__",
"__ab__"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":""} and experimentalShowCaseSensitiveAutocomplete=false:
[
"1",
"2",
"10",
"20",
"\"__aa__\"",
"\"__ab__\"",
"\"aa\"",
"\"Aa\"",
"\"ab\"",
"\"Ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"1"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"1",
"10"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"a"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"\"aa\"",
"\"ab\"",
"\"Aa\"",
"\"Ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"A"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"\"Aa\"",
"\"Ab\"",
"\"aa\"",
"\"ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"_"} and experimentalShowCaseSensitiveAutocomplete=false:
[
"\"__aa__\"",
"\"__ab__\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":""} and experimentalShowCaseSensitiveAutocomplete=true:
[
"aa",
"Aa",
"ab",
"Ab",
"__aa__",
"__ab__"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"a"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"aa",
"ab"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"A"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"Aa",
"Ab"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds.","prefix":"_"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"__aa__",
"__ab__"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":""} and experimentalShowCaseSensitiveAutocomplete=true:
[
"1",
"2",
"10",
"20",
"\"__aa__\"",
"\"__ab__\"",
"\"aa\"",
"\"Aa\"",
"\"ab\"",
"\"Ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"1"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"1",
"10"
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"a"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"\"aa\"",
"\"ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"A"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"\"Aa\"",
"\"Ab\""
]

Completions with options={"baseString":"objectWithMixedPropertyKinds[","prefix":"\"_"} and experimentalShowCaseSensitiveAutocomplete=true:
[
"\"__aa__\"",
"\"__ab__\""
]


83 changes: 67 additions & 16 deletions LayoutTests/inspector/console/js-completions.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,43 @@
<head>
<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
<script>
let objectWithMixedPropertyKinds = Object.assign(Object.create(null), {
1: 42,
2: 42,
10: 42,
20: 42,
aa: 42,
ab: 42,
Aa: 42,
Ab: 42,
__aa__: 42,
__ab__: 42
});

function test()
{
const generateJSCompletions = (options = {}) => new Promise((resolve, reject) => {
let mockCompletionController = {
mode: null,
updateCompletions: (completions) => {
resolve(completions);
},
};

let defaultCompletions = options.defaultCompletions || [];
let baseString = options.baseString || "";
let prefix = options.prefix || "";
let suffix = options.suffix || "";
const forced = true;
WI.javaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded(mockCompletionController, defaultCompletions, baseString, prefix, suffix, forced);
});

let suite = InspectorTest.createAsyncSuite("console.jsCompletions");

suite.addTestCase({
name: "console.jsCompletions.variableCompletions",
description: "Test that the JavaScript completions for variables and its cache work as expected.",
async test() {
const generateJSCompletions = () => new Promise((resolve, reject) => {
let mockCompletionController = {
mode: null,
updateCompletions: (completions) => {
resolve(completions);
},
};

const defaultCompletions = [];
const baseString = "";
const prefix = "";
const suffix = "";
const forced = true;
WI.javaScriptRuntimeCompletionProvider.completionControllerCompletionsNeeded(mockCompletionController, defaultCompletions, baseString, prefix, suffix, forced);
});

let completionsBeforeEvaluating = await generateJSCompletions();
InspectorTest.expectFalse(completionsBeforeEvaluating.includes("myVariable"), "Completions should not contain myVariable by default.");

Expand All @@ -52,6 +65,44 @@
}
});

suite.addTestCase({
name: "console.jsCompletions.completionOrdering",
description: "Test that the JavaScript completions are ordered correctly.",
async test() {
const outputCompletions = async (options) => {
InspectorTest.log(`Completions with options=${JSON.stringify(options)} and experimentalShowCaseSensitiveAutocomplete=${WI.settings.experimentalShowCaseSensitiveAutocomplete.value}:`);
InspectorTest.json(await generateJSCompletions(options));
InspectorTest.log("");
};

WI.settings.experimentalShowCaseSensitiveAutocomplete.value = false;
WI.javaScriptRuntimeCompletionProvider.clearCachedPropertyNames();
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: ""}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "a"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "A"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "_"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: ""}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "1"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"a"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"A"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"_"}));

WI.settings.experimentalShowCaseSensitiveAutocomplete.value = true;
WI.javaScriptRuntimeCompletionProvider.clearCachedPropertyNames();
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: ""}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "a"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "A"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds.", prefix: "_"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: ""}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "1"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"a"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"A"}));
await outputCompletions(({baseString: "objectWithMixedPropertyKinds[", prefix: "\"_"}));

WI.settings.experimentalShowCaseSensitiveAutocomplete.reset();
}
});

suite.runTestCasesAndFinish();
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1853,6 +1853,7 @@ localizedStrings["Update Font"] = "Update Font";
localizedStrings["Update Image"] = "Update Image";
localizedStrings["Update Local Override"] = "Update Local Override";
localizedStrings["Usage: %s"] = "Usage: %s";
localizedStrings["Use case sensitive autocomplete"] = "Use case sensitive autocomplete";
localizedStrings["Use default media styles"] = "Use default media styles";
localizedStrings["Use fuzzy matching for CSS code completion"] = "Use fuzzy matching for CSS code completion";
localizedStrings["Use mock capture devices"] = "Use mock capture devices";
Expand Down
1 change: 1 addition & 0 deletions Source/WebInspectorUI/UserInterface/Base/Setting.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ WI.settings = {
experimentalCSSSortPropertyNameAutocompletionByUsage: new WI.Setting("experimental-css-sort-property-name-autocompletion-by-usage", true),
experimentalEnableNetworkEmulatedCondition: new WI.Setting("experimental-enable-network-emulated-condition", false),
experimentalGroupSourceMapErrors: new WI.Setting("experimental-group-source-map-errors", true),
experimentalShowCaseSensitiveAutocomplete: new WI.Setting("experimental-show-case-sensitive-auto-complete", false),
experimentalLimitSourceCodeHighlighting: new WI.Setting("engineering-limit-source-code-highlighting", false),
experimentalUseFuzzyMatchingForCSSCodeCompletion: new WI.Setting("experimental-use-fuzzy-matching-for-css-code-completion", true),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ WI.CodeMirrorCompletionController = class CodeMirrorCompletionController extends
var matchingWords = [];

var prefix = this._prefix;
let prefixLowerCase = prefix.toLowerCase();
let caseSensitiveMatching = WI.settings.experimentalShowCaseSensitiveAutocomplete.value;

var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state;

Expand Down Expand Up @@ -688,8 +690,11 @@ WI.CodeMirrorCompletionController = class CodeMirrorCompletionController extends
continue;
if (declaringVariable && !allowedKeywordsWhenDeclaringVariable.has(keyword))
continue;
if (!keyword.startsWith(prefix))

let startsWithPrefix = caseSensitiveMatching ? keyword.startsWith(prefix) : keyword.toLowerCase().startsWith(prefixLowerCase);
if (!startsWithPrefix)
continue;

matchingWords.push(keyword);
}
}
Expand All @@ -703,9 +708,14 @@ WI.CodeMirrorCompletionController = class CodeMirrorCompletionController extends
// Otherwise the currently typed text will always match and that isn't useful.
if (declaringVariable && variable.name === prefix)
continue;
if (matchingWords.includes(variable.name))
continue;

let startsWithPrefix = caseSensitiveMatching ? variable.name.startsWith(prefix) : variable.name.toLowerCase().startsWith(prefixLowerCase);
if (!startsWithPrefix)
continue;

if (variable.name.startsWith(prefix) && !matchingWords.includes(variable.name))
matchingWords.push(variable.name);
matchingWords.push(variable.name);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvid

var completions = defaultCompletions;
let knownCompletions = new Set(completions);
let prefixLowerCase = prefix.toLowerCase();
let caseSensitiveMatching = WI.settings.experimentalShowCaseSensitiveAutocomplete.value;

for (var i = 0; i < propertyNames.length; ++i) {
var property = propertyNames[i];
Expand All @@ -372,30 +374,34 @@ WI.JavaScriptRuntimeCompletionProvider = class JavaScriptRuntimeCompletionProvid
property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + (suffix !== quoteUsed ? quoteUsed : "");
}

if (!property.startsWith(prefix) || knownCompletions.has(property))
if (knownCompletions.has(property))
continue;

let startsWithPrefix = caseSensitiveMatching ? property.startsWith(prefix) : property.toLowerCase().startsWith(prefixLowerCase);
if (!startsWithPrefix)
continue;

completions.push(property);
knownCompletions.add(property);
}

function compare(a, b)
let completionSortCriteria = new Map;
for (let completion of completions) {
completionSortCriteria.set(completion, {
startsWithPrefix: completion.startsWith(prefix),
numericValue: isFinite(completion) ? +completion : Infinity,
isRareProperty: completion.startsWith("__") && completion.endsWith("__"),
});
}

function compare(s, t)
{
// Try to sort in numerical order first.
let numericCompareResult = a - b;
if (!isNaN(numericCompareResult))
return numericCompareResult;

// Sort __defineGetter__, __lookupGetter__, and friends last.
let aRareProperty = a.startsWith("__") && a.endsWith("__");
let bRareProperty = b.startsWith("__") && b.endsWith("__");
if (aRareProperty && !bRareProperty)
return 1;
if (!aRareProperty && bRareProperty)
return -1;

// Not numbers, sort as strings.
return a.extendedLocaleCompare(b);
let a = completionSortCriteria.get(s);
let b = completionSortCriteria.get(t);
return a.numericValue - b.numericValue // Order completions that are numbers to come first, with lower values coming first.
|| a.isRareProperty - b.isRareProperty // Order __defineGetter__, __lookupGetter__, and friends to come last.
|| b.startsWithPrefix - a.startsWithPrefix // Order completions that directly match the typed text to come first.
|| s.extendedLocaleCompare(t); // Order completions in the same category by natural string ordering.
}

completions.sort(compare);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ WI.SettingsTabContentView = class SettingsTabContentView extends WI.TabContentVi

let consoleGroup = experimentalSettingsView.addGroup(WI.UIString("Console:"));
consoleGroup.addSetting(WI.settings.experimentalGroupSourceMapErrors, WI.UIString("Group source map network errors"));
consoleGroup.addSetting(WI.settings.experimentalShowCaseSensitiveAutocomplete, WI.UIString("Use case sensitive autocomplete"));

experimentalSettingsView.addSeparator();

Expand Down

0 comments on commit 14ea1c1

Please sign in to comment.