Bug 944499 - Make the css autocompletion faster, r=harth

This commit is contained in:
Girish Sharma 2014-03-13 22:49:54 +05:30
Родитель a55a3e4884
Коммит a3de7f13eb
2 изменённых файлов: 104 добавлений и 9 удалений

Просмотреть файл

@ -60,10 +60,10 @@ function setupAutoCompletion(ctx, walker) {
keyMap[Editor.accel("Space")] = cm => autoComplete(ctx);
cm.addKeyMap(keyMap);
cm.on("keydown", (cm, e) => onEditorKeypress(ed, e));
cm.on("keydown", (cm, e) => onEditorKeypress(ctx, e));
ed.on("change", () => autoComplete(ctx));
ed.on("destroy", () => {
cm.off("keydown", (cm, e) => onEditorKeypress(ed, e));
cm.off("keydown", (cm, e) => onEditorKeypress(ctx, e));
ed.off("change", () => autoComplete(ctx));
popup.destroy();
popup = null;
@ -155,7 +155,7 @@ function cycleSuggestions(ed, reverse) {
* onkeydown handler for the editor instance to prevent autocompleting on some
* keypresses.
*/
function onEditorKeypress(ed, event) {
function onEditorKeypress({ ed, Editor }, event) {
let private = privates.get(ed);
switch (event.keyCode) {
case event.DOM_VK_ESCAPE:
@ -165,8 +165,14 @@ function onEditorKeypress(ed, event) {
case event.DOM_VK_RIGHT:
case event.DOM_VK_HOME:
case event.DOM_VK_END:
private.doNotAutocomplete = true;
private.popup.hidePopup();
break;
case event.DOM_VK_BACK_SPACE:
case event.DOM_VK_DELETE:
if (ed.config.mode == Editor.modes.css)
private.completer.invalidateCache(ed.getCursor().line)
private.doNotAutocomplete = true;
private.popup.hidePopup();
break;

Просмотреть файл

@ -84,6 +84,10 @@ const { properties, propertyNames } = getCSSKeywords();
function CSSCompleter(options = {}) {
this.walker = options.walker;
this.maxEntries = options.maxEntries || 15;
// Array containing the [line, ch, scopeStack] for the locations where the
// CSS state is "null"
this.nullStates = [];
}
CSSCompleter.prototype = {
@ -150,6 +154,29 @@ CSSCompleter.prototype = {
resolveState: function(source, {line, ch}) {
// Function to return the last element of an array
let peek = arr => arr[arr.length - 1];
// _state can be one of CSS_STATES;
let _state = CSS_STATES.null;
let selector = "";
let selectorState = SELECTOR_STATES.null;
let propertyName = null;
let scopeStack = [];
// Fetch the closest null state line, ch from cached null state locations
let matchedStateIndex = this.findNearestNullState(line);
if (matchedStateIndex > -1) {
let state = this.nullStates[matchedStateIndex];
line -= state[0];
if (line == 0)
ch -= state[1];
source = source.split("\n").slice(state[0]);
source[0] = source[0].slice(state[1]);
source = source.join("\n");
scopeStack = [...state[2]];
this.nullStates.length = matchedStateIndex + 1;
}
else {
this.nullStates = [];
}
let tokens = cssTokenizer(source, {loc:true});
let tokIndex = tokens.length - 1;
if (tokens[tokIndex].loc.end.line < line ||
@ -163,16 +190,10 @@ CSSCompleter.prototype = {
// Since last token is EOF, the cursor token is last - 1
tokIndex--;
// _state can be one of CSS_STATES;
let _state = CSS_STATES.null;
let cursor = 0;
// This will maintain a stack of paired elements like { & }, @m & }, : & ; etc
let scopeStack = [];
let token = null;
let propertyName = null;
let selector = "";
let selectorBeforeNot = "";
let selectorState = SELECTOR_STATES.null;
while (cursor <= tokIndex && (token = tokens[cursor++])) {
switch (_state) {
case CSS_STATES.property:
@ -601,6 +622,20 @@ CSSCompleter.prototype = {
}
break;
}
if (_state == CSS_STATES.null) {
if (this.nullStates.length == 0) {
this.nullStates.push([token.loc.end.line, token.loc.end.column,
[...scopeStack]]);
continue;
}
let tokenLine = token.loc.end.line;
let tokenCh = token.loc.end.column;
if (tokenLine == 0)
continue;
if (matchedStateIndex > -1)
tokenLine += this.nullStates[matchedStateIndex][0];
this.nullStates.push([tokenLine, tokenCh, [...scopeStack]]);
}
}
this.state = _state;
if (!token)
@ -768,6 +803,60 @@ CSSCompleter.prototype = {
}
return Promise.resolve(finalList);
},
/**
* A biased binary search in a sorted array where the middle element is
* calculated based on the values at the lower and the upper index in each
* iteration.
*
* This method returns the index of the closest null state from the passed
* `line` argument. Once we have the closest null state, we can start applying
* the state machine logic from that location instead of the absolute starting
* of the CSS source. This speeds up the tokenizing and the state machine a
* lot while using autocompletion at high line numbers in a CSS source.
*/
findNearestNullState: function(line) {
let arr = this.nullStates;
let high = arr.length - 1;
let low = 0;
let target = 0;
if (high < 0)
return -1;
if (arr[high][0] <= line)
return high;
if (arr[low][0] > line)
return -1;
while (high > low) {
if (arr[low][0] <= line && arr[low [0]+ 1] > line)
return low;
if (arr[high][0] > line && arr[high - 1][0] <= line)
return high - 1;
target = (((line - arr[low][0]) / (arr[high][0] - arr[low][0])) *
(high - low)) | 0;
if (arr[target][0] <= line && arr[target + 1][0] > line) {
return target;
} else if (line > arr[target][0]) {
low = target + 1;
high--;
} else {
high = target - 1;
low++;
}
}
return -1;
},
/**
* Invalidates the state cache for and above the line.
*/
invalidateCache: function(line) {
this.nullStates.length = this.findNearestNullState(line) + 1;
}
}
/**