Bug 1029511 - Source Editor: Add ability to toggle autocomplete option. r=vporof

This commit is contained in:
Brian Grinstead 2014-06-30 11:23:00 +02:00
parent 3debf3b4d5
commit 82839e29d0
9 changed files with 224 additions and 27 deletions

View File

@ -1436,6 +1436,7 @@ pref("devtools.editor.expandtab", true);
pref("devtools.editor.keymap", "default");
pref("devtools.editor.autoclosebrackets", true);
pref("devtools.editor.detectindentation", true);
pref("devtools.editor.autocomplete", true);
// Enable the Font Inspector
pref("devtools.fontinspector.enabled", true);

View File

@ -169,7 +169,6 @@ AutocompletePopup.prototype = {
if (this.isOpen) {
this.hidePopup();
}
this.clearItems();
if (this.onSelect) {
this._list.removeEventListener("select", this.onSelect, false);
@ -187,6 +186,8 @@ AutocompletePopup.prototype = {
gDevTools.off("pref-changed", this._handleThemeChange);
}
this._list.remove();
this._panel.remove();
this._document = null;
this._list = null;
this._panel = null;

View File

@ -16,8 +16,11 @@ const privates = new WeakMap();
/**
* Prepares an editor instance for autocompletion.
*/
function setupAutoCompletion(ctx, options) {
function initializeAutoCompletion(ctx, options = {}) {
let { cm, ed, Editor } = ctx;
if (privates.has(ed)) {
return;
}
let win = ed.container.contentWindow.wrappedJSObject;
let { CodeMirror, document } = win;
@ -59,11 +62,10 @@ function setupAutoCompletion(ctx, options) {
return tip;
}
});
cm.on("cursorActivity", (cm) => {
cm.tern.updateArgHints(cm);
});
let keyMap = {};
let updateArgHintsCallback = cm.tern.updateArgHints.bind(cm.tern, cm);
cm.on("cursorActivity", updateArgHintsCallback);
keyMap[autocompleteKey] = (cm) => {
cm.tern.getHint(cm, (data) => {
@ -79,9 +81,22 @@ function setupAutoCompletion(ctx, options) {
ed.emit("show-information");
});
};
cm.addKeyMap(keyMap);
let destroyTern = function() {
ed.off("destroy", destroyTern);
cm.off("cursorActivity", updateArgHintsCallback);
cm.removeKeyMap(keyMap);
win.tern = cm.tern = null;
privates.delete(ed);
};
ed.on("destroy", destroyTern);
privates.set(ed, {
destroy: destroyTern
});
// TODO: Integrate tern autocompletion with this autocomplete API.
return;
} else if (ed.config.mode == Editor.modes.css) {
@ -126,27 +141,48 @@ function setupAutoCompletion(ctx, options) {
return CodeMirror.Pass;
}
};
keyMap[autocompleteKey] = cm => autoComplete(ctx);
let autoCompleteCallback = autoComplete.bind(null, ctx);
let keypressCallback = onEditorKeypress.bind(null, ctx);
keyMap[autocompleteKey] = autoCompleteCallback;
cm.addKeyMap(keyMap);
cm.on("keydown", (cm, e) => onEditorKeypress(ctx, e));
ed.on("change", () => autoComplete(ctx));
ed.on("destroy", () => {
cm.off("keydown", (cm, e) => onEditorKeypress(ctx, e));
ed.off("change", () => autoComplete(ctx));
cm.on("keydown", keypressCallback);
ed.on("change", autoCompleteCallback);
ed.on("destroy", destroy);
function destroy() {
ed.off("destroy", destroy);
cm.off("keydown", keypressCallback);
ed.off("change", autoCompleteCallback);
cm.removeKeyMap(keyMap);
popup.destroy();
popup = null;
completer = null;
});
keyMap = popup = completer = null;
privates.delete(ed);
}
privates.set(ed, {
popup: popup,
completer: completer,
keyMap: keyMap,
destroy: destroy,
insertingSuggestion: false,
suggestionInsertedOnce: false
});
}
/**
* Destroy autocompletion on an editor instance.
*/
function destroyAutoCompletion(ctx) {
let { ed } = ctx;
if (!privates.has(ed)) {
return;
}
let {destroy} = privates.get(ed);
destroy();
}
/**
* Provides suggestions to autocomplete the current token/word being typed.
*/
@ -226,7 +262,7 @@ function cycleSuggestions(ed, reverse) {
* onkeydown handler for the editor instance to prevent autocompleting on some
* keypresses.
*/
function onEditorKeypress({ ed, Editor }, event) {
function onEditorKeypress({ ed, Editor }, cm, event) {
let private = privates.get(ed);
// Do not try to autocomplete with multiple selections.
@ -283,7 +319,10 @@ function onEditorKeypress({ ed, Editor }, event) {
* Returns the private popup. This method is used by tests to test the feature.
*/
function getPopup({ ed }) {
return privates.get(ed).popup;
if (privates.has(ed))
return privates.get(ed).popup;
return null;
}
/**
@ -300,6 +339,7 @@ function getInfoAt({ ed }, caret) {
// Export functions
module.exports.setupAutoCompletion = setupAutoCompletion;
module.exports.initializeAutoCompletion = initializeAutoCompletion;
module.exports.destroyAutoCompletion = destroyAutoCompletion;
module.exports.getAutocompletionPopup = getPopup;
module.exports.getInfoAt = getInfoAt;

View File

@ -12,6 +12,7 @@ const TAB_SIZE = "devtools.editor.tabsize";
const EXPAND_TAB = "devtools.editor.expandtab";
const KEYMAP = "devtools.editor.keymap";
const AUTO_CLOSE = "devtools.editor.autoclosebrackets";
const AUTOCOMPLETE = "devtools.editor.autocomplete";
const DETECT_INDENT = "devtools.editor.detectindentation";
const DETECT_INDENT_MAX_LINES = 500;
const L10N_BUNDLE = "chrome://browser/locale/devtools/sourceeditor.properties";
@ -98,9 +99,7 @@ const CM_MAPPING = [
"clearHistory",
"openDialog",
"refresh",
"getScrollInfo",
"getOption",
"setOption"
"getScrollInfo"
];
const { cssProperties, cssValues, cssColors } = getCSSKeywords();
@ -360,10 +359,17 @@ Editor.prototype = {
/**
* Changes the value of a currently used highlighting mode.
* See Editor.modes for the list of all suppoert modes.
* See Editor.modes for the list of all supported modes.
*/
setMode: function (value) {
this.setOption("mode", value);
// If autocomplete was set up and the mode is changing, then
// turn it off and back on again so the proper mode can be used.
if (this.config.autocomplete) {
this.setOption("autocomplete", false);
this.setOption("autocomplete", true);
}
},
/**
@ -865,16 +871,54 @@ Editor.prototype = {
cm.refresh();
},
/**
* Sets an option for the editor. For most options it just defers to
* CodeMirror.setOption, but certain ones are maintained within the editor
* instance.
*/
setOption: function(o, v) {
let cm = editors.get(this);
if (o === "autocomplete") {
this.config.autocomplete = v;
this.setupAutoCompletion();
} else {
cm.setOption(o, v);
}
},
/**
* Gets an option for the editor. For most options it just defers to
* CodeMirror.getOption, but certain ones are maintained within the editor
* instance.
*/
getOption: function(o) {
let cm = editors.get(this);
if (o === "autocomplete") {
return this.config.autocomplete;
} else {
return cm.getOption(o);
}
},
/**
* Sets up autocompletion for the editor. Lazily imports the required
* dependencies because they vary by editor mode.
*
* Autocompletion is special, because we don't want to automatically use
* it just because it is preffed on (it still needs to be requested by the
* editor), but we do want to always disable it if it is preffed off.
*/
setupAutoCompletion: function (options = {}) {
if (this.config.autocomplete) {
// The autocomplete module will overwrite this.initializeAutoCompletion
// with a mode specific autocompletion handler.
if (!this.initializeAutoCompletion) {
this.extend(require("./autocomplete"));
// The autocomplete module will overwrite this.setupAutoCompletion with
// a mode specific autocompletion handler.
this.setupAutoCompletion(options);
}
if (this.config.autocomplete && Services.prefs.getBoolPref(AUTOCOMPLETE)) {
this.initializeAutoCompletion(options);
} else {
this.destroyAutoCompletion();
}
},

View File

@ -20,6 +20,8 @@ support-files =
vimemacs.html
head.js
[browser_editor_autocomplete_basic.js]
[browser_editor_autocomplete_js.js]
[browser_editor_basic.js]
[browser_editor_cursor.js]
[browser_editor_goto_line.js]

View File

@ -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 AUTOCOMPLETION_PREF = "devtools.editor.autocomplete";
// Test to make sure that different autocompletion modes can be created,
// switched, and destroyed. This doesn't test the actual autocompletion
// popups, only their integration with the editor.
function test() {
waitForExplicitFinish();
setup((ed, win) => {
let edWin = ed.container.contentWindow.wrappedJSObject;
testJS(ed, edWin);
testCSS(ed, edWin);
testPref(ed, edWin);
teardown(ed, win);
});
}
function testJS(ed, win) {
ok (!ed.getOption("autocomplete"), "Autocompletion is not set");
ok (!win.tern, "Tern is not defined on the window");
ed.setMode(Editor.modes.js);
ed.setOption("autocomplete", true);
ok (ed.getOption("autocomplete"), "Autocompletion is set");
ok (win.tern, "Tern is defined on the window");
}
function testCSS(ed, win) {
ok (ed.getOption("autocomplete"), "Autocompletion is set");
ok (win.tern, "Tern is currently defined on the window");
ed.setMode(Editor.modes.css);
ed.setOption("autocomplete", true);
ok (ed.getOption("autocomplete"), "Autocompletion is still set");
ok (!win.tern, "Tern is no longer defined on the window");
}
function testPref(ed, win) {
ed.setMode(Editor.modes.js);
ed.setOption("autocomplete", true);
ok (ed.getOption("autocomplete"), "Autocompletion is set");
ok (win.tern, "Tern is defined on the window");
info ("Preffing autocompletion off");
Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false);
ed.setupAutoCompletion();
ok (ed.getOption("autocomplete"), "Autocompletion is still set");
ok (!win.tern, "Tern is no longer defined on the window");
Services.prefs.clearUserPref(AUTOCOMPLETION_PREF);
}

View File

@ -0,0 +1,44 @@
/* 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";
// Test to make sure that JS autocompletion is opening popups.
function test() {
waitForExplicitFinish();
setup((ed, win) => {
let edWin = ed.container.contentWindow.wrappedJSObject;
testJS(ed, edWin).then(() => {
teardown(ed, win);
});
});
}
function testJS(ed, win) {
ok (!ed.getOption("autocomplete"), "Autocompletion is not set");
ok (!win.tern, "Tern is not defined on the window");
ed.setMode(Editor.modes.js);
ed.setOption("autocomplete", true);
ok (ed.getOption("autocomplete"), "Autocompletion is set");
ok (win.tern, "Tern is defined on the window");
ed.focus();
ed.setText("document.");
ed.setCursor({line: 0, ch: 9});
let waitForSuggestion = promise.defer();
ed.on("before-suggest", () => {
info("before-suggest has been triggered");
EventUtils.synthesizeKey("VK_ESCAPE", { }, win);
waitForSuggestion.resolve();
});
let autocompleteKey = Editor.keyFor("autocompletion", { noaccel: true }).toUpperCase();
EventUtils.synthesizeKey("VK_" + autocompleteKey, { ctrlKey: true }, win);
return waitForSuggestion.promise;
}

View File

@ -7,6 +7,7 @@
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { require } = devtools;
const Editor = require("devtools/sourceeditor/editor");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
gDevTools.testing = true;
SimpleTest.registerCleanupFunction(() => {

View File

@ -190,7 +190,9 @@ function testAutocompletionDisabled() {
function testEditorAddedDisabled(panel) {
info("Editor added, getting the source editor and starting tests");
panel.UI.editors[0].getSourceEditor().then(editor => {
ok(!editor.sourceEditor.getAutocompletionPopup,
is(editor.sourceEditor.getOption("autocomplete"), false,
"Autocompletion option does not exist");
ok(!editor.sourceEditor.getAutocompletionPopup(),
"Autocompletion popup does not exist");
cleanup();
});