From d99153a1a6fd659af281e71216af3df459ef8e61 Mon Sep 17 00:00:00 2001 From: Mihai Sucan Date: Fri, 28 Jun 2013 20:56:33 +0300 Subject: [PATCH] Bug 812618 - Autocomplete at cursor location; r=robcee --- ...webconsole_bug_585991_autocomplete_keys.js | 47 ++++++++++++++++++- browser/devtools/webconsole/webconsole.js | 16 +++++-- toolkit/devtools/server/actors/webconsole.js | 3 +- .../devtools/webconsole/WebConsoleUtils.jsm | 19 +++++--- .../devtools/webconsole/test/test_jsterm.html | 43 +++++++++++++++-- 5 files changed, 112 insertions(+), 16 deletions(-) diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js index 21dcc603b1d..6e0e1cb69a2 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js @@ -279,5 +279,50 @@ function popupHideAfterReturnWithNoSelection() is(jsterm.history[jsterm.history.length-1], "window.testBug", "jsterm history is correct"); - executeSoon(finishTest); + executeSoon(testCompletionInText); +} + +function testCompletionInText() +{ + info("test that completion works inside text, see bug 812618"); + + popup._panel.addEventListener("popupshown", function onShown() { + popup._panel.removeEventListener("popupshown", onShown); + + ok(popup.isOpen, "popup is open"); + is(popup.itemCount, 2, "popup.itemCount is correct"); + + EventUtils.synthesizeKey("VK_DOWN", {}); + EventUtils.synthesizeKey("VK_DOWN", {}); + is(popup.selectedIndex, 0, "popup.selectedIndex is correct"); + ok(!completeNode.value, "completeNode.value is empty"); + + let items = popup.getItems().reverse().map(e => e.label); + let sameItems = items.every((prop, index) => + ["testBug873250a", "testBug873250b"][index] === prop); + ok(sameItems, "getItems returns the items we expect"); + + info("press Tab and wait for popup to hide"); + popup._panel.addEventListener("popuphidden", popupHideAfterCompletionInText); + EventUtils.synthesizeKey("VK_TAB", {}); + }); + + jsterm.setInputValue("dump(window.testBu)"); + inputNode.selectionStart = inputNode.selectionEnd = 18; + EventUtils.synthesizeKey("g", {}); +} + +function popupHideAfterCompletionInText() +{ + // At this point the completion suggestion should be accepted. + popup._panel.removeEventListener("popuphidden", popupHideAfterCompletionInText); + + ok(!popup.isOpen, "popup is not open"); + is(inputNode.value, "dump(window.testBug873250b)", + "completion was successful after VK_TAB"); + is(inputNode.selectionStart, 26, "cursor location is correct"); + is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)"); + ok(!completeNode.value, "completeNode is empty"); + + finishTest(); } diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js index 21d4f1a4016..23b91a70c00 100644 --- a/browser/devtools/webconsole/webconsole.js +++ b/browser/devtools/webconsole/webconsole.js @@ -4077,9 +4077,8 @@ JSTerm.prototype = { return false; } - // Only complete if the selection is empty and at the end of the input. - if (inputNode.selectionStart == inputNode.selectionEnd && - inputNode.selectionEnd != inputValue.length) { + // Only complete if the selection is empty. + if (inputNode.selectionStart != inputNode.selectionEnd) { this.clearCompletion(); return false; } @@ -4215,6 +4214,11 @@ JSTerm.prototype = { onAutocompleteSelect: function JSTF_onAutocompleteSelect() { + // Render the suggestion only if the cursor is at the end of the input. + if (this.inputNode.selectionStart != this.inputNode.value.length) { + return; + } + let currentItem = this.autocompletePopup.selectedItem; if (currentItem && this.lastCompletion.value) { let suffix = currentItem.label.substring(this.lastCompletion. @@ -4256,7 +4260,11 @@ JSTerm.prototype = { if (currentItem && this.lastCompletion.value) { let suffix = currentItem.label.substring(this.lastCompletion. matchProp.length); - this.setInputValue(this.inputNode.value + suffix); + let cursor = this.inputNode.selectionStart; + let value = this.inputNode.value; + this.setInputValue(value.substr(0, cursor) + suffix + value.substr(cursor)); + let newCursor = cursor + suffix.length; + this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor; updated = true; } diff --git a/toolkit/devtools/server/actors/webconsole.js b/toolkit/devtools/server/actors/webconsole.js index 3e7e2bd9fc4..d85c8f2f6e5 100644 --- a/toolkit/devtools/server/actors/webconsole.js +++ b/toolkit/devtools/server/actors/webconsole.js @@ -626,7 +626,8 @@ WebConsoleActor.prototype = { // TODO: Bug 842682 - use the debugger API for autocomplete in the Web // Console, and provide suggestions from the selected debugger stack frame. - let result = JSPropertyProvider(this.window, aRequest.text) || {}; + let result = JSPropertyProvider(this.window, aRequest.text, + aRequest.cursor) || {}; return { from: this.actorID, matches: result.matches || [], diff --git a/toolkit/devtools/webconsole/WebConsoleUtils.jsm b/toolkit/devtools/webconsole/WebConsoleUtils.jsm index 3f560a0572d..d061d54819f 100644 --- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm +++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm @@ -727,10 +727,12 @@ function findCompletionBeginning(aStr) * * @param object aScope * Scope to use for the completion. - * * @param string aInputValue * Value that should be completed. - * + * @param number [aCursor=aInputValue.length] + * Optional offset in the input where the cursor is located. If this is + * omitted then the cursor is assumed to be at the end of the input + * value. * @returns null or object * If no completion valued could be computed, null is returned, * otherwise a object with the following form is returned: @@ -740,13 +742,18 @@ function findCompletionBeginning(aStr) * the matches-strings. * } */ -function JSPropertyProvider(aScope, aInputValue) +function JSPropertyProvider(aScope, aInputValue, aCursor) { + if (aCursor === undefined) { + aCursor = aInputValue.length; + } + + let inputValue = aInputValue.substring(0, aCursor); let obj = WCU.unwrap(aScope); - // Analyse the aInputValue and find the beginning of the last part that + // Analyse the inputValue and find the beginning of the last part that // should be completed. - let beginning = findCompletionBeginning(aInputValue); + let beginning = findCompletionBeginning(inputValue); // There was an error analysing the string. if (beginning.err) { @@ -759,7 +766,7 @@ function JSPropertyProvider(aScope, aInputValue) return null; } - let completionPart = aInputValue.substring(beginning.startPos); + let completionPart = inputValue.substring(beginning.startPos); // Don't complete on just an empty string. if (completionPart.trim() == "") { diff --git a/toolkit/devtools/webconsole/test/test_jsterm.html b/toolkit/devtools/webconsole/test/test_jsterm.html index bf69f18a881..bc3637312f3 100644 --- a/toolkit/devtools/webconsole/test/test_jsterm.html +++ b/toolkit/devtools/webconsole/test/test_jsterm.html @@ -37,15 +37,16 @@ function onAttach(aState, aResponse) gState = aState; - let tests = [doAutocomplete1, doAutocomplete2, doSimpleEval, doWindowEval, - doEvalWithException, doEvalWithHelper, doEvalString, doEvalLongString]; + let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3, + doAutocomplete4, doSimpleEval, doWindowEval, doEvalWithException, + doEvalWithHelper, doEvalString, doEvalLongString]; runTests(tests, testEnd); } function doAutocomplete1() { info("test autocomplete for 'window.foo'"); - gState.client.autocomplete("window.foo", 0, onAutocomplete1); + gState.client.autocomplete("window.foo", 10, onAutocomplete1); } function onAutocomplete1(aResponse) @@ -62,7 +63,7 @@ function onAutocomplete1(aResponse) function doAutocomplete2() { info("test autocomplete for 'window.foobarObject.'"); - gState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2); + gState.client.autocomplete("window.foobarObject.", 20, onAutocomplete2); } function onAutocomplete2(aResponse) @@ -77,6 +78,40 @@ function onAutocomplete2(aResponse) nextTest(); } +function doAutocomplete3() +{ + // Check that completion suggestions are offered inside the string. + info("test autocomplete for 'dump(window.foobarObject.)'"); + gState.client.autocomplete("dump(window.foobarObject.)", 25, onAutocomplete3); +} + +function onAutocomplete3(aResponse) +{ + let matches = aResponse.matches; + + ok(!aResponse.matchProp, "matchProp"); + is(matches.length, 7, "matches.length"); + checkObject(matches, + ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]); + + nextTest(); +} + +function doAutocomplete4() +{ + // Check that completion requests can have no suggestions. + info("test autocomplete for 'dump(window.foobarObject.)'"); + gState.client.autocomplete("dump(window.foobarObject.)", 26, onAutocomplete4); +} + +function onAutocomplete4(aResponse) +{ + ok(!aResponse.matchProp, "matchProp"); + is(aResponse.matches.length, 0, "matches.length"); + + nextTest(); +} + function doSimpleEval() { info("test eval '2+2'");