Changeset 286611 in webkit
- Timestamp:
- Dec 7, 2021 12:51:52 PM (7 months ago)
- Location:
- trunk
- Files:
-
- 3 added
- 19 edited
-
LayoutTests/ChangeLog (modified) (1 diff)
-
LayoutTests/inspector/unit-tests/css-keyword-completions-expected.txt (modified) (1 diff)
-
LayoutTests/inspector/unit-tests/css-keyword-completions.html (modified) (3 diffs)
-
LayoutTests/inspector/unit-tests/css-query-controller-expected.txt (added)
-
LayoutTests/inspector/unit-tests/css-query-controller.html (added)
-
LayoutTests/inspector/unit-tests/string-utilities-expected.txt (modified) (1 diff)
-
LayoutTests/inspector/unit-tests/string-utilities.html (modified) (1 diff)
-
Source/WebInspectorUI/ChangeLog (modified) (1 diff)
-
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Base/Setting.js (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Base/Utilities.js (modified) (2 diffs)
-
Source/WebInspectorUI/UserInterface/Controllers/CSSQueryController.js (added)
-
Source/WebInspectorUI/UserInterface/Main.html (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js (modified) (2 diffs)
-
Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js (modified) (4 diffs)
-
Source/WebInspectorUI/UserInterface/Models/QueryResult.js (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Test.html (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Views/CompletionSuggestionsView.css (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Views/CompletionSuggestionsView.js (modified) (5 diffs)
-
Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js (modified) (1 diff)
-
Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js (modified) (3 diffs)
-
Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js (modified) (18 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r286609 r286611 1 2021-12-07 Razvan Caliman <rcaliman@apple.com> 2 3 Web Inspector: Support fuzzy matching in CSS completions 4 https://bugs.webkit.org/show_bug.cgi?id=230351 5 <rdar://82976292> 6 7 Reviewed by Devin Rousso. 8 9 Add test for `WI.CSSQueryController` to check fuzzy matching logic for CSS completions. 10 Follows prior example from `LayoutTests/inspector/unit-tests/resource-query-controller.html` 11 since `WI.ResouceQueryController` served as the model class that was adapted for CSS. 12 13 * inspector/unit-tests/css-query-controller-expected.txt: Added. 14 * inspector/unit-tests/css-query-controller.html: Added. 15 1 16 2021-12-07 Chris Dumez <cdumez@apple.com> 2 17 -
trunk/LayoutTests/inspector/unit-tests/css-keyword-completions-expected.txt
r279502 r286611 72 72 PASS: All expected completions were present. 73 73 74 -- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.NoWhitespaceAfterFunction 75 PASS: Expected result prefix to be "" 76 PASS: Expected exactly 0 completion results. 77 PASS: All expected completions were present. 78 79 -- Running test case: WI.CSSKeywordCompletions.forPartialPropertyValue.WhitespaceAfterFunction 80 PASS: Expected result prefix to be "a" 81 PASS: All expected completions were present. 82 -
trunk/LayoutTests/inspector/unit-tests/css-keyword-completions.html
r279502 r286611 67 67 }); 68 68 69 function addTestForPartialPropertyValue({name, description, propertyName, text, caretPosition, expectedPrefix, expectedCompletions, additionalFunctionValueCompletionsProvider}) {69 function addTestForPartialPropertyValue({name, description, propertyName, text, caretPosition, expectedPrefix, expectedCompletions, expectedCompletionCount, additionalFunctionValueCompletionsProvider}) { 70 70 suite.addTestCase({ 71 71 name, … … 75 75 expectedPrefix ??= text; 76 76 expectedCompletions ??= []; 77 expectedCompletionCount ??= -1; 77 78 additionalFunctionValueCompletionsProvider ??= () => {}; 78 79 79 80 let completionResults = WI.CSSKeywordCompletions.forPartialPropertyValue(text, propertyName, {caretPosition, additionalFunctionValueCompletionsProvider}); 80 81 InspectorTest.expectEqual(completionResults.prefix, expectedPrefix, `Expected result prefix to be "${expectedPrefix}"`); 82 83 if (expectedCompletionCount >= 0) 84 InspectorTest.expectEqual(completionResults.completions.length, expectedCompletionCount, `Expected exactly ${expectedCompletionCount} completion results.`); 81 85 82 86 // 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. … … 230 234 }); 231 235 236 // `hsl()a|` 237 addTestForPartialPropertyValue({ 238 name: "WI.CSSKeywordCompletions.forPartialPropertyValue.NoWhitespaceAfterFunction", 239 description: "Test that color completions are not offered immediately after a closing parenthesis", 240 propertyName: "background", 241 text: "hsl()a", 242 caretPosition: 6, 243 expectedPrefix: "", 244 expectedCompletionCount: 0 245 }); 246 247 // `hsl() a|` 248 addTestForPartialPropertyValue({ 249 name: "WI.CSSKeywordCompletions.forPartialPropertyValue.WhitespaceAfterFunction", 250 description: "Test that color completions are offered when a closing parenthesis is followed by whitespace", 251 propertyName: "background", 252 text: "hsl() a", 253 caretPosition: 7, 254 expectedPrefix: "a", 255 expectedCompletions: ["aliceblue", "antiquewhite"], 256 }); 257 232 258 suite.runTestCasesAndFinish(); 233 259 } -
trunk/LayoutTests/inspector/unit-tests/string-utilities-expected.txt
r237644 r286611 71 71 PASS: The letter 'c', 'd', and 'e' are escaped. 72 72 73 -- Running test case: String.prototype.isLowerCase 74 PASS: String with single lowercase character should be lowercase. 75 PASS: String with multiple lowercase characters should be lowercase. 76 PASS: String with single uppercase character should not be lowercase. 77 PASS: String with mixed case characters should not be lowercase. 78 PASS: Empty string should not be lowercase. 79 PASS: String with non-alpha character should not be lowercase. 80 PASS: String with numeric character should not be lowercase. 81 82 -- Running test case: String.prototype.isUpperCase 83 PASS: String with single uppercase character should be uppercase. 84 PASS: String with multiple uppercase characters should be uppercase. 85 PASS: String with single lowercase character should not be uppercase. 86 PASS: String with mixed case characters should not be uppercase. 87 PASS: Empty string should not be uppercase. 88 PASS: String with non-alpha character should not be uppercase. 89 PASS: String with numeric character should not be uppercase. 90 -
trunk/LayoutTests/inspector/unit-tests/string-utilities.html
r237644 r286611 126 126 }); 127 127 128 suite.addTestCase({ 129 name: "String.prototype.isLowerCase", 130 test() { 131 InspectorTest.expectTrue("a".isLowerCase(), "String with single lowercase character should be lowercase."); 132 InspectorTest.expectTrue("abc".isLowerCase(), "String with multiple lowercase characters should be lowercase."); 133 InspectorTest.expectFalse("A".isLowerCase(), "String with single uppercase character should not be lowercase."); 134 InspectorTest.expectFalse("aBc".isLowerCase(), "String with mixed case characters should not be lowercase."); 135 InspectorTest.expectFalse("".isLowerCase(), "Empty string should not be lowercase."); 136 InspectorTest.expectFalse(".".isLowerCase(), "String with non-alpha character should not be lowercase."); 137 InspectorTest.expectFalse("1".isLowerCase(), "String with numeric character should not be lowercase."); 138 139 return true; 140 } 141 }); 142 143 suite.addTestCase({ 144 name: "String.prototype.isUpperCase", 145 test() { 146 InspectorTest.expectTrue("A".isUpperCase(), "String with single uppercase character should be uppercase."); 147 InspectorTest.expectTrue("ABC".isUpperCase(), "String with multiple uppercase characters should be uppercase."); 148 InspectorTest.expectFalse("a".isUpperCase(), "String with single lowercase character should not be uppercase."); 149 InspectorTest.expectFalse("AbC".isUpperCase(), "String with mixed case characters should not be uppercase."); 150 InspectorTest.expectFalse("".isUpperCase(), "Empty string should not be uppercase."); 151 InspectorTest.expectFalse(".".isUpperCase(), "String with non-alpha character should not be uppercase."); 152 InspectorTest.expectFalse("1".isUpperCase(), "String with numeric character should not be uppercase."); 153 154 return true; 155 } 156 }); 157 128 158 suite.runTestCasesAndFinish(); 129 159 } -
trunk/Source/WebInspectorUI/ChangeLog
r286558 r286611 1 2021-12-07 Razvan Caliman <rcaliman@apple.com> 2 3 Web Inspector: Support fuzzy matching in CSS completions 4 https://bugs.webkit.org/show_bug.cgi?id=230351 5 <rdar://82976292> 6 7 Reviewed by Devin Rousso. 8 9 Use fuzzy matching for identifying CSS completions in the Styles details sidebar. 10 11 There are three main parts to this patch: 12 1. Introduce `WI.CSSQueryController` with logic to do fuzzy matching on provided values. 13 2. Change `WI.CompletionSuggestionsView` to support both plain strings and `WI.QueryResult`s. 14 3. Change `WI.SpreadsheetTextField` so its `value` doesn't always return its `element.textContent`. 15 16 With fuzzy matching, completions are not guaranteed anymore to be prefixed with the query. 17 Therefore, it's no longer viable to rely on `WI.SpreadsheetTextField.value` being a concatenation of `textContent` of nodes within. 18 19 To adress this, there's now `WI.SpreadsheetTextField._pendingValue` which includes the prospective 20 completion regardless of whether the query is a completion prefix or at match at any other position. 21 22 * Localizations/en.lproj/localizedStrings.js: 23 * UserInterface/Base/Setting.js: 24 Add a flag to enable the fuzzy matching feature. 25 26 * UserInterface/Controllers/CSSQueryController.js: Added. 27 (WI.CSSQueryController): 28 (WI.CSSQueryController.prototype.addValues): 29 (WI.CSSQueryController.prototype.reset): 30 (WI.CSSQueryController.prototype.executeQuery): 31 (WI.CSSQueryController.prototype._findSpecialCharacterIndices): 32 Add a speclialized class to hold the logic for fuzzy matching of CSS properties and values. 33 It clones logic from `WI.ResouceQueryController` with adjustments specific to CSS; more to follow as the feature gets refined. 34 35 * UserInterface/Main.html: 36 * UserInterface/Models/CSSCompletions.js: 37 (WI.CSSCompletions): 38 (WI.CSSCompletions.prototype.addValues): 39 (WI.CSSCompletions.prototype.executeQuery): 40 Support both the current prefix matching approach as well as fuzzy matching. 41 42 * UserInterface/Models/CSSKeywordCompletions.js: 43 (WI.CSSKeywordCompletions.forPartialPropertyName): 44 Opt into fuzzy matching when getting completions for property names and values. 45 46 * UserInterface/Models/QueryResult.js: 47 (WI.QueryResult.prototype.get matches): 48 * UserInterface/Test.html: 49 50 * UserInterface/Views/CompletionSuggestionsView.css: 51 (.completion-suggestions-container > .item > .highlighted): 52 Highlight specific characters that matched in result identified by fuzzy matching. 53 54 * UserInterface/Views/CompletionSuggestionsView.js: 55 (WI.CompletionSuggestionsView.prototype.selectNext): 56 (WI.CompletionSuggestionsView.prototype.selectPrevious): 57 (WI.CompletionSuggestionsView.prototype.update): 58 (WI.CompletionSuggestionsView.prototype.getCompletionText): 59 (WI.CompletionSuggestionsView.prototype._createHighlightedCompletionFragment): 60 Change `WI.CompletionSuggestionsView` to hold a list of completions, 61 either strings or `WI.QueryResult`, and return the appropriate completion text 62 instead of returning the `textContent` of the selected element. 63 64 `WI.CompletionSuggestionsView` is used elsewhere in the Console and Sources panel 65 so avoid impacting those consumers until they opt in to fuzzy matching as well. 66 67 * UserInterface/Views/SettingsTabContentView.js: 68 69 * UserInterface/Views/SpreadsheetStyleProperty.js: 70 (WI.SpreadsheetStyleProperty.prototype._handleNameChange): 71 (WI.SpreadsheetStyleProperty.prototype._handleValueChange): 72 (WI.SpreadsheetStyleProperty.prototype._nameCompletionDataProvider): 73 (WI.SpreadsheetStyleProperty.prototype._valueCompletionDataProvider): 74 On change, get the value of the `WI.SpreadsheetTextField` instead of 75 the corresponding element's `textContent`. 76 77 * UserInterface/Views/SpreadsheetTextField.js: 78 Introduced `SpreadsheetTextField._completionPrefix` to hold the query a user is typing. 79 80 Introduced `SpreadsheetTextField._completionText` to hold the completion text of the selected but 81 not yet applied completion. This replaces the previous behavior of concatenating the query and `suggestionHint` 82 because with fuzzy matching the completion isn't guaranteed to be prefixed with the query. 83 84 Introduced `SpreadsheetTextField._pendingValue` to hold the result of applying the selected 85 completion. This replaces the previous behavior of `WI.SpreadsheetTextField._combineEditorElementChildren`. 86 87 (WI.SpreadsheetTextField): 88 (WI.SpreadsheetTextField.prototype.get value): 89 Change `WI.SpreadsheetTextField` so its value is divorced from its element's `textContent`. 90 While editing, `WI.SpreadsheetTextField.value` returns the pending value so that 91 a valid CSS string gets written to the stylesheet. 92 93 (WI.SpreadsheetTextField.prototype.set suggestionHint): 94 If the query is a prefix for the selected completion, keep the existing behavior of appending 95 the suggestion hint substring. Otherwise, hide the suggestion hint element because the 96 concatenation doesn't make sense. 97 98 (WI.SpreadsheetTextField.prototype.stopEditing): 99 (WI.SpreadsheetTextField.prototype.discardCompletion): 100 (WI.SpreadsheetTextField.prototype.completionSuggestionsSelectedCompletion): 101 (WI.SpreadsheetTextField.prototype.completionSuggestionsClickedCompletion): 102 (WI.SpreadsheetTextField.prototype._discardChange): 103 (WI.SpreadsheetTextField.prototype._handleBlur): 104 (WI.SpreadsheetTextField.prototype._handleKeyDown): 105 (WI.SpreadsheetTextField.prototype._handleKeyDownForSuggestionView): 106 (WI.SpreadsheetTextField.prototype._handleInput): 107 (WI.SpreadsheetTextField.prototype._updateCompletions): 108 (WI.SpreadsheetTextField.prototype._applyPendingValue): 109 After applying a completion, the value is replaced with `WI.SpreadsheetTextField._pendingValue`. 110 At this point, `WI.SpreadsheetTextField.value` and the `textContent` of the element converge. 111 112 (WI.SpreadsheetTextField.prototype._updatePendingValueWithCompletionText): 113 When presented with a completion, the query gets substituted with the full completion text in 114 `WI.SpreadsheetTextField._pendingValue` regardless of whether the query is a prefix or not. 115 116 (WI.SpreadsheetTextField.prototype._applyCompletionHint): Deleted. 117 (WI.SpreadsheetTextField.prototype._combineEditorElementChildren): Deleted. 118 1 119 2021-12-06 Patrick Angle <pangle@apple.com> 2 120 -
trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
r285974 r286611 1608 1608 localizedStrings["Use Mock Capture Devices"] = "Use Mock Capture Devices"; 1609 1609 localizedStrings["Use default media styles"] = "Use default media styles"; 1610 localizedStrings["Use fuzzy matching for completion suggestions"] = "Use fuzzy matching for completion suggestions"; 1610 1611 localizedStrings["Use the resource cache when loading resources"] = "Use the resource cache when loading resources"; 1611 1612 localizedStrings["User Agent"] = "User Agent"; -
trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js
r283212 r286611 232 232 experimentalCollapseBlackboxedCallFrames: new WI.Setting("experimental-collapse-blackboxed-call-frames", false), 233 233 experimentalAllowInspectingInspector: new WI.Setting("experimental-allow-inspecting-inspector", false), 234 experimentalCSSCompletionFuzzyMatching: new WI.Setting("experimental-css-completion-fuzzy-matching", false), 234 235 235 236 // Protocol -
trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js
r276975 r286611 774 774 value() 775 775 { 776 return String(this) === this.toLowerCase();776 return /^[a-z]+$/.test(this); 777 777 } 778 778 }); … … 782 782 value() 783 783 { 784 return String(this) === this.toUpperCase();784 return /^[A-Z]+$/.test(this); 785 785 } 786 786 }); -
trunk/Source/WebInspectorUI/UserInterface/Main.html
r286329 r286611 928 928 <script src="Controllers/NetworkManager.js"></script> 929 929 <script src="Controllers/OverlayManager.js"></script> 930 <script src="Controllers/CSSQueryController.js"></script> 930 931 <script src="Controllers/ResourceQueryController.js"></script> 931 932 <script src="Controllers/RuntimeManager.js"></script> -
trunk/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js
r286458 r286611 59 59 60 60 this._acceptEmptyPrefix = acceptEmptyPrefix; 61 this._queryController = null; 61 62 } 62 63 … … 254 255 this._values.pushAll(values); 255 256 this._values.sort(); 257 258 this._queryController?.addValues(values); 259 } 260 261 executeQuery(query) 262 { 263 this._queryController ||= new WI.CSSQueryController(this._values); 264 265 return this._queryController.executeQuery(query); 256 266 } 257 267 -
trunk/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js
r285615 r286611 32 32 WI.CSSKeywordCompletions = {}; 33 33 34 WI.CSSKeywordCompletions.forPartialPropertyName = function(text, {caretPosition, allowEmptyPrefix } = {})34 WI.CSSKeywordCompletions.forPartialPropertyName = function(text, {caretPosition, allowEmptyPrefix, useFuzzyMatching} = {}) 35 35 { 36 36 caretPosition ??= text.length; … … 43 43 if (!text.length && allowEmptyPrefix) 44 44 return {prefix: text, completions: WI.CSSCompletions.cssNameCompletions.values}; 45 return {prefix: text, completions:WI.CSSCompletions.cssNameCompletions.startsWith(text)}; 45 46 let completions; 47 if (useFuzzyMatching) 48 completions = WI.CSSCompletions.cssNameCompletions.executeQuery(text); 49 else 50 completions = WI.CSSCompletions.cssNameCompletions.startsWith(text); 51 52 return {prefix: text, completions}; 46 53 }; 47 54 48 WI.CSSKeywordCompletions.forPartialPropertyValue = function(text, propertyName, {caretPosition, additionalFunctionValueCompletionsProvider } = {})55 WI.CSSKeywordCompletions.forPartialPropertyValue = function(text, propertyName, {caretPosition, additionalFunctionValueCompletionsProvider, useFuzzyMatching} = {}) 49 56 { 50 57 caretPosition ??= text.length; … … 87 94 return {prefix: "", completions: []}; 88 95 89 // If the current token value is a comma or open ingparenthesis, treat it as if we are at the start of a new token.96 // If the current token value is a comma or open parenthesis, treat it as if we are at the start of a new token. 90 97 if (currentTokenValue === "(" || currentTokenValue === ",") 91 98 currentTokenValue = ""; 99 100 // It's not valid CSS to append completions immediately after a closing parenthesis. 101 let tokenBeforeCaret = tokens[indexOfTokenAtCaret - 1]; 102 if (currentTokenValue === ")" || tokenBeforeCaret?.value === ")") 103 return {prefix: "", completions: []}; 92 104 93 105 let functionName = null; … … 109 121 } 110 122 123 let valueCompletions; 111 124 if (functionName) { 112 let completions = WI.CSSKeywordCompletions.forFunction(functionName); 113 let contextualValueCompletions = additionalFunctionValueCompletionsProvider?.(functionName) || []; 114 completions.addValues(contextualValueCompletions); 115 return {prefix: currentTokenValue, completions: completions.startsWith(currentTokenValue)}; 116 } 117 118 return {prefix: currentTokenValue, completions: WI.CSSKeywordCompletions.forProperty(propertyName).startsWith(currentTokenValue)}; 125 valueCompletions = WI.CSSKeywordCompletions.forFunction(functionName); 126 valueCompletions.addValues(additionalFunctionValueCompletionsProvider?.(functionName) ?? []); 127 } else 128 valueCompletions = WI.CSSKeywordCompletions.forProperty(propertyName); 129 130 let completions; 131 if (useFuzzyMatching) 132 completions = valueCompletions.executeQuery(currentTokenValue); 133 else 134 completions = valueCompletions.startsWith(currentTokenValue); 135 136 return {prefix: currentTokenValue, completions}; 119 137 }; 120 138 -
trunk/Source/WebInspectorUI/UserInterface/Models/QueryResult.js
r285711 r286611 37 37 38 38 get value() { return this._value; } 39 get matches() { return this._matches; } 39 40 40 41 get rank() -
trunk/Source/WebInspectorUI/UserInterface/Test.html
r285711 r286611 254 254 <script src="Controllers/BrowserManager.js"></script> 255 255 <script src="Controllers/CSSManager.js"></script> 256 <script src="Controllers/CSSQueryController.js"></script> 256 257 <script src="Controllers/CanvasManager.js"></script> 257 258 <script src="Controllers/ConsoleManager.js"></script> -
trunk/Source/WebInspectorUI/UserInterface/Views/CompletionSuggestionsView.css
r239760 r286611 71 71 } 72 72 73 .completion-suggestions-container > .item > .matched { 74 font-weight: bold; 75 } 76 73 77 .completion-suggestions-container:not(:active) > .item.selected, 74 78 .completion-suggestions-container > .item:active { -
trunk/Source/WebInspectorUI/UserInterface/Views/CompletionSuggestionsView.js
r249863 r286611 33 33 this._preventBlur = preventBlur || false; 34 34 35 this._completions = []; 35 36 this._selectedIndex = NaN; 36 37 this._moveIntervalIdentifier = null; … … 90 91 ++this.selectedIndex; 91 92 92 var selectedItemElement = this._selectedItemElement; 93 if (selectedItemElement && this._delegate && typeof this._delegate.completionSuggestionsSelectedCompletion === "function") 94 this._delegate.completionSuggestionsSelectedCompletion(this, selectedItemElement.textContent); 93 if (this._completions[this.selectedIndex]) 94 this._delegate?.completionSuggestionsSelectedCompletion?.(this, this.getCompletionText(this._completions[this.selectedIndex])); 95 95 } 96 96 … … 102 102 --this.selectedIndex; 103 103 104 var selectedItemElement = this._selectedItemElement; 105 if (selectedItemElement && this._delegate && typeof this._delegate.completionSuggestionsSelectedCompletion === "function") 106 this._delegate.completionSuggestionsSelectedCompletion(this, selectedItemElement.textContent); 104 if (this._completions[this.selectedIndex]) 105 this._delegate?.completionSuggestionsSelectedCompletion?.(this, this.getCompletionText(this._completions[this.selectedIndex])); 107 106 } 108 107 … … 176 175 { 177 176 this._containerElement.removeChildren(); 177 this._completions = completions; 178 178 179 179 if (typeof selectedIndex === "number") 180 180 this._selectedIndex = selectedIndex; 181 181 182 for ( var i = 0; i < completions.length; ++i) {182 for (let [index, completion] of completions.entries()) { 183 183 var itemElement = document.createElement("div"); 184 184 itemElement.classList.add("item"); 185 itemElement.classList.toggle("selected", i === this._selectedIndex); 186 itemElement.textContent = completions[i]; 185 itemElement.classList.toggle("selected", index === this._selectedIndex); 186 187 if (typeof completion === "string") 188 itemElement.textContent = completion; 189 else if (completion instanceof WI.QueryResult) 190 itemElement.appendChild(this._createMatchedCompletionFragment(completion.value, completion.matchingTextRanges)); 191 187 192 this._containerElement.appendChild(itemElement); 188 189 if (this._delegate && typeof this._delegate.completionSuggestionsViewCustomizeCompletionElement === "function") 190 this._delegate.completionSuggestionsViewCustomizeCompletionElement(this, itemElement, completions[i]); 193 this._delegate?.completionSuggestionsViewCustomizeCompletionElement?.(this, itemElement, completion); 191 194 } 195 } 196 197 getCompletionText(completion) 198 { 199 console.assert(typeof completion === "string" || completion instanceof WI.QueryResult, completion); 200 201 if (typeof completion === "string") 202 return completion; 203 204 if (completion instanceof WI.QueryResult) 205 return completion.value; 206 207 return ""; 192 208 } 193 209 … … 204 220 } 205 221 222 _createMatchedCompletionFragment(completionText, matchingTextRanges) 223 { 224 let completionFragment = document.createDocumentFragment(); 225 let lastIndex = 0; 226 for (let textRange of matchingTextRanges) { 227 console.assert(textRange.startColumn >= 0 && textRange.startColumn < completionText.length, textRange); 228 console.assert(textRange.endColumn > 0 && textRange.endColumn <= completionText.length, textRange); 229 console.assert(textRange.startColumn < textRange.endColumn); 230 231 if (textRange.startColumn > lastIndex) 232 completionFragment.append(completionText.substring(lastIndex, textRange.startColumn)); 233 234 let matchedSpan = document.createElement("span"); 235 matchedSpan.classList.add("matched"); 236 matchedSpan.append(completionText.substring(textRange.startColumn, textRange.endColumn)); 237 completionFragment.append(matchedSpan); 238 lastIndex = textRange.endColumn; 239 } 240 241 if (lastIndex < completionText.length) 242 completionFragment.append(completionText.substring(lastIndex, completionText.length)); 243 244 return completionFragment; 245 } 246 206 247 _mouseDown(event) 207 248 { -
trunk/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js
r283212 r286611 398 398 stylesGroup.addSetting(WI.settings.experimentalEnableStylesJumpToEffective, WI.UIString("Show jump to effective property button")); 399 399 stylesGroup.addSetting(WI.settings.experimentalEnableStylesJumpToVariableDeclaration, WI.UIString("Show jump to variable declaration button")); 400 stylesGroup.addSetting(WI.settings.experimentalCSSCompletionFuzzyMatching, WI.UIString("Use fuzzy matching for completion suggestions")); 400 401 401 402 experimentalSettingsView.addSeparator(); -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js
r285983 r286611 937 937 _handleNameChange() 938 938 { 939 this._property.name = this._name Element.textContent.trim();939 this._property.name = this._nameTextField.value; 940 940 } 941 941 942 942 _handleValueChange() 943 943 { 944 let value = this._value Element.textContent;944 let value = this._valueTextField.value; 945 945 946 946 this._property.rawValue = value.trim(); … … 976 976 } 977 977 978 _nameCompletionDataProvider(text, {caretPosition, allowEmptyPrefix}= {})979 { 980 return WI.CSSKeywordCompletions.forPartialPropertyName(text, {caretPosition, allowEmptyPrefix});978 _nameCompletionDataProvider(text, options = {}) 979 { 980 return WI.CSSKeywordCompletions.forPartialPropertyName(text, options); 981 981 } 982 982 … … 1000 1000 } 1001 1001 1002 _valueCompletionDataProvider(text, {caretPosition, allowEmptyPrefix} = {}) 1003 { 1004 return WI.CSSKeywordCompletions.forPartialPropertyValue(text, this._nameElement.textContent.trim(), {caretPosition, additionalFunctionValueCompletionsProvider: this.additionalFunctionValueCompletionsProvider.bind(this)}); 1002 _valueCompletionDataProvider(text, options = {}) 1003 { 1004 options.additionalFunctionValueCompletionsProvider = this.additionalFunctionValueCompletionsProvider.bind(this); 1005 return WI.CSSKeywordCompletions.forPartialPropertyValue(text, this._nameElement.textContent.trim(), options); 1005 1006 } 1006 1007 -
trunk/Source/WebInspectorUI/UserInterface/Views/SpreadsheetTextField.js
r285851 r286611 30 30 this._delegate = delegate; 31 31 this._element = element; 32 this._pendingValue = null; 32 33 33 34 this._completionProvider = completionProvider || null; … … 53 54 this._valueBeforeEditing = ""; 54 55 this._completionPrefix = ""; 56 this._completionText = ""; 55 57 this._controlSpaceKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, WI.KeyboardShortcut.Key.Space); 56 58 } … … 62 64 get editing() { return this._editing; } 63 65 64 get value() { return this._element.textContent; } 65 set value(value) { this._element.textContent = value; } 66 get value() 67 { 68 return this._pendingValue ?? this._element.textContent; 69 } 70 71 set value(value) 72 { 73 this._element.textContent = value; 74 75 this._pendingValue = null; 76 } 66 77 67 78 valueWithoutSuggestion() … … 95 106 96 107 this._suggestionHintElement.remove(); 97 98 // Removing the suggestion hint element may leave the contents of `_element` fragmented into multiple text nodes.99 this._combineEditorElementChildren();100 108 } 101 109 … … 131 139 this._editing = false; 132 140 this._valueBeforeEditing = ""; 141 this._pendingValue = null; 142 this._completionText = ""; 133 143 this._element.classList.remove("editing"); 134 144 this._element.contentEditable = false; … … 144 154 this._suggestionsView.hide(); 145 155 146 let hadSuggestionHint = !!this.suggestionHint; 156 let hadCompletionText = this._completionText.length > 0; 157 158 // Resetting the suggestion hint removes any suggestion hint element that is attached. 147 159 this.suggestionHint = ""; 148 if (hadSuggestionHint && this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") 149 this._delegate.spreadsheetTextFieldDidChange(this); 160 this._completionText = ""; 161 162 if (hadCompletionText) { 163 this._pendingValue = this.valueWithoutSuggestion(); 164 this._delegate?.spreadsheetTextFieldDidChange?.(this); 165 } 150 166 } 151 167 … … 158 174 // CompletionSuggestionsView delegate 159 175 160 completionSuggestionsSelectedCompletion(suggestionsView, selectedText = "") 161 { 162 this.suggestionHint = selectedText.slice(this._completionPrefix.length); 163 164 if (this.suggestionHint.length) 165 this._reAttachSuggestionHint(); 176 completionSuggestionsSelectedCompletion(suggestionsView, completionText = "") 177 { 178 this._completionText = completionText; 179 180 if (this._completionText.startsWith(this._completionPrefix)) 181 this.suggestionHint = this._completionText.slice(this._completionPrefix.length); 182 else 183 this.suggestionHint = ""; 184 185 this._updatePendingValueWithCompletionText(); 166 186 167 187 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function") … … 169 189 } 170 190 171 completionSuggestionsClickedCompletion(suggestionsView, selectedText)172 { 173 this. suggestionHint = selectedText.slice(this._completionPrefix.length);174 175 this._apply CompletionHint({moveCaretToEndOfCompletion: true});191 completionSuggestionsClickedCompletion(suggestionsView, completionText) 192 { 193 this._completionText = completionText; 194 this._updatePendingValueWithCompletionText(); 195 this._applyPendingValue({moveCaretToEndOfCompletion: true}); 176 196 this.discardCompletion(); 177 197 … … 220 240 return; 221 241 222 this._apply CompletionHint();242 this._applyPendingValue(); 223 243 this.discardCompletion(); 224 244 … … 253 273 if (isEnterKey || isTabKey) { 254 274 event.stop(); 255 this._apply CompletionHint();275 this._applyPendingValue(); 256 276 257 277 let direction = (isTabKey && event.shiftKey) ? "backward" : "forward"; … … 339 359 } 340 360 341 if (event.key === "ArrowRight" && this. suggestionHint.length) {361 if (event.key === "ArrowRight" && this._completionText.length) { 342 362 let selection = window.getSelection(); 343 363 344 364 if (selection.isCollapsed) { 345 365 event.stop(); 346 this._apply CompletionHint({moveCaretToEndOfCompletion: true});366 this._applyPendingValue({moveCaretToEndOfCompletion: true}); 347 367 348 368 // When completing "background", don't hide the completion popover. … … 359 379 if (event.key === "Escape" && this._suggestionsView.visible) { 360 380 event.stop(); 361 362 let willChange = !!this.suggestionHint;363 381 this.discardCompletion(); 364 382 365 if (willChange && this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function")366 this._delegate.spreadsheetTextFieldDidChange(this);367 368 383 return true; 369 384 } 370 385 371 if (event.key === "ArrowLeft" && (this. suggestionHint|| this._suggestionsView.visible)) {386 if (event.key === "ArrowLeft" && (this._completionText.length || this._suggestionsView.visible)) { 372 387 this.discardCompletion(); 373 388 374 if (this._delegate && typeof this._delegate.spreadsheetTextFieldDidChange === "function")375 this._delegate.spreadsheetTextFieldDidChange(this);376 389 return true; 377 390 } … … 406 419 return; 407 420 421 this._pendingValue = this.valueWithoutSuggestion().trim(); 408 422 this._preventDiscardingCompletionsOnKeyUp = true; 409 423 this._updateCompletions(); … … 418 432 return; 419 433 434 let useFuzzyMatching = WI.settings.experimentalCSSCompletionFuzzyMatching.value; 420 435 let valueWithoutSuggestion = this.valueWithoutSuggestion(); 421 let {completions, prefix} = this._completionProvider(valueWithoutSuggestion, {allowEmptyPrefix: forceCompletions, caretPosition: this._getCaretPosition() });436 let {completions, prefix} = this._completionProvider(valueWithoutSuggestion, {allowEmptyPrefix: forceCompletions, caretPosition: this._getCaretPosition(), useFuzzyMatching}); 422 437 this._completionPrefix = prefix; 423 438 … … 428 443 429 444 // No need to show the completion popover with only one item that matches the entered value. 430 if (completions.length === 1 && completions[0]=== valueWithoutSuggestion) {445 if (completions.length === 1 && this._suggestionsView.getCompletionText(completions[0]) === valueWithoutSuggestion) { 431 446 this.discardCompletion(); 432 447 return; … … 441 456 this._suggestionsView.update(completions); 442 457 443 if (completions.length === 1) { 444 // No need to show the completion popover that matches the suggestion hint. 458 if (completions.length === 1 && this._suggestionsView.getCompletionText(completions[0]).startsWith(this._completionPrefix)) { 459 // No need to show the completion popover with only one item that begins with the completion prefix. 460 // When using fuzzy matching, the completion prefix may not occur at the beginning of the suggestion. 445 461 this._suggestionsView.hide(); 446 462 } else … … 531 547 } 532 548 533 _applyCompletionHint({moveCaretToEndOfCompletion} = {}) 534 { 535 if (!this._completionProvider || !this.suggestionHint) 536 return; 537 538 this._combineEditorElementChildren({newCaretPosition: moveCaretToEndOfCompletion ? this._getCaretPosition() + this.suggestionHint.length : null}); 539 } 540 541 _combineEditorElementChildren({newCaretPosition} = {}) 542 { 543 newCaretPosition ??= this._getCaretPosition(); 544 545 // Setting the textContent of the element to its current textContent will take the text from the multiple 546 // potential child nodes (potentially a suggestion hint node and some number of existing text nodes) and turn 547 // them into a single text node within the element. 548 this._element.textContent = this._element.textContent; 549 _applyPendingValue({moveCaretToEndOfCompletion} = {}) 550 { 551 if (!this._pendingValue) 552 return; 553 554 let caretPosition = this._getCaretPosition(); 555 let newCaretPosition = moveCaretToEndOfCompletion ? caretPosition - this._completionPrefix.length + this._completionText.length : caretPosition; 556 557 // Setting the value collapses the text selection. Get the caret position before doing this. 558 this.value = this._pendingValue; 549 559 550 560 if (this._element.textContent.length) { … … 554 564 } 555 565 566 _updatePendingValueWithCompletionText() 567 { 568 let caretPosition = this._getCaretPosition(); 569 let value = this.valueWithoutSuggestion(); 570 571 this._pendingValue = value.slice(0, caretPosition - this._completionPrefix.length) + this._completionText + value.slice(caretPosition + 1, value.length); 572 } 573 556 574 _reAttachSuggestionHint() 557 575 {
Note: See TracChangeset
for help on using the changeset viewer.