blob: 4da8c43cc5cddf0deee489f941091e8dc7c725ce [file] [log] [blame]
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
WebInspector.CodeMirrorCompletionController = function(codeMirror, delegate, stopCharactersRegex)
{
// FIXME: Convert this to a WebInspector.Object subclass, and call super().
// WebInspector.Object.call(this);
console.assert(codeMirror);
this._codeMirror = codeMirror;
this._stopCharactersRegex = stopCharactersRegex || null;
this._delegate = delegate || null;
this._startOffset = NaN;
this._endOffset = NaN;
this._lineNumber = NaN;
this._prefix = "";
this._completions = [];
this._extendedCompletionProviders = {};
this._suggestionsView = new WebInspector.CompletionSuggestionsView(this);
this._keyMap = {
"Up": this._handleUpKey.bind(this),
"Down": this._handleDownKey.bind(this),
"Right": this._handleRightOrEnterKey.bind(this),
"Esc": this._handleEscapeKey.bind(this),
"Enter": this._handleRightOrEnterKey.bind(this),
"Tab": this._handleTabKey.bind(this),
"Cmd-A": this._handleHideKey.bind(this),
"Cmd-Z": this._handleHideKey.bind(this),
"Shift-Cmd-Z": this._handleHideKey.bind(this),
"Cmd-Y": this._handleHideKey.bind(this)
};
this._handleChangeListener = this._handleChange.bind(this);
this._handleCursorActivityListener = this._handleCursorActivity.bind(this);
this._handleHideActionListener = this._handleHideAction.bind(this);
this._codeMirror.addKeyMap(this._keyMap);
this._codeMirror.on("change", this._handleChangeListener);
this._codeMirror.on("cursorActivity", this._handleCursorActivityListener);
this._codeMirror.on("blur", this._handleHideActionListener);
this._codeMirror.on("scroll", this._handleHideActionListener);
};
WebInspector.CodeMirrorCompletionController.GenericStopCharactersRegex = /[\s=:;,]/;
WebInspector.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap = {"css": /[\s:;,{}()]/, "javascript": /[\s=:;,!+\-*/%&|^~?<>.{}()[\]]/};
WebInspector.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap = {"javascript": /[\s=:;,!+\-*/%&|^~?<>]/};
WebInspector.CodeMirrorCompletionController.OpenBracketCharactersRegex = /[({[]/;
WebInspector.CodeMirrorCompletionController.CloseBracketCharactersRegex = /[)}\]]/;
WebInspector.CodeMirrorCompletionController.MatchingBrackets = {"{": "}", "(": ")", "[": "]", "}": "{", ")": "(", "]": "["};
WebInspector.CodeMirrorCompletionController.CompletionHintStyleClassName = "completion-hint";
WebInspector.CodeMirrorCompletionController.CompletionsHiddenDelay = 250;
WebInspector.CodeMirrorCompletionController.CompletionTypingDelay = 250;
WebInspector.CodeMirrorCompletionController.CompletionOrigin = "+completion";
WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin = "+delete-completion";
WebInspector.CodeMirrorCompletionController.prototype = {
constructor: WebInspector.CodeMirrorCompletionController,
// Public
get delegate()
{
return this._delegate;
},
addExtendedCompletionProvider: function(modeName, provider)
{
this._extendedCompletionProviders[modeName] = provider;
},
updateCompletions: function(completions, implicitSuffix)
{
if (isNaN(this._startOffset) || isNaN(this._endOffset) || isNaN(this._lineNumber))
return;
if (!completions || !completions.length) {
this.hideCompletions();
return;
}
this._completions = completions;
if (typeof implicitSuffix === "string")
this._implicitSuffix = implicitSuffix;
var from = {line: this._lineNumber, ch: this._startOffset};
var to = {line: this._lineNumber, ch: this._endOffset};
var firstCharCoords = this._codeMirror.cursorCoords(from);
var lastCharCoords = this._codeMirror.cursorCoords(to);
var bounds = new WebInspector.Rect(firstCharCoords.left, firstCharCoords.top, lastCharCoords.right - firstCharCoords.left, firstCharCoords.bottom - firstCharCoords.top);
// Try to restore the previous selected index, otherwise just select the first.
var index = this._currentCompletion ? completions.indexOf(this._currentCompletion) : 0;
if (index === -1)
index = 0;
if (this._forced || completions.length > 1 || completions[index] !== this._prefix) {
// Update and show the suggestion list.
this._suggestionsView.update(completions, index);
this._suggestionsView.show(bounds);
} else if (this._implicitSuffix) {
// The prefix and the completion exactly match, but there is an implicit suffix.
// Just hide the suggestion list and keep the completion hint for the implicit suffix.
this._suggestionsView.hide();
} else {
// The prefix and the completion exactly match, hide the completions. Return early so
// the completion hint isn't updated.
this.hideCompletions();
return;
}
this._applyCompletionHint(completions[index]);
},
isCompletionChange: function(change)
{
return this._ignoreChange || change.origin === WebInspector.CodeMirrorCompletionController.CompletionOrigin || change.origin === WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin;
},
isShowingCompletions: function()
{
return this._suggestionsView.visible || (this._completionHintMarker && this._completionHintMarker.find());
},
isHandlingClickEvent: function()
{
return this._suggestionsView.isHandlingClickEvent();
},
hideCompletions: function()
{
this._suggestionsView.hide();
this._removeCompletionHint();
this._startOffset = NaN;
this._endOffset = NaN;
this._lineNumber = NaN;
this._prefix = "";
this._completions = [];
this._implicitSuffix = "";
this._forced = false;
if (this._completionDelayTimeout) {
clearTimeout(this._completionDelayTimeout);
delete this._completionDelayTimeout;
}
delete this._currentCompletion;
delete this._ignoreNextCursorActivity;
},
close: function()
{
this._codeMirror.removeKeyMap(this._keyMap);
this._codeMirror.off("change", this._handleChangeListener);
this._codeMirror.off("cursorActivity", this._handleCursorActivityListener);
this._codeMirror.off("blur", this._handleHideActionListener);
this._codeMirror.off("scroll", this._handleHideActionListener);
},
// Protected
completionSuggestionsSelectedCompletion: function(suggestionsView, completionText)
{
this._applyCompletionHint(completionText);
},
completionSuggestionsClickedCompletion: function(suggestionsView, completionText)
{
// The clicked suggestion causes the editor to loose focus. Restore it so the user can keep typing.
this._codeMirror.focus();
this._applyCompletionHint(completionText);
this._commitCompletionHint();
},
// Private
get _currentReplacementText()
{
return this._currentCompletion + this._implicitSuffix;
},
_hasPendingCompletion: function()
{
return !isNaN(this._startOffset) && !isNaN(this._endOffset) && !isNaN(this._lineNumber);
},
_notifyCompletionsHiddenSoon: function()
{
function notify()
{
if (this._completionHintMarker)
return;
if (this._delegate && typeof this._delegate.completionControllerCompletionsHidden === "function")
this._delegate.completionControllerCompletionsHidden(this);
}
if (this._notifyCompletionsHiddenIfNeededTimeout)
clearTimeout(this._notifyCompletionsHiddenIfNeededTimeout);
this._notifyCompletionsHiddenIfNeededTimeout = setTimeout(notify.bind(this), WebInspector.CodeMirrorCompletionController.CompletionsHiddenDelay);
},
_applyCompletionHint: function(completionText)
{
console.assert(completionText);
if (!completionText)
return;
function update()
{
this._currentCompletion = completionText;
this._removeCompletionHint(true, true);
var replacementText = this._currentReplacementText;
var from = {line: this._lineNumber, ch: this._startOffset};
var cursor = {line: this._lineNumber, ch: this._endOffset};
var to = {line: this._lineNumber, ch: this._startOffset + replacementText.length};
this._codeMirror.replaceRange(replacementText, from, cursor, WebInspector.CodeMirrorCompletionController.CompletionOrigin);
this._removeLastChangeFromHistory();
this._codeMirror.setCursor(cursor);
if (cursor.ch !== to.ch)
this._completionHintMarker = this._codeMirror.markText(cursor, to, {className: WebInspector.CodeMirrorCompletionController.CompletionHintStyleClassName});
}
this._ignoreChange = true;
this._ignoreNextCursorActivity = true;
this._codeMirror.operation(update.bind(this));
delete this._ignoreChange;
},
_commitCompletionHint: function()
{
function update()
{
this._removeCompletionHint(true, true);
var replacementText = this._currentReplacementText;
var from = {line: this._lineNumber, ch: this._startOffset};
var cursor = {line: this._lineNumber, ch: this._endOffset};
var to = {line: this._lineNumber, ch: this._startOffset + replacementText.length};
var lastChar = this._currentCompletion.charAt(this._currentCompletion.length - 1);
var isClosing = ")]}".indexOf(lastChar);
if (isClosing !== -1)
to.ch -= 1 + this._implicitSuffix.length;
this._codeMirror.replaceRange(replacementText, from, cursor, WebInspector.CodeMirrorCompletionController.CompletionOrigin);
// Don't call _removeLastChangeFromHistory here to allow the committed completion to be undone.
this._codeMirror.setCursor(to);
this.hideCompletions();
}
this._ignoreChange = true;
this._ignoreNextCursorActivity = true;
this._codeMirror.operation(update.bind(this));
delete this._ignoreChange;
},
_removeLastChangeFromHistory: function()
{
var history = this._codeMirror.getHistory();
// We don't expect a undone history. But if there is one clear it. If could lead to undefined behavior.
console.assert(!history.undone.length);
history.undone = [];
// Pop the last item from the done history.
console.assert(history.done.length);
history.done.pop();
this._codeMirror.setHistory(history);
},
_removeCompletionHint: function(nonatomic, dontRestorePrefix)
{
if (!this._completionHintMarker)
return;
this._notifyCompletionsHiddenSoon();
function update()
{
var range = this._completionHintMarker.find();
if (range) {
this._completionHintMarker.clear();
this._codeMirror.replaceRange("", range.from, range.to, WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin);
this._removeLastChangeFromHistory();
}
this._completionHintMarker = null;
if (dontRestorePrefix)
return;
console.assert(!isNaN(this._startOffset));
console.assert(!isNaN(this._endOffset));
console.assert(!isNaN(this._lineNumber));
var from = {line: this._lineNumber, ch: this._startOffset};
var to = {line: this._lineNumber, ch: this._endOffset};
this._codeMirror.replaceRange(this._prefix, from, to, WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin);
this._removeLastChangeFromHistory();
}
if (nonatomic) {
update.call(this);
return;
}
this._ignoreChange = true;
this._codeMirror.operation(update.bind(this));
delete this._ignoreChange;
},
_scanStringForExpression: function(modeName, string, startOffset, direction, allowMiddleAndEmpty, includeStopCharacter, ignoreInitialUnmatchedOpenBracket, stopCharactersRegex)
{
console.assert(direction === -1 || direction === 1);
var stopCharactersRegex = stopCharactersRegex || this._stopCharactersRegex || WebInspector.CodeMirrorCompletionController.DefaultStopCharactersRegexModeMap[modeName] || WebInspector.CodeMirrorCompletionController.GenericStopCharactersRegex;
function isStopCharacter(character)
{
return stopCharactersRegex.test(character);
}
function isOpenBracketCharacter(character)
{
return WebInspector.CodeMirrorCompletionController.OpenBracketCharactersRegex.test(character);
}
function isCloseBracketCharacter(character)
{
return WebInspector.CodeMirrorCompletionController.CloseBracketCharactersRegex.test(character);
}
function matchingBracketCharacter(character)
{
return WebInspector.CodeMirrorCompletionController.MatchingBrackets[character];
}
var endOffset = Math.min(startOffset, string.length);
var endOfLineOrWord = endOffset === string.length || isStopCharacter(string.charAt(endOffset));
if (!endOfLineOrWord && !allowMiddleAndEmpty)
return null;
var bracketStack = [];
var bracketOffsetStack = [];
var lastCloseBracketOffset = NaN;
var startOffset = endOffset;
var firstOffset = endOffset + direction;
for (var i = firstOffset; direction > 0 ? i < string.length : i >= 0; i += direction) {
var character = string.charAt(i);
// Ignore stop characters when we are inside brackets.
if (isStopCharacter(character) && !bracketStack.length)
break;
if (isCloseBracketCharacter(character)) {
bracketStack.push(character);
bracketOffsetStack.push(i);
} else if (isOpenBracketCharacter(character)) {
if ((!ignoreInitialUnmatchedOpenBracket || i !== firstOffset) && (!bracketStack.length || matchingBracketCharacter(character) !== bracketStack.lastValue))
break;
bracketOffsetStack.pop();
bracketStack.pop();
}
startOffset = i + (direction > 0 ? 1 : 0);
}
if (bracketOffsetStack.length)
startOffset = bracketOffsetStack.pop() + 1;
if (includeStopCharacter && startOffset > 0 && startOffset < string.length)
startOffset += direction;
if (direction > 0) {
var tempEndOffset = endOffset;
endOffset = startOffset;
startOffset = tempEndOffset;
}
return {string: string.substring(startOffset, endOffset), startOffset, endOffset};
},
_completeAtCurrentPosition: function(force)
{
if (this._codeMirror.somethingSelected()) {
this.hideCompletions();
return;
}
if (this._completionDelayTimeout) {
clearTimeout(this._completionDelayTimeout);
delete this._completionDelayTimeout;
}
this._removeCompletionHint(true, true);
var cursor = this._codeMirror.getCursor();
var token = this._codeMirror.getTokenAt(cursor);
// Don't try to complete inside comments.
if (token.type && /\bcomment\b/.test(token.type)) {
this.hideCompletions();
return;
}
var mode = this._codeMirror.getMode();
var innerMode = CodeMirror.innerMode(mode, token.state).mode;
var modeName = innerMode.alternateName || innerMode.name;
var lineNumber = cursor.line;
var lineString = this._codeMirror.getLine(lineNumber);
var backwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, -1, force);
if (!backwardScanResult) {
this.hideCompletions();
return;
}
var forwardScanResult = this._scanStringForExpression(modeName, lineString, cursor.ch, 1, true, true);
var suffix = forwardScanResult.string;
this._ignoreNextCursorActivity = true;
this._startOffset = backwardScanResult.startOffset;
this._endOffset = backwardScanResult.endOffset;
this._lineNumber = lineNumber;
this._prefix = backwardScanResult.string;
this._completions = [];
this._implicitSuffix = "";
this._forced = force;
var baseExpressionStopCharactersRegex = WebInspector.CodeMirrorCompletionController.BaseExpressionStopCharactersRegexModeMap[modeName];
if (baseExpressionStopCharactersRegex)
var baseScanResult = this._scanStringForExpression(modeName, lineString, this._startOffset, -1, true, false, true, baseExpressionStopCharactersRegex);
if (!force && !backwardScanResult.string && (!baseScanResult || !baseScanResult.string)) {
this.hideCompletions();
return;
}
var defaultCompletions = [];
switch (modeName) {
case "css":
defaultCompletions = this._generateCSSCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
break;
case "javascript":
defaultCompletions = this._generateJavaScriptCompletions(token, baseScanResult ? baseScanResult.string : null, suffix);
break;
}
var extendedCompletionsProvider = this._extendedCompletionProviders[modeName];
if (extendedCompletionsProvider) {
extendedCompletionsProvider.completionControllerCompletionsNeeded(this, defaultCompletions, baseScanResult ? baseScanResult.string : null, this._prefix, suffix, force);
return;
}
if (this._delegate && typeof this._delegate.completionControllerCompletionsNeeded === "function")
this._delegate.completionControllerCompletionsNeeded(this, this._prefix, defaultCompletions, baseScanResult ? baseScanResult.string : null, suffix, force);
else
this.updateCompletions(defaultCompletions);
},
_generateCSSCompletions: function(mainToken, base, suffix)
{
// We only support completion inside CSS block context.
if (mainToken.state.state === "media" || mainToken.state.state === "top" || mainToken.state.state === "parens")
return [];
var token = mainToken;
var lineNumber = this._lineNumber;
// Scan backwards looking for the current property.
while (token.state.state === "prop") {
// Found the beginning of the line. Go to the previous line.
if (!token.start) {
--lineNumber;
// No more lines, stop.
if (lineNumber < 0)
break;
}
// Get the previous token.
token = this._codeMirror.getTokenAt({line: lineNumber, ch: token.start ? token.start : Number.MAX_VALUE});
}
// If we have a property token and it's not the main token, then we are working on
// the value for that property and should complete allowed values.
if (mainToken !== token && token.type && /\bproperty\b/.test(token.type)) {
var propertyName = token.string;
// If there is a suffix and it isn't a semicolon, then we should use a space since
// the user is editing in the middle.
this._implicitSuffix = suffix && suffix !== ";" ? " " : ";";
// Don't use an implicit suffix if it would be the same as the existing suffix.
if (this._implicitSuffix === suffix)
this._implicitSuffix = "";
return WebInspector.CSSKeywordCompletions.forProperty(propertyName).startsWith(this._prefix);
}
this._implicitSuffix = suffix !== ":" ? ": " : "";
// Complete property names.
return WebInspector.CSSCompletions.cssNameCompletions.startsWith(this._prefix);
},
_generateJavaScriptCompletions: function(mainToken, base, suffix)
{
// If there is a base expression then we should not attempt to match any keywords or variables.
// Allow only open bracket characters at the end of the base, otherwise leave completions with
// a base up to the delegate to figure out.
if (base && !/[({[]$/.test(base))
return [];
var matchingWords = [];
const prefix = this._prefix;
var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state;
const declaringVariable = localState.lexical.type === "vardef";
const insideSwitch = localState.lexical.prev ? localState.lexical.prev.info === "switch" : false;
const insideBlock = localState.lexical.prev ? localState.lexical.prev.type === "}" : false;
const insideParenthesis = localState.lexical.type === ")";
const insideBrackets = localState.lexical.type === "]";
const allKeywords = ["break", "case", "catch", "const", "continue", "debugger", "default", "delete", "do", "else", "false", "finally", "for", "function", "if", "in",
"Infinity", "instanceof", "NaN", "new", "null", "return", "switch", "this", "throw", "true", "try", "typeof", "undefined", "var", "void", "while", "with"];
const valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined"];
const allowedKeywordsInsideBlocks = allKeywords.keySet();
const allowedKeywordsWhenDeclaringVariable = valueKeywords.keySet();
const allowedKeywordsInsideParenthesis = valueKeywords.concat(["function"]).keySet();
const allowedKeywordsInsideBrackets = allowedKeywordsInsideParenthesis;
const allowedKeywordsOnlyInsideSwitch = ["case", "default"].keySet();
function matchKeywords(keywords)
{
matchingWords = matchingWords.concat(keywords.filter(function(word) {
if (!insideSwitch && word in allowedKeywordsOnlyInsideSwitch)
return false;
if (insideBlock && !(word in allowedKeywordsInsideBlocks))
return false;
if (insideBrackets && !(word in allowedKeywordsInsideBrackets))
return false;
if (insideParenthesis && !(word in allowedKeywordsInsideParenthesis))
return false;
if (declaringVariable && !(word in allowedKeywordsWhenDeclaringVariable))
return false;
return word.startsWith(prefix);
}));
}
function matchVariables()
{
function filterVariables(variables)
{
for (var variable = variables; variable; variable = variable.next) {
// Don't match the variable if this token is in a variable declaration.
// Otherwise the currently typed text will always match and that isn't useful.
if (declaringVariable && variable.name === prefix)
continue;
if (variable.name.startsWith(prefix) && !matchingWords.contains(variable.name))
matchingWords.push(variable.name);
}
}
var context = localState.context;
while (context) {
if (context.vars)
filterVariables(context.vars);
context = context.prev;
}
if (localState.localVars)
filterVariables(localState.localVars);
if (localState.globalVars)
filterVariables(localState.globalVars);
}
switch (suffix.substring(0, 1)) {
case "":
case " ":
matchVariables();
matchKeywords(allKeywords);
break;
case ".":
case "[":
matchVariables();
matchKeywords(["false", "Infinity", "NaN", "this", "true"]);
break;
case "(":
matchVariables();
matchKeywords(["catch", "else", "for", "function", "if", "return", "switch", "throw", "while", "with"]);
break;
case "{":
matchKeywords(["do", "else", "finally", "return", "try"]);
break;
case ":":
if (insideSwitch)
matchKeywords(["case", "default"]);
break;
case ";":
matchVariables();
matchKeywords(valueKeywords);
matchKeywords(["break", "continue", "debugger", "return", "void"]);
break;
}
return matchingWords;
},
_handleUpKey: function(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._suggestionsView.selectPrevious();
},
_handleDownKey: function(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._suggestionsView.selectNext();
},
_handleRightOrEnterKey: function(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
this._commitCompletionHint();
},
_handleEscapeKey: function(codeMirror)
{
var delegateImplementsShouldAllowEscapeCompletion = this._delegate && typeof this._delegate.completionControllerShouldAllowEscapeCompletion === "function";
if (this._hasPendingCompletion())
this.hideCompletions();
else if (this._codeMirror.getOption("readOnly"))
return CodeMirror.Pass;
else if (!delegateImplementsShouldAllowEscapeCompletion || this._delegate.completionControllerShouldAllowEscapeCompletion(this))
this._completeAtCurrentPosition(true);
else
return CodeMirror.Pass;
},
_handleTabKey: function(codeMirror)
{
if (!this._hasPendingCompletion())
return CodeMirror.Pass;
if (!this.isShowingCompletions())
return;
console.assert(this._completions.length);
if (!this._completions.length)
return;
console.assert(this._currentCompletion);
if (!this._currentCompletion)
return;
// Commit the current completion if there is only one suggestion.
if (this._completions.length === 1) {
this._commitCompletionHint();
return;
}
var prefixLength = this._prefix.length;
var commonPrefix = this._completions[0];
for (var i = 1; i < this._completions.length; ++i) {
var completion = this._completions[i];
var lastIndex = Math.min(commonPrefix.length, completion.length);
for (var j = prefixLength; j < lastIndex; ++j) {
if (commonPrefix[j] !== completion[j]) {
commonPrefix = commonPrefix.substr(0, j);
break;
}
}
}
// Commit the current completion if there is no common prefix that is longer.
if (commonPrefix === this._prefix) {
this._commitCompletionHint();
return;
}
// Set the prefix to the common prefix so _applyCompletionHint will insert the
// common prefix as commited text. Adjust _endOffset to match the new prefix.
this._prefix = commonPrefix;
this._endOffset = this._startOffset + commonPrefix.length;
this._applyCompletionHint(this._currentCompletion);
},
_handleChange: function(codeMirror, change)
{
if (this.isCompletionChange(change))
return;
this._ignoreNextCursorActivity = true;
if (!change.origin || change.origin.charAt(0) !== "+") {
this.hideCompletions();
return;
}
// Only complete on delete if we are showing completions already.
if (change.origin === "+delete" && !this._hasPendingCompletion())
return;
if (this._completionDelayTimeout) {
clearTimeout(this._completionDelayTimeout);
delete this._completionDelayTimeout;
}
if (this._hasPendingCompletion())
this._completeAtCurrentPosition(false);
else
this._completionDelayTimeout = setTimeout(this._completeAtCurrentPosition.bind(this, false), WebInspector.CodeMirrorCompletionController.CompletionTypingDelay);
},
_handleCursorActivity: function(codeMirror)
{
if (this._ignoreChange)
return;
if (this._ignoreNextCursorActivity) {
delete this._ignoreNextCursorActivity;
return;
}
this.hideCompletions();
},
_handleHideKey: function(codeMirror)
{
this.hideCompletions();
return CodeMirror.Pass;
},
_handleHideAction: function(codeMirror)
{
// Clicking a suggestion causes the editor to blur. We don't want to hide completions in this case.
if (this.isHandlingClickEvent())
return;
this.hideCompletions();
}
};
WebInspector.CodeMirrorCompletionController.prototype.__proto__ = WebInspector.Object.prototype;