forked from mirrors/gecko-dev
Differential Revision: https://phabricator.services.mozilla.com/D19641 --HG-- rename : devtools/client/sourceeditor/.eslintrc.js => devtools/client/shared/sourceeditor/.eslintrc.js rename : devtools/client/sourceeditor/README => devtools/client/shared/sourceeditor/README rename : devtools/client/sourceeditor/autocomplete.js => devtools/client/shared/sourceeditor/autocomplete.js rename : devtools/client/sourceeditor/codemirror/LICENSE => devtools/client/shared/sourceeditor/codemirror/LICENSE rename : devtools/client/sourceeditor/codemirror/addon/comment/comment.js => devtools/client/shared/sourceeditor/codemirror/addon/comment/comment.js rename : devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js => devtools/client/shared/sourceeditor/codemirror/addon/comment/continuecomment.js rename : devtools/client/sourceeditor/codemirror/addon/dialog/dialog.css => devtools/client/shared/sourceeditor/codemirror/addon/dialog/dialog.css rename : devtools/client/sourceeditor/codemirror/addon/dialog/dialog.js => devtools/client/shared/sourceeditor/codemirror/addon/dialog/dialog.js rename : devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/closebrackets.js rename : devtools/client/sourceeditor/codemirror/addon/edit/closetag.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/closetag.js rename : devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/continuelist.js rename : devtools/client/sourceeditor/codemirror/addon/edit/matchbrackets.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/matchbrackets.js rename : devtools/client/sourceeditor/codemirror/addon/edit/matchtags.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/matchtags.js rename : devtools/client/sourceeditor/codemirror/addon/edit/trailingspace.js => devtools/client/shared/sourceeditor/codemirror/addon/edit/trailingspace.js rename : devtools/client/sourceeditor/codemirror/addon/fold/brace-fold.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/brace-fold.js rename : devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/comment-fold.js rename : devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/foldcode.js rename : devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.css => devtools/client/shared/sourceeditor/codemirror/addon/fold/foldgutter.css rename : devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/foldgutter.js rename : devtools/client/sourceeditor/codemirror/addon/fold/indent-fold.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/indent-fold.js rename : devtools/client/sourceeditor/codemirror/addon/fold/markdown-fold.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/markdown-fold.js rename : devtools/client/sourceeditor/codemirror/addon/fold/xml-fold.js => devtools/client/shared/sourceeditor/codemirror/addon/fold/xml-fold.js rename : devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js => devtools/client/shared/sourceeditor/codemirror/addon/hint/show-hint.js rename : devtools/client/sourceeditor/codemirror/addon/runmode/runmode.js => devtools/client/shared/sourceeditor/codemirror/addon/runmode/runmode.js rename : devtools/client/sourceeditor/codemirror/addon/search/match-highlighter.js => devtools/client/shared/sourceeditor/codemirror/addon/search/match-highlighter.js rename : devtools/client/sourceeditor/codemirror/addon/search/search.js => devtools/client/shared/sourceeditor/codemirror/addon/search/search.js rename : devtools/client/sourceeditor/codemirror/addon/search/searchcursor.js => devtools/client/shared/sourceeditor/codemirror/addon/search/searchcursor.js rename : devtools/client/sourceeditor/codemirror/addon/selection/active-line.js => devtools/client/shared/sourceeditor/codemirror/addon/selection/active-line.js rename : devtools/client/sourceeditor/codemirror/addon/selection/mark-selection.js => devtools/client/shared/sourceeditor/codemirror/addon/selection/mark-selection.js rename : devtools/client/sourceeditor/codemirror/addon/tern/tern.css => devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.css rename : devtools/client/sourceeditor/codemirror/addon/tern/tern.js => devtools/client/shared/sourceeditor/codemirror/addon/tern/tern.js rename : devtools/client/sourceeditor/codemirror/cmiframe.html => devtools/client/shared/sourceeditor/codemirror/cmiframe.html rename : devtools/client/sourceeditor/codemirror/codemirror.bundle.js => devtools/client/shared/sourceeditor/codemirror/codemirror.bundle.js rename : devtools/client/sourceeditor/codemirror/keymap/emacs.js => devtools/client/shared/sourceeditor/codemirror/keymap/emacs.js rename : devtools/client/sourceeditor/codemirror/keymap/sublime.js => devtools/client/shared/sourceeditor/codemirror/keymap/sublime.js rename : devtools/client/sourceeditor/codemirror/keymap/vim.js => devtools/client/shared/sourceeditor/codemirror/keymap/vim.js rename : devtools/client/sourceeditor/codemirror/lib/codemirror.css => devtools/client/shared/sourceeditor/codemirror/lib/codemirror.css rename : devtools/client/sourceeditor/codemirror/lib/codemirror.js => devtools/client/shared/sourceeditor/codemirror/lib/codemirror.js rename : devtools/client/sourceeditor/codemirror/mode/clike/clike.js => devtools/client/shared/sourceeditor/codemirror/mode/clike/clike.js rename : devtools/client/sourceeditor/codemirror/mode/clojure/clojure.js => devtools/client/shared/sourceeditor/codemirror/mode/clojure/clojure.js rename : devtools/client/sourceeditor/codemirror/mode/coffeescript/coffeescript.js => devtools/client/shared/sourceeditor/codemirror/mode/coffeescript/coffeescript.js rename : devtools/client/sourceeditor/codemirror/mode/css/css.js => devtools/client/shared/sourceeditor/codemirror/mode/css/css.js rename : devtools/client/sourceeditor/codemirror/mode/elm/elm.js => devtools/client/shared/sourceeditor/codemirror/mode/elm/elm.js rename : devtools/client/sourceeditor/codemirror/mode/haxe/haxe.js => devtools/client/shared/sourceeditor/codemirror/mode/haxe/haxe.js rename : devtools/client/sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js => devtools/client/shared/sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js rename : devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js => devtools/client/shared/sourceeditor/codemirror/mode/javascript/javascript.js rename : devtools/client/sourceeditor/codemirror/mode/jsx/jsx.js => devtools/client/shared/sourceeditor/codemirror/mode/jsx/jsx.js rename : devtools/client/sourceeditor/codemirror/mode/wasm/wasm.js => devtools/client/shared/sourceeditor/codemirror/mode/wasm/wasm.js rename : devtools/client/sourceeditor/codemirror/mode/xml/xml.js => devtools/client/shared/sourceeditor/codemirror/mode/xml/xml.js rename : devtools/client/sourceeditor/codemirror/mozilla.css => devtools/client/shared/sourceeditor/codemirror/mozilla.css rename : devtools/client/sourceeditor/css-autocompleter.js => devtools/client/shared/sourceeditor/css-autocompleter.js rename : devtools/client/sourceeditor/debugger.js => devtools/client/shared/sourceeditor/debugger.js rename : devtools/client/sourceeditor/editor-commands-controller.js => devtools/client/shared/sourceeditor/editor-commands-controller.js rename : devtools/client/sourceeditor/editor.js => devtools/client/shared/sourceeditor/editor.js rename : devtools/client/sourceeditor/moz.build => devtools/client/shared/sourceeditor/moz.build rename : devtools/client/sourceeditor/package.json => devtools/client/shared/sourceeditor/package.json rename : devtools/client/sourceeditor/tern/README => devtools/client/shared/sourceeditor/tern/README rename : devtools/client/sourceeditor/tern/browser.js => devtools/client/shared/sourceeditor/tern/browser.js rename : devtools/client/sourceeditor/tern/comment.js => devtools/client/shared/sourceeditor/tern/comment.js rename : devtools/client/sourceeditor/tern/condense.js => devtools/client/shared/sourceeditor/tern/condense.js rename : devtools/client/sourceeditor/tern/def.js => devtools/client/shared/sourceeditor/tern/def.js rename : devtools/client/sourceeditor/tern/ecma5.js => devtools/client/shared/sourceeditor/tern/ecma5.js rename : devtools/client/sourceeditor/tern/infer.js => devtools/client/shared/sourceeditor/tern/infer.js rename : devtools/client/sourceeditor/tern/moz.build => devtools/client/shared/sourceeditor/tern/moz.build rename : devtools/client/sourceeditor/tern/signal.js => devtools/client/shared/sourceeditor/tern/signal.js rename : devtools/client/sourceeditor/tern/tern.js => devtools/client/shared/sourceeditor/tern/tern.js rename : devtools/client/sourceeditor/tern/tests/unit/head_tern.js => devtools/client/shared/sourceeditor/tern/tests/unit/head_tern.js rename : devtools/client/sourceeditor/tern/tests/unit/test_autocompletion.js => devtools/client/shared/sourceeditor/tern/tests/unit/test_autocompletion.js rename : devtools/client/sourceeditor/tern/tests/unit/test_import_tern.js => devtools/client/shared/sourceeditor/tern/tests/unit/test_import_tern.js rename : devtools/client/sourceeditor/tern/tests/unit/xpcshell.ini => devtools/client/shared/sourceeditor/tern/tests/unit/xpcshell.ini rename : devtools/client/sourceeditor/test/.eslintrc.js => devtools/client/shared/sourceeditor/test/.eslintrc.js rename : devtools/client/sourceeditor/test/browser.ini => devtools/client/shared/sourceeditor/test/browser.ini rename : devtools/client/sourceeditor/test/browser_codemirror.js => devtools/client/shared/sourceeditor/test/browser_codemirror.js rename : devtools/client/sourceeditor/test/browser_css_autocompletion.js => devtools/client/shared/sourceeditor/test/browser_css_autocompletion.js rename : devtools/client/sourceeditor/test/browser_css_getInfo.js => devtools/client/shared/sourceeditor/test/browser_css_getInfo.js rename : devtools/client/sourceeditor/test/browser_css_statemachine.js => devtools/client/shared/sourceeditor/test/browser_css_statemachine.js rename : devtools/client/sourceeditor/test/browser_detectindent.js => devtools/client/shared/sourceeditor/test/browser_detectindent.js rename : devtools/client/sourceeditor/test/browser_editor_addons.js => devtools/client/shared/sourceeditor/test/browser_editor_addons.js rename : devtools/client/sourceeditor/test/browser_editor_alt_b_f.js => devtools/client/shared/sourceeditor/test/browser_editor_alt_b_f.js rename : devtools/client/sourceeditor/test/browser_editor_autocomplete_basic.js => devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_basic.js rename : devtools/client/sourceeditor/test/browser_editor_autocomplete_events.js => devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_events.js rename : devtools/client/sourceeditor/test/browser_editor_autocomplete_js.js => devtools/client/shared/sourceeditor/test/browser_editor_autocomplete_js.js rename : devtools/client/sourceeditor/test/browser_editor_basic.js => devtools/client/shared/sourceeditor/test/browser_editor_basic.js rename : devtools/client/sourceeditor/test/browser_editor_cursor.js => devtools/client/shared/sourceeditor/test/browser_editor_cursor.js rename : devtools/client/sourceeditor/test/browser_editor_find_again.js => devtools/client/shared/sourceeditor/test/browser_editor_find_again.js rename : devtools/client/sourceeditor/test/browser_editor_goto_line.js => devtools/client/shared/sourceeditor/test/browser_editor_goto_line.js rename : devtools/client/sourceeditor/test/browser_editor_history.js => devtools/client/shared/sourceeditor/test/browser_editor_history.js rename : devtools/client/sourceeditor/test/browser_editor_markers.js => devtools/client/shared/sourceeditor/test/browser_editor_markers.js rename : devtools/client/sourceeditor/test/browser_editor_movelines.js => devtools/client/shared/sourceeditor/test/browser_editor_movelines.js rename : devtools/client/sourceeditor/test/browser_editor_prefs.js => devtools/client/shared/sourceeditor/test/browser_editor_prefs.js rename : devtools/client/sourceeditor/test/browser_editor_script_injection.js => devtools/client/shared/sourceeditor/test/browser_editor_script_injection.js rename : devtools/client/sourceeditor/test/browser_vimemacs.js => devtools/client/shared/sourceeditor/test/browser_vimemacs.js rename : devtools/client/sourceeditor/test/cm_mode_ruby.js => devtools/client/shared/sourceeditor/test/cm_mode_ruby.js rename : devtools/client/sourceeditor/test/cm_script_injection_test.js => devtools/client/shared/sourceeditor/test/cm_script_injection_test.js rename : devtools/client/sourceeditor/test/codemirror/codemirror.html => devtools/client/shared/sourceeditor/test/codemirror/codemirror.html rename : devtools/client/sourceeditor/test/codemirror/comment_test.js => devtools/client/shared/sourceeditor/test/codemirror/comment_test.js rename : devtools/client/sourceeditor/test/codemirror/doc_test.js => devtools/client/shared/sourceeditor/test/codemirror/doc_test.js rename : devtools/client/sourceeditor/test/codemirror/driver.js => devtools/client/shared/sourceeditor/test/codemirror/driver.js rename : devtools/client/sourceeditor/test/codemirror/emacs_test.js => devtools/client/shared/sourceeditor/test/codemirror/emacs_test.js rename : devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js => devtools/client/shared/sourceeditor/test/codemirror/mode/javascript/test.js rename : devtools/client/sourceeditor/test/codemirror/mode_test.css => devtools/client/shared/sourceeditor/test/codemirror/mode_test.css rename : devtools/client/sourceeditor/test/codemirror/mode_test.js => devtools/client/shared/sourceeditor/test/codemirror/mode_test.js rename : devtools/client/sourceeditor/test/codemirror/multi_test.js => devtools/client/shared/sourceeditor/test/codemirror/multi_test.js rename : devtools/client/sourceeditor/test/codemirror/search_test.js => devtools/client/shared/sourceeditor/test/codemirror/search_test.js rename : devtools/client/sourceeditor/test/codemirror/sublime_test.js => devtools/client/shared/sourceeditor/test/codemirror/sublime_test.js rename : devtools/client/sourceeditor/test/codemirror/test.js => devtools/client/shared/sourceeditor/test/codemirror/test.js rename : devtools/client/sourceeditor/test/codemirror/vim_test.js => devtools/client/shared/sourceeditor/test/codemirror/vim_test.js rename : devtools/client/sourceeditor/test/codemirror/vimemacs.html => devtools/client/shared/sourceeditor/test/codemirror/vimemacs.html rename : devtools/client/sourceeditor/test/css_autocompletion_tests.json => devtools/client/shared/sourceeditor/test/css_autocompletion_tests.json rename : devtools/client/sourceeditor/test/css_statemachine_testcases.css => devtools/client/shared/sourceeditor/test/css_statemachine_testcases.css rename : devtools/client/sourceeditor/test/css_statemachine_tests.json => devtools/client/shared/sourceeditor/test/css_statemachine_tests.json rename : devtools/client/sourceeditor/test/head.js => devtools/client/shared/sourceeditor/test/head.js rename : devtools/client/sourceeditor/test/head.xul => devtools/client/shared/sourceeditor/test/head.xul rename : devtools/client/sourceeditor/test/helper_codemirror_runner.js => devtools/client/shared/sourceeditor/test/helper_codemirror_runner.js rename : devtools/client/sourceeditor/wasm.js => devtools/client/shared/sourceeditor/wasm.js rename : devtools/client/sourceeditor/webpack.config.js => devtools/client/shared/sourceeditor/webpack.config.js extra : moz-landing-system : lando
406 lines
12 KiB
JavaScript
406 lines
12 KiB
JavaScript
/* vim:set ts=2 sw=2 sts=2 et tw=80:
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
|
|
|
|
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
|
|
loader.lazyRequireGetter(this, "CSSCompleter", "devtools/client/shared/sourceeditor/css-autocompleter");
|
|
|
|
const CM_TERN_SCRIPTS = [
|
|
"chrome://devtools/content/shared/sourceeditor/codemirror/addon/tern/tern.js",
|
|
"chrome://devtools/content/shared/sourceeditor/codemirror/addon/hint/show-hint.js",
|
|
];
|
|
|
|
const autocompleteMap = new WeakMap();
|
|
|
|
/**
|
|
* Prepares an editor instance for autocompletion.
|
|
*/
|
|
function initializeAutoCompletion(ctx, options = {}) {
|
|
const { cm, ed, Editor } = ctx;
|
|
if (autocompleteMap.has(ed)) {
|
|
return;
|
|
}
|
|
|
|
const win = ed.container.contentWindow.wrappedJSObject;
|
|
const { CodeMirror, document } = win;
|
|
|
|
let completer = null;
|
|
const autocompleteKey = "Ctrl-" +
|
|
Editor.keyFor("autocompletion", { noaccel: true });
|
|
if (ed.config.mode == Editor.modes.js) {
|
|
const defs = [
|
|
require("./tern/browser"),
|
|
require("./tern/ecma5"),
|
|
];
|
|
|
|
CM_TERN_SCRIPTS.forEach(ed.loadScript, ed);
|
|
win.tern = require("./tern/tern");
|
|
cm.tern = new CodeMirror.TernServer({
|
|
defs: defs,
|
|
typeTip: function(data) {
|
|
const tip = document.createElement("span");
|
|
tip.className = "CodeMirror-Tern-information";
|
|
const tipType = document.createElement("strong");
|
|
const tipText = document.createTextNode(data.type ||
|
|
cm.l10n("autocompletion.notFound"));
|
|
tipType.appendChild(tipText);
|
|
tip.appendChild(tipType);
|
|
|
|
if (data.doc) {
|
|
tip.appendChild(document.createTextNode(" — " + data.doc));
|
|
}
|
|
|
|
if (data.url) {
|
|
tip.appendChild(document.createTextNode(" "));
|
|
const docLink = document.createElement("a");
|
|
docLink.textContent = "[" + cm.l10n("autocompletion.docsLink") + "]";
|
|
docLink.href = data.url;
|
|
docLink.className = "theme-link";
|
|
docLink.setAttribute("target", "_blank");
|
|
tip.appendChild(docLink);
|
|
}
|
|
|
|
return tip;
|
|
},
|
|
});
|
|
|
|
const keyMap = {};
|
|
const updateArgHintsCallback = cm.tern.updateArgHints.bind(cm.tern, cm);
|
|
cm.on("cursorActivity", updateArgHintsCallback);
|
|
|
|
keyMap[autocompleteKey] = cmArg => {
|
|
cmArg.tern.getHint(cmArg, data => {
|
|
CodeMirror.on(data, "shown", () => ed.emit("before-suggest"));
|
|
CodeMirror.on(data, "close", () => ed.emit("after-suggest"));
|
|
CodeMirror.on(data, "select", () => ed.emit("suggestion-entered"));
|
|
CodeMirror.showHint(cmArg, (cmIgnore, cb) => cb(data), { async: true });
|
|
});
|
|
};
|
|
|
|
keyMap[Editor.keyFor("showInformation2", { noaccel: true })] = cmArg => {
|
|
cmArg.tern.showType(cmArg, null, () => {
|
|
ed.emit("show-information");
|
|
});
|
|
};
|
|
cm.addKeyMap(keyMap);
|
|
|
|
const destroyTern = function() {
|
|
ed.off("destroy", destroyTern);
|
|
cm.off("cursorActivity", updateArgHintsCallback);
|
|
cm.removeKeyMap(keyMap);
|
|
win.tern = cm.tern = null;
|
|
autocompleteMap.delete(ed);
|
|
};
|
|
|
|
ed.on("destroy", destroyTern);
|
|
|
|
autocompleteMap.set(ed, {
|
|
destroy: destroyTern,
|
|
});
|
|
|
|
// TODO: Integrate tern autocompletion with this autocomplete API.
|
|
return;
|
|
} else if (ed.config.mode == Editor.modes.css) {
|
|
completer = new CSSCompleter({walker: options.walker,
|
|
cssProperties: options.cssProperties});
|
|
}
|
|
|
|
function insertSelectedPopupItem() {
|
|
const autocompleteState = autocompleteMap.get(ed);
|
|
if (!popup || !popup.isOpen || !autocompleteState) {
|
|
return false;
|
|
}
|
|
|
|
if (!autocompleteState.suggestionInsertedOnce && popup.selectedItem) {
|
|
autocompleteMap.get(ed).insertingSuggestion = true;
|
|
insertPopupItem(ed, popup.selectedItem);
|
|
}
|
|
|
|
popup.once("popup-closed", () => {
|
|
// This event is used in tests.
|
|
ed.emit("popup-hidden");
|
|
});
|
|
popup.hidePopup();
|
|
return true;
|
|
}
|
|
|
|
// Give each popup a new name to avoid sharing the elements.
|
|
|
|
let popup = new AutocompletePopup(win.parent.document, {
|
|
position: "bottom",
|
|
autoSelect: true,
|
|
onClick: insertSelectedPopupItem,
|
|
});
|
|
|
|
const cycle = reverse => {
|
|
if (popup && popup.isOpen) {
|
|
// eslint-disable-next-line mozilla/no-compare-against-boolean-literals
|
|
cycleSuggestions(ed, reverse == true);
|
|
return null;
|
|
}
|
|
|
|
return CodeMirror.Pass;
|
|
};
|
|
|
|
let keyMap = {
|
|
"Tab": cycle,
|
|
"Down": cycle,
|
|
"Shift-Tab": cycle.bind(null, true),
|
|
"Up": cycle.bind(null, true),
|
|
"Enter": () => {
|
|
const wasHandled = insertSelectedPopupItem();
|
|
return wasHandled ? true : CodeMirror.Pass;
|
|
},
|
|
};
|
|
|
|
const autoCompleteCallback = autoComplete.bind(null, ctx);
|
|
const keypressCallback = onEditorKeypress.bind(null, ctx);
|
|
keyMap[autocompleteKey] = autoCompleteCallback;
|
|
cm.addKeyMap(keyMap);
|
|
|
|
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();
|
|
keyMap = popup = completer = null;
|
|
autocompleteMap.delete(ed);
|
|
}
|
|
|
|
autocompleteMap.set(ed, {
|
|
popup: popup,
|
|
completer: completer,
|
|
keyMap: keyMap,
|
|
destroy: destroy,
|
|
insertingSuggestion: false,
|
|
suggestionInsertedOnce: false,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Destroy autocompletion on an editor instance.
|
|
*/
|
|
function destroyAutoCompletion(ctx) {
|
|
const { ed } = ctx;
|
|
if (!autocompleteMap.has(ed)) {
|
|
return;
|
|
}
|
|
|
|
const {destroy} = autocompleteMap.get(ed);
|
|
destroy();
|
|
}
|
|
|
|
/**
|
|
* Provides suggestions to autocomplete the current token/word being typed.
|
|
*/
|
|
function autoComplete({ ed, cm }) {
|
|
const autocompleteOpts = autocompleteMap.get(ed);
|
|
const { completer, popup } = autocompleteOpts;
|
|
if (!completer || autocompleteOpts.insertingSuggestion ||
|
|
autocompleteOpts.doNotAutocomplete) {
|
|
autocompleteOpts.insertingSuggestion = false;
|
|
return;
|
|
}
|
|
const cur = ed.getCursor();
|
|
completer.complete(cm.getRange({line: 0, ch: 0}, cur), cur).then(suggestions => {
|
|
if (!suggestions || !suggestions.length || suggestions[0].preLabel == null) {
|
|
autocompleteOpts.suggestionInsertedOnce = false;
|
|
popup.once("popup-closed", () => {
|
|
// This event is used in tests.
|
|
ed.emit("after-suggest");
|
|
});
|
|
popup.hidePopup();
|
|
return;
|
|
}
|
|
// The cursor is at the end of the currently entered part of the token,
|
|
// like "backgr|" but we need to open the popup at the beginning of the
|
|
// character "b". Thus we need to calculate the width of the entered part
|
|
// of the token ("backgr" here).
|
|
|
|
const cursorElement = cm.display.cursorDiv.querySelector(".CodeMirror-cursor");
|
|
const left = suggestions[0].preLabel.length * cm.defaultCharWidth();
|
|
popup.hidePopup();
|
|
popup.setItems(suggestions);
|
|
|
|
popup.once("popup-opened", () => {
|
|
// This event is used in tests.
|
|
ed.emit("after-suggest");
|
|
});
|
|
popup.openPopup(cursorElement, -1 * left, 0);
|
|
autocompleteOpts.suggestionInsertedOnce = false;
|
|
}).catch(console.error);
|
|
}
|
|
|
|
/**
|
|
* Inserts a popup item into the current cursor location
|
|
* in the editor.
|
|
*/
|
|
function insertPopupItem(ed, popupItem) {
|
|
const {preLabel, text} = popupItem;
|
|
const cur = ed.getCursor();
|
|
const textBeforeCursor = ed.getText(cur.line).substring(0, cur.ch);
|
|
const backwardsTextBeforeCursor = textBeforeCursor.split("").reverse().join("");
|
|
const backwardsPreLabel = preLabel.split("").reverse().join("");
|
|
|
|
// If there is additional text in the preLabel vs the line, then
|
|
// just insert the entire autocomplete text. An example:
|
|
// if you type 'a' and select '#about' from the autocomplete menu,
|
|
// then the final text needs to the end up as '#about'.
|
|
if (backwardsPreLabel.indexOf(backwardsTextBeforeCursor) === 0) {
|
|
ed.replaceText(text, {line: cur.line, ch: 0}, cur);
|
|
} else {
|
|
ed.replaceText(text.slice(preLabel.length), cur, cur);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cycles through provided suggestions by the popup in a top to bottom manner
|
|
* when `reverse` is not true. Opposite otherwise.
|
|
*/
|
|
function cycleSuggestions(ed, reverse) {
|
|
const autocompleteOpts = autocompleteMap.get(ed);
|
|
const { popup } = autocompleteOpts;
|
|
const cur = ed.getCursor();
|
|
autocompleteOpts.insertingSuggestion = true;
|
|
if (!autocompleteOpts.suggestionInsertedOnce) {
|
|
autocompleteOpts.suggestionInsertedOnce = true;
|
|
let firstItem;
|
|
if (reverse) {
|
|
firstItem = popup.getItemAtIndex(popup.itemCount - 1);
|
|
popup.selectPreviousItem();
|
|
} else {
|
|
firstItem = popup.getItemAtIndex(0);
|
|
if (firstItem.label == firstItem.preLabel && popup.itemCount > 1) {
|
|
firstItem = popup.getItemAtIndex(1);
|
|
popup.selectNextItem();
|
|
}
|
|
}
|
|
if (popup.itemCount == 1) {
|
|
popup.hidePopup();
|
|
}
|
|
insertPopupItem(ed, firstItem);
|
|
} else {
|
|
const fromCur = {
|
|
line: cur.line,
|
|
ch: cur.ch - popup.selectedItem.text.length,
|
|
};
|
|
if (reverse) {
|
|
popup.selectPreviousItem();
|
|
} else {
|
|
popup.selectNextItem();
|
|
}
|
|
ed.replaceText(popup.selectedItem.text, fromCur, cur);
|
|
}
|
|
// This event is used in tests.
|
|
ed.emit("suggestion-entered");
|
|
}
|
|
|
|
/**
|
|
* onkeydown handler for the editor instance to prevent autocompleting on some
|
|
* keypresses.
|
|
*/
|
|
function onEditorKeypress({ ed, Editor }, cm, event) {
|
|
const autocompleteOpts = autocompleteMap.get(ed);
|
|
|
|
// Do not try to autocomplete with multiple selections.
|
|
if (ed.hasMultipleSelections()) {
|
|
autocompleteOpts.doNotAutocomplete = true;
|
|
autocompleteOpts.popup.hidePopup();
|
|
return;
|
|
}
|
|
|
|
if ((event.ctrlKey || event.metaKey) && event.keyCode == KeyCodes.DOM_VK_SPACE) {
|
|
// When Ctrl/Cmd + Space is pressed, two simultaneous keypresses are emitted
|
|
// first one for just the Ctrl/Cmd and second one for combo. The first one
|
|
// leave the autocompleteOpts.doNotAutocomplete as true, so we have to make
|
|
// it false
|
|
autocompleteOpts.doNotAutocomplete = false;
|
|
return;
|
|
}
|
|
|
|
if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
autocompleteOpts.doNotAutocomplete = true;
|
|
autocompleteOpts.popup.hidePopup();
|
|
return;
|
|
}
|
|
|
|
switch (event.keyCode) {
|
|
case KeyCodes.DOM_VK_RETURN:
|
|
autocompleteOpts.doNotAutocomplete = true;
|
|
break;
|
|
case KeyCodes.DOM_VK_ESCAPE:
|
|
if (autocompleteOpts.popup.isOpen) {
|
|
event.preventDefault();
|
|
}
|
|
break;
|
|
case KeyCodes.DOM_VK_LEFT:
|
|
case KeyCodes.DOM_VK_RIGHT:
|
|
case KeyCodes.DOM_VK_HOME:
|
|
case KeyCodes.DOM_VK_END:
|
|
autocompleteOpts.doNotAutocomplete = true;
|
|
autocompleteOpts.popup.hidePopup();
|
|
break;
|
|
case KeyCodes.DOM_VK_BACK_SPACE:
|
|
case KeyCodes.DOM_VK_DELETE:
|
|
if (ed.config.mode == Editor.modes.css) {
|
|
autocompleteOpts.completer.invalidateCache(ed.getCursor().line);
|
|
}
|
|
autocompleteOpts.doNotAutocomplete = true;
|
|
autocompleteOpts.popup.hidePopup();
|
|
break;
|
|
default:
|
|
autocompleteOpts.doNotAutocomplete = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the private popup. This method is used by tests to test the feature.
|
|
*/
|
|
function getPopup({ ed }) {
|
|
if (autocompleteMap.has(ed)) {
|
|
return autocompleteMap.get(ed).popup;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns contextual information about the token covered by the caret if the
|
|
* implementation of completer supports it.
|
|
*/
|
|
function getInfoAt({ ed }, caret) {
|
|
if (autocompleteMap.has(ed)) {
|
|
const completer = autocompleteMap.get(ed).completer;
|
|
if (completer && completer.getInfoAt) {
|
|
return completer.getInfoAt(ed.getText(), caret);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns whether autocompletion is enabled for this editor.
|
|
* Used for testing
|
|
*/
|
|
function isAutocompletionEnabled({ ed }) {
|
|
return autocompleteMap.has(ed);
|
|
}
|
|
|
|
// Export functions
|
|
|
|
module.exports.initializeAutoCompletion = initializeAutoCompletion;
|
|
module.exports.destroyAutoCompletion = destroyAutoCompletion;
|
|
module.exports.getAutocompletionPopup = getPopup;
|
|
module.exports.getInfoAt = getInfoAt;
|
|
module.exports.isAutocompletionEnabled = isAutocompletionEnabled;
|