mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
Bug 1029511 - Source Editor: Add ability to toggle autocomplete option. r=vporof
This commit is contained in:
parent
3debf3b4d5
commit
82839e29d0
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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(() => {
|
||||
|
@ -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();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user