the huge 117+ fixes update
This commit is contained in:
parent
baf06e964f
commit
ccae90fb3e
110 changed files with 7809 additions and 2864 deletions
320
chrome/utils/styloaix/autocomplete.js
Normal file
320
chrome/utils/styloaix/autocomplete.js
Normal file
|
@ -0,0 +1,320 @@
|
|||
/* 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');
|
||||
|
||||
let loader;
|
||||
try {
|
||||
({ loader } = ChromeUtils.import('resource://devtools/shared/loader/Loader.jsm'));
|
||||
} catch (e) {
|
||||
// tb91
|
||||
({ loader } = ChromeUtils.import('resource://devtools/shared/Loader.jsm'));
|
||||
}
|
||||
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"KeyCodes",
|
||||
"devtools/client/shared/keycodes",
|
||||
true
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"CSSCompleter",
|
||||
"devtools/client/shared/sourceeditor/css-autocompleter"
|
||||
);
|
||||
|
||||
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 } = win;
|
||||
|
||||
let completer = null;
|
||||
const autocompleteKey =
|
||||
"Ctrl-" + Editor.keyFor("autocompletion", { noaccel: true });
|
||||
if (ed.config.mode == Editor.modes.css) {
|
||||
completer = new CSSCompleter({
|
||||
walker: options.walker,
|
||||
cssProperties: options.cssProperties,
|
||||
maxEntries: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
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?.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,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Prevent the Console input to open, but still remove the autocomplete popup.
|
||||
autocompleteOpts.doNotAutocomplete = true;
|
||||
autocompleteOpts.popup.hidePopup();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions
|
||||
|
||||
exports.initializeAutoCompletion = initializeAutoCompletion;
|
Loading…
Add table
Add a link
Reference in a new issue