diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js | 875 |
1 files changed, 875 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js new file mode 100644 index 000000000..33d7ccbd6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Controllers/CodeMirrorCompletionController.js @@ -0,0 +1,875 @@ +/* + * 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 = class CodeMirrorCompletionController extends WebInspector.Object +{ + constructor(codeMirror, delegate, stopCharactersRegex) + { + super(); + + 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._noEndingSemicolon = false; + 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); + + this._updatePromise = null; + } + + // Public + + get delegate() + { + return this._delegate; + } + + addExtendedCompletionProvider(modeName, provider) + { + this._extendedCompletionProviders[modeName] = provider; + } + + updateCompletions(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]); + + this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.CompletionsFound); + } + + isCompletionChange(change) + { + return this._ignoreChange || change.origin === WebInspector.CodeMirrorCompletionController.CompletionOrigin || change.origin === WebInspector.CodeMirrorCompletionController.DeleteCompletionOrigin; + } + + isShowingCompletions() + { + return this._suggestionsView.visible || (this._completionHintMarker && this._completionHintMarker.find()); + } + + isHandlingClickEvent() + { + return this._suggestionsView.isHandlingClickEvent(); + } + + hideCompletions() + { + this._suggestionsView.hide(); + + this._removeCompletionHint(); + + this._startOffset = NaN; + this._endOffset = NaN; + this._lineNumber = NaN; + this._prefix = ""; + this._completions = []; + this._implicitSuffix = ""; + this._forced = false; + + delete this._currentCompletion; + delete this._ignoreNextCursorActivity; + + this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.NoCompletionsFound); + } + + close() + { + 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); + } + + completeAtCurrentPositionIfNeeded(force) + { + this._resolveUpdatePromise(WebInspector.CodeMirrorCompletionController.UpdatePromise.Canceled); + + var update = this._updatePromise = new WebInspector.WrappedPromise; + + this._completeAtCurrentPosition(force); + + return update.promise; + } + + // Protected + + completionSuggestionsSelectedCompletion(suggestionsView, completionText) + { + this._applyCompletionHint(completionText); + } + + completionSuggestionsClickedCompletion(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(); + } + + set noEndingSemicolon(noEndingSemicolon) + { + this._noEndingSemicolon = noEndingSemicolon; + } + + // Private + + _resolveUpdatePromise(message) + { + if (!this._updatePromise) + return; + + this._updatePromise.resolve(message); + this._updatePromise = null; + } + + get _currentReplacementText() + { + return this._currentCompletion + this._implicitSuffix; + } + + _hasPendingCompletion() + { + return !isNaN(this._startOffset) && !isNaN(this._endOffset) && !isNaN(this._lineNumber); + } + + _notifyCompletionsHiddenSoon() + { + 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); + } + + _createCompletionHintMarker(position, text) + { + var container = document.createElement("span"); + container.classList.add(WebInspector.CodeMirrorCompletionController.CompletionHintStyleClassName); + container.textContent = text; + + this._completionHintMarker = this._codeMirror.setUniqueBookmark(position, {widget: container, insertLeft: true}); + } + + _applyCompletionHint(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 currentText = this._codeMirror.getRange(from, cursor); + + this._createCompletionHintMarker(cursor, replacementText.replace(currentText, "")); + } + + this._ignoreChange = true; + this._ignoreNextCursorActivity = true; + + this._codeMirror.operation(update.bind(this)); + + delete this._ignoreChange; + } + + _commitCompletionHint() + { + 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() + { + 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(nonatomic, dontRestorePrefix) + { + if (!this._completionHintMarker) + return; + + this._notifyCompletionsHiddenSoon(); + + function clearMarker(marker) + { + if (!marker) + return; + + var range = marker.find(); + if (range) + marker.clear(); + + return null; + } + + function update() + { + this._completionHintMarker = clearMarker(this._completionHintMarker); + + 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(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 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(force) + { + if (this._codeMirror.somethingSelected()) { + this.hideCompletions(); + return; + } + + 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(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 []; + + // Don't complete in the middle of a property name. + if (/^[a-z]/i.test(suffix)) + 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. Likewise if the suffix starts with an open + // paren we are changing a function name so don't add a suffix. + this._implicitSuffix = " "; + if (suffix === ";") + this._implicitSuffix = this._noEndingSemicolon ? "" : ";"; + else if (suffix.startsWith("(")) + this._implicitSuffix = ""; + + // Don't use an implicit suffix if it would be the same as the existing suffix. + if (this._implicitSuffix === suffix) + this._implicitSuffix = ""; + + let completions = WebInspector.CSSKeywordCompletions.forProperty(propertyName).startsWith(this._prefix); + + if (suffix.startsWith("(")) + completions = completions.map((x) => x.replace(/\(\)$/, "")); + + return completions; + } + + this._implicitSuffix = suffix !== ":" ? ": " : ""; + + // Complete property names. + return WebInspector.CSSCompletions.cssNameCompletions.startsWith(this._prefix); + } + + _generateJavaScriptCompletions(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 = []; + + var prefix = this._prefix; + + var localState = mainToken.state.localState ? mainToken.state.localState : mainToken.state; + + var declaringVariable = localState.lexical.type === "vardef"; + var insideSwitch = localState.lexical.prev ? localState.lexical.prev.info === "switch" : false; + var insideBlock = localState.lexical.prev ? localState.lexical.prev.type === "}" : false; + var insideParenthesis = localState.lexical.type === ")"; + var insideBrackets = localState.lexical.type === "]"; + + // FIXME: Include module keywords if we know this is a module environment. + // var moduleKeywords = ["default", "export", "import"]; + + var allKeywords = [ + "break", "case", "catch", "class", "const", "continue", "debugger", "default", + "delete", "do", "else", "extends", "false", "finally", "for", "function", + "if", "in", "Infinity", "instanceof", "let", "NaN", "new", "null", "of", + "return", "static", "super", "switch", "this", "throw", "true", "try", + "typeof", "undefined", "var", "void", "while", "with", "yield" + ]; + var valueKeywords = ["false", "Infinity", "NaN", "null", "this", "true", "undefined"]; + + var allowedKeywordsInsideBlocks = allKeywords.keySet(); + var allowedKeywordsWhenDeclaringVariable = valueKeywords.keySet(); + var allowedKeywordsInsideParenthesis = valueKeywords.concat(["class", "function"]).keySet(); + var allowedKeywordsInsideBrackets = allowedKeywordsInsideParenthesis; + var 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.includes(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", "yield"]); + break; + + case "{": + matchKeywords(["do", "else", "finally", "return", "try", "yield"]); + break; + + case ":": + if (insideSwitch) + matchKeywords(["case", "default"]); + break; + + case ";": + matchVariables(); + matchKeywords(valueKeywords); + matchKeywords(["break", "continue", "debugger", "return", "void"]); + break; + } + + return matchingWords; + } + + _handleUpKey(codeMirror) + { + if (!this._hasPendingCompletion()) + return CodeMirror.Pass; + + if (!this.isShowingCompletions()) + return; + + this._suggestionsView.selectPrevious(); + } + + _handleDownKey(codeMirror) + { + if (!this._hasPendingCompletion()) + return CodeMirror.Pass; + + if (!this.isShowingCompletions()) + return; + + this._suggestionsView.selectNext(); + } + + _handleRightOrEnterKey(codeMirror) + { + if (!this._hasPendingCompletion()) + return CodeMirror.Pass; + + if (!this.isShowingCompletions()) + return; + + this._commitCompletionHint(); + } + + _handleEscapeKey(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(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(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; + + this._completeAtCurrentPosition(false); + } + + _handleCursorActivity(codeMirror) + { + if (this._ignoreChange) + return; + + if (this._ignoreNextCursorActivity) { + delete this._ignoreNextCursorActivity; + return; + } + + this.hideCompletions(); + } + + _handleHideKey(codeMirror) + { + this.hideCompletions(); + + return CodeMirror.Pass; + } + + _handleHideAction(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.UpdatePromise = { + Canceled: "code-mirror-completion-controller-canceled", + CompletionsFound: "code-mirror-completion-controller-completions-found", + NoCompletionsFound: "code-mirror-completion-controller-no-completions-found" +}; + +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"; |