From cd8796ecf97aadd461fa507c4172f09b8c61301a Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Tue, 17 Mar 2015 11:42:00 -0400 Subject: [PATCH] Bug 964939 - Allow clicking on autocomplete items for CSS source editor. r=pbrosset --- browser/devtools/shared/autocomplete-popup.js | 5 ++ browser/devtools/sourceeditor/autocomplete.js | 37 ++++++----- .../devtools/sourceeditor/test/browser.ini | 1 + .../browser_editor_autocomplete_events.js | 62 ++++++++++++++++++ browser/devtools/sourceeditor/test/head.js | 63 ++++++++++++++++--- 5 files changed, 143 insertions(+), 25 deletions(-) create mode 100644 browser/devtools/sourceeditor/test/browser_editor_autocomplete_events.js diff --git a/browser/devtools/shared/autocomplete-popup.js b/browser/devtools/shared/autocomplete-popup.js index b2828ac8396e..4c8bff6b5286 100644 --- a/browser/devtools/shared/autocomplete-popup.js +++ b/browser/devtools/shared/autocomplete-popup.js @@ -10,6 +10,7 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); +const events = require("devtools/toolkit/event-emitter"); /** * Autocomplete popup UI implementation. @@ -109,6 +110,8 @@ function AutocompletePopup(aDocument, aOptions = {}) this._list.addEventListener("keypress", this.onKeypress, false); } this._itemIdCounter = 0; + + events.decorate(this); } exports.AutocompletePopup = AutocompletePopup; @@ -144,6 +147,8 @@ AutocompletePopup.prototype = { if (this.autoSelect) { this.selectFirstItem(); } + + this.emit("popup-opened"); }, /** diff --git a/browser/devtools/sourceeditor/autocomplete.js b/browser/devtools/sourceeditor/autocomplete.js index 3d032ba6d387..3a199fc94f90 100644 --- a/browser/devtools/sourceeditor/autocomplete.js +++ b/browser/devtools/sourceeditor/autocomplete.js @@ -104,11 +104,30 @@ function initializeAutoCompletion(ctx, options = {}) { completer = new cssAutoCompleter({walker: options.walker}); } + function insertSelectedPopupItem() { + let autocompleteState = autocompleteMap.get(ed); + if (!popup || !popup.isOpen || !autocompleteState) { + return; + } + + if (!autocompleteState.suggestionInsertedOnce && popup.selectedItem) { + autocompleteMap.get(ed).insertingSuggestion = true; + let {label, preLabel, text} = popup.selectedItem; + let cur = ed.getCursor(); + ed.replaceText(text.slice(preLabel.length), cur, cur); + } + + popup.hidePopup(); + ed.emit("popup-hidden"); // This event is used in tests. + return true; + } + let popup = new AutocompletePopup(win.parent.document, { position: "after_start", fixedWidth: true, theme: "auto", - autoSelect: true + autoSelect: true, + onClick: insertSelectedPopupItem }); let cycle = (reverse) => { @@ -126,20 +145,8 @@ function initializeAutoCompletion(ctx, options = {}) { "Shift-Tab": cycle.bind(null, true), "Up": cycle.bind(null, true), "Enter": () => { - if (popup && popup.isOpen) { - if (!autocompleteMap.get(ed).suggestionInsertedOnce) { - autocompleteMap.get(ed).insertingSuggestion = true; - let {label, preLabel, text} = popup.getItemAtIndex(0); - let cur = ed.getCursor(); - ed.replaceText(text.slice(preLabel.length), cur, cur); - } - popup.hidePopup(); - // This event is used in tests - ed.emit("popup-hidden"); - return; - } - - return CodeMirror.Pass; + let wasHandled = insertSelectedPopupItem(); + return wasHandled ? true : CodeMirror.Pass; } }; let autoCompleteCallback = autoComplete.bind(null, ctx); diff --git a/browser/devtools/sourceeditor/test/browser.ini b/browser/devtools/sourceeditor/test/browser.ini index df7f6a8c3103..dee57b02615a 100644 --- a/browser/devtools/sourceeditor/test/browser.ini +++ b/browser/devtools/sourceeditor/test/browser.ini @@ -21,6 +21,7 @@ support-files = helper_codemirror_runner.js [browser_editor_autocomplete_basic.js] +[browser_editor_autocomplete_events.js] [browser_editor_autocomplete_js.js] [browser_editor_basic.js] [browser_editor_cursor.js] diff --git a/browser/devtools/sourceeditor/test/browser_editor_autocomplete_events.js b/browser/devtools/sourceeditor/test/browser_editor_autocomplete_events.js new file mode 100644 index 000000000000..77aefe4ed906 --- /dev/null +++ b/browser/devtools/sourceeditor/test/browser_editor_autocomplete_events.js @@ -0,0 +1,62 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const {InspectorFront} = require("devtools/server/actors/inspector"); +const AUTOCOMPLETION_PREF = "devtools.editor.autocomplete"; +const TEST_URI = "data:text/html;charset=UTF-8,"; + +add_task(function*() { + yield promiseTab(TEST_URI); + yield runTests(); +}); + +function* runTests() { + let target = devtools.TargetFactory.forTab(gBrowser.selectedTab); + yield target.makeRemote(); + let inspector = InspectorFront(target.client, target.form); + let walker = yield inspector.getWalker(); + let {ed, win, edWin} = yield setup(null, { + autocomplete: true, + mode: Editor.modes.css, + autocompleteOpts: {walker: walker} + }); + yield testMouse(ed, edWin); + yield testKeyboard(ed, edWin); + teardown(ed, win); +} + +function* testKeyboard(ed, win) { + ed.focus(); + ed.setText("b"); + ed.setCursor({line: 1, ch: 1}); + + let popupOpened = ed.getAutocompletionPopup().once("popup-opened"); + + let autocompleteKey = Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase(); + EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win); + + info ("Waiting for popup to be opened"); + yield popupOpened; + + EventUtils.synthesizeKey("VK_RETURN", { }, win); + is (ed.getText(), "b1", "Editor text has been updated"); +} + +function* testMouse(ed, win) { + ed.focus(); + ed.setText("b"); + ed.setCursor({line: 1, ch: 1}); + + let popupOpened = ed.getAutocompletionPopup().once("popup-opened"); + + let autocompleteKey = Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase(); + EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win); + + info ("Waiting for popup to be opened"); + yield popupOpened; + ed.getAutocompletionPopup()._list.firstChild.click(); + is (ed.getText(), "b1", "Editor text has been updated"); +} diff --git a/browser/devtools/sourceeditor/test/head.js b/browser/devtools/sourceeditor/test/head.js index 3698eda78108..78bb33ec28eb 100644 --- a/browser/devtools/sourceeditor/test/head.js +++ b/browser/devtools/sourceeditor/test/head.js @@ -14,32 +14,71 @@ SimpleTest.registerCleanupFunction(() => { gDevTools.testing = false; }); -function setup(cb) { +/** + * Open a new tab at a URL and call a callback on load + */ +function addTab(aURL, aCallback) { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(); + content.location = aURL; + + let tab = gBrowser.selectedTab; + let browser = gBrowser.getBrowserForTab(tab); + + function onTabLoad() { + browser.removeEventListener("load", onTabLoad, true); + aCallback(browser, tab, browser.contentDocument); + } + + browser.addEventListener("load", onTabLoad, true); +} + +function promiseTab(aURL) { + return new Promise(resolve => + addTab(aURL, resolve)); +} + +function setup(cb, additionalOpts = {}) { + cb = cb || function() {}; + let def = promise.defer(); const opt = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no"; - const url = "data:text/xml;charset=UTF-8," + + const url = "data:application/vnd.mozilla.xul+xml;charset=UTF-8," + "" + ""; let win = Services.ww.openWindow(null, url, "_blank", opt, null); + let opts = { + value: "Hello.", + lineNumbers: true, + foldGutter: true, + gutters: [ "CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter" ] + } + for (let o in additionalOpts) { + opts[o] = additionalOpts[o]; + } win.addEventListener("load", function onLoad() { win.removeEventListener("load", onLoad, false); waitForFocus(function () { let box = win.document.querySelector("box"); - let editor = new Editor({ - value: "Hello.", - lineNumbers: true, - foldGutter: true, - gutters: [ "CodeMirror-linenumbers", "breakpoints", "CodeMirror-foldgutter" ] - }); + let editor = new Editor(opts); editor.appendTo(box) - .then(() => cb(editor, win)) - .then(null, (err) => ok(false, err.message)); + .then(() => { + def.resolve({ + ed: editor, + win: win, + edWin: editor.container.contentWindow.wrappedJSObject + }); + cb(editor, win); + }, err => ok(false, err.message)); }, win); }, false); + + return def.promise; } function ch(exp, act, label) { @@ -50,6 +89,10 @@ function ch(exp, act, label) { function teardown(ed, win) { ed.destroy(); win.close(); + + while (gBrowser.tabs.length > 1) { + gBrowser.removeCurrentTab(); + } finish(); }