637 lines
21 KiB
JavaScript
637 lines
21 KiB
JavaScript
/*
|
|
* 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.
|
|
*/
|
|
|
|
WI.CodeMirrorTokenTrackingController = class CodeMirrorTokenTrackingController extends WI.Object
|
|
{
|
|
constructor(codeMirror, delegate)
|
|
{
|
|
super();
|
|
|
|
console.assert(codeMirror);
|
|
|
|
this._codeMirror = codeMirror;
|
|
this._delegate = delegate || null;
|
|
this._mode = WI.CodeMirrorTokenTrackingController.Mode.None;
|
|
|
|
this._mouseOverDelayDuration = 0;
|
|
this._mouseOutReleaseDelayDuration = 0;
|
|
this._classNameForHighlightedRange = null;
|
|
|
|
this._enabled = false;
|
|
this._tracking = false;
|
|
this._previousTokenInfo = null;
|
|
this._hoveredMarker = null;
|
|
this._ignoreNextMouseMove = false;
|
|
|
|
const hidePopover = this._hidePopover.bind(this);
|
|
|
|
this._codeMirror.addKeyMap({
|
|
"Cmd-Enter": this._handleCommandEnterKey.bind(this),
|
|
"Esc": hidePopover
|
|
});
|
|
|
|
this._codeMirror.on("cursorActivity", hidePopover);
|
|
}
|
|
|
|
// Public
|
|
|
|
get delegate()
|
|
{
|
|
return this._delegate;
|
|
}
|
|
|
|
set delegate(x)
|
|
{
|
|
this._delegate = x;
|
|
}
|
|
|
|
get enabled()
|
|
{
|
|
return this._enabled;
|
|
}
|
|
|
|
set enabled(enabled)
|
|
{
|
|
if (this._enabled === enabled)
|
|
return;
|
|
|
|
this._enabled = enabled;
|
|
|
|
var wrapper = this._codeMirror.getWrapperElement();
|
|
if (enabled) {
|
|
wrapper.addEventListener("mouseenter", this);
|
|
wrapper.addEventListener("mouseleave", this);
|
|
this._updateHoveredTokenInfo({left: WI.mouseCoords.x, top: WI.mouseCoords.y});
|
|
this._startTracking();
|
|
} else {
|
|
wrapper.removeEventListener("mouseenter", this);
|
|
wrapper.removeEventListener("mouseleave", this);
|
|
this._stopTracking();
|
|
}
|
|
}
|
|
|
|
get mode()
|
|
{
|
|
return this._mode;
|
|
}
|
|
|
|
set mode(mode)
|
|
{
|
|
var oldMode = this._mode;
|
|
|
|
this._mode = mode || WI.CodeMirrorTokenTrackingController.Mode.None;
|
|
|
|
if (oldMode !== this._mode && this._tracking && this._previousTokenInfo)
|
|
this._processNewHoveredToken(this._previousTokenInfo);
|
|
}
|
|
|
|
get mouseOverDelayDuration()
|
|
{
|
|
return this._mouseOverDelayDuration;
|
|
}
|
|
|
|
set mouseOverDelayDuration(x)
|
|
{
|
|
console.assert(x >= 0);
|
|
this._mouseOverDelayDuration = Math.max(x, 0);
|
|
}
|
|
|
|
get mouseOutReleaseDelayDuration()
|
|
{
|
|
return this._mouseOutReleaseDelayDuration;
|
|
}
|
|
|
|
set mouseOutReleaseDelayDuration(x)
|
|
{
|
|
console.assert(x >= 0);
|
|
this._mouseOutReleaseDelayDuration = Math.max(x, 0);
|
|
}
|
|
|
|
get classNameForHighlightedRange()
|
|
{
|
|
return this._classNameForHighlightedRange;
|
|
}
|
|
|
|
set classNameForHighlightedRange(x)
|
|
{
|
|
this._classNameForHighlightedRange = x || null;
|
|
}
|
|
|
|
get candidate()
|
|
{
|
|
return this._candidate;
|
|
}
|
|
|
|
get hoveredMarker()
|
|
{
|
|
return this._hoveredMarker;
|
|
}
|
|
|
|
set hoveredMarker(hoveredMarker)
|
|
{
|
|
this._hoveredMarker = hoveredMarker;
|
|
}
|
|
|
|
highlightLastHoveredRange()
|
|
{
|
|
if (this._candidate)
|
|
this.highlightRange(this._candidate.hoveredTokenRange);
|
|
}
|
|
|
|
highlightRange(range)
|
|
{
|
|
// Nothing to do if we're trying to highlight the same range.
|
|
if (this._codeMirrorMarkedText && this._codeMirrorMarkedText.className === this._classNameForHighlightedRange) {
|
|
var highlightedRange = this._codeMirrorMarkedText.find();
|
|
if (!highlightedRange)
|
|
return;
|
|
if (WI.compareCodeMirrorPositions(highlightedRange.from, range.start) === 0 &&
|
|
WI.compareCodeMirrorPositions(highlightedRange.to, range.end) === 0)
|
|
return;
|
|
}
|
|
|
|
this.removeHighlightedRange();
|
|
|
|
var className = this._classNameForHighlightedRange || "";
|
|
this._codeMirrorMarkedText = this._codeMirror.markText(range.start, range.end, {className});
|
|
|
|
window.addEventListener("mousemove", this, true);
|
|
}
|
|
|
|
removeHighlightedRange()
|
|
{
|
|
if (!this._codeMirrorMarkedText)
|
|
return;
|
|
|
|
this._codeMirrorMarkedText.clear();
|
|
this._codeMirrorMarkedText = null;
|
|
|
|
window.removeEventListener("mousemove", this, true);
|
|
}
|
|
|
|
// Private
|
|
|
|
_startTracking()
|
|
{
|
|
if (this._tracking)
|
|
return;
|
|
|
|
this._tracking = true;
|
|
this._ignoreNextMouseMove = false;
|
|
|
|
var wrapper = this._codeMirror.getWrapperElement();
|
|
wrapper.addEventListener("mousemove", this, true);
|
|
wrapper.addEventListener("mouseout", this, false);
|
|
wrapper.addEventListener("mousedown", this, false);
|
|
wrapper.addEventListener("mouseup", this, false);
|
|
wrapper.addEventListener("mousewheel", this, false);
|
|
window.addEventListener("blur", this, true);
|
|
}
|
|
|
|
_stopTracking()
|
|
{
|
|
if (!this._tracking)
|
|
return;
|
|
|
|
this._tracking = false;
|
|
this._candidate = null;
|
|
|
|
var wrapper = this._codeMirror.getWrapperElement();
|
|
wrapper.removeEventListener("mousemove", this, true);
|
|
wrapper.removeEventListener("mouseout", this, false);
|
|
wrapper.removeEventListener("mousedown", this, false);
|
|
wrapper.removeEventListener("mouseup", this, false);
|
|
wrapper.removeEventListener("mousewheel", this, false);
|
|
window.removeEventListener("blur", this, true);
|
|
window.removeEventListener("mousemove", this, true);
|
|
|
|
this._resetTrackingStates();
|
|
}
|
|
|
|
handleEvent(event)
|
|
{
|
|
switch (event.type) {
|
|
case "mouseenter":
|
|
this._mouseEntered(event);
|
|
break;
|
|
case "mouseleave":
|
|
this._mouseLeft(event);
|
|
break;
|
|
case "mousemove":
|
|
if (event.currentTarget === window)
|
|
this._mouseMovedWithMarkedText(event);
|
|
else
|
|
this._mouseMovedOverEditor(event);
|
|
break;
|
|
case "mouseout":
|
|
// Only deal with a mouseout event that has the editor wrapper as the target.
|
|
if (!event.currentTarget.contains(event.relatedTarget))
|
|
this._mouseMovedOutOfEditor(event);
|
|
break;
|
|
case "mousedown":
|
|
this._mouseButtonWasPressedOverEditor(event);
|
|
break;
|
|
case "mouseup":
|
|
this._mouseButtonWasReleasedOverEditor(event);
|
|
break;
|
|
case "mousewheel":
|
|
this._ignoreNextMouseMove = true;
|
|
break;
|
|
case "blur":
|
|
this._windowLostFocus(event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_handleCommandEnterKey(codeMirror)
|
|
{
|
|
const tokenInfo = this._getTokenInfoForPosition(codeMirror.getCursor("head"));
|
|
tokenInfo.triggeredBy = WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard;
|
|
this._processNewHoveredToken(tokenInfo);
|
|
}
|
|
|
|
_hidePopover()
|
|
{
|
|
if (!this._candidate)
|
|
return CodeMirror.Pass;
|
|
|
|
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function") {
|
|
const forceHidePopover = true;
|
|
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this, forceHidePopover);
|
|
}
|
|
}
|
|
|
|
_mouseEntered(event)
|
|
{
|
|
if (!this._tracking)
|
|
this._startTracking();
|
|
}
|
|
|
|
_mouseLeft(event)
|
|
{
|
|
this._stopTracking();
|
|
}
|
|
|
|
_mouseMovedWithMarkedText(event)
|
|
{
|
|
if (this._candidate && this._candidate.triggeredBy === WI.CodeMirrorTokenTrackingController.TriggeredBy.Keyboard)
|
|
return;
|
|
|
|
var shouldRelease = !event.target.classList.contains(this._classNameForHighlightedRange);
|
|
if (shouldRelease && this._delegate && typeof this._delegate.tokenTrackingControllerCanReleaseHighlightedRange === "function")
|
|
shouldRelease = this._delegate.tokenTrackingControllerCanReleaseHighlightedRange(this, event.target);
|
|
|
|
if (shouldRelease) {
|
|
if (!this._markedTextMouseoutTimer)
|
|
this._markedTextMouseoutTimer = setTimeout(this._markedTextIsNoLongerHovered.bind(this), this._mouseOutReleaseDelayDuration);
|
|
return;
|
|
}
|
|
|
|
if (this._markedTextMouseoutTimer)
|
|
clearTimeout(this._markedTextMouseoutTimer);
|
|
|
|
this._markedTextMouseoutTimer = 0;
|
|
}
|
|
|
|
_markedTextIsNoLongerHovered()
|
|
{
|
|
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeReleased === "function")
|
|
this._delegate.tokenTrackingControllerHighlightedRangeReleased(this);
|
|
|
|
this._markedTextMouseoutTimer = 0;
|
|
}
|
|
|
|
_mouseMovedOverEditor(event)
|
|
{
|
|
if (this._ignoreNextMouseMove) {
|
|
this._ignoreNextMouseMove = false;
|
|
return;
|
|
}
|
|
|
|
this._updateHoveredTokenInfo({left: event.pageX, top: event.pageY});
|
|
}
|
|
|
|
_updateHoveredTokenInfo(mouseCoords)
|
|
{
|
|
// Get the position in the text and the token at that position.
|
|
var position = this._codeMirror.coordsChar(mouseCoords);
|
|
var token = this._codeMirror.getTokenAt(position);
|
|
|
|
if (!token || !token.type || !token.string) {
|
|
if (this._hoveredMarker && this._delegate && typeof this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker === "function") {
|
|
if (!this._codeMirror.findMarksAt(position).includes(this._hoveredMarker.codeMirrorTextMarker))
|
|
this._delegate.tokenTrackingControllerMouseOutOfHoveredMarker(this, this._hoveredMarker);
|
|
}
|
|
|
|
this._resetTrackingStates();
|
|
return;
|
|
}
|
|
|
|
// Stop right here if we're hovering the same token as we were last time.
|
|
if (this._previousTokenInfo &&
|
|
this._previousTokenInfo.position.line === position.line &&
|
|
this._previousTokenInfo.token.start === token.start &&
|
|
this._previousTokenInfo.token.end === token.end)
|
|
return;
|
|
|
|
// We have a new hovered token.
|
|
var tokenInfo = this._previousTokenInfo = this._getTokenInfoForPosition(position);
|
|
|
|
if (/\bmeta\b/.test(token.type)) {
|
|
let nextTokenPosition = Object.shallowCopy(position);
|
|
nextTokenPosition.ch = tokenInfo.token.end + 1;
|
|
|
|
let nextToken = this._codeMirror.getTokenAt(nextTokenPosition);
|
|
if (nextToken && nextToken.type && !/\bmeta\b/.test(nextToken.type)) {
|
|
console.assert(tokenInfo.token.end === nextToken.start);
|
|
|
|
tokenInfo.token.type = nextToken.type;
|
|
tokenInfo.token.string = tokenInfo.token.string + nextToken.string;
|
|
tokenInfo.token.end = nextToken.end;
|
|
}
|
|
} else {
|
|
let previousTokenPosition = Object.shallowCopy(position);
|
|
previousTokenPosition.ch = tokenInfo.token.start - 1;
|
|
|
|
let previousToken = this._codeMirror.getTokenAt(previousTokenPosition);
|
|
if (previousToken && previousToken.type && /\bmeta\b/.test(previousToken.type)) {
|
|
console.assert(tokenInfo.token.start === previousToken.end);
|
|
|
|
tokenInfo.token.string = previousToken.string + tokenInfo.token.string;
|
|
tokenInfo.token.start = previousToken.start;
|
|
}
|
|
}
|
|
|
|
if (this._tokenHoverTimer)
|
|
clearTimeout(this._tokenHoverTimer);
|
|
|
|
this._tokenHoverTimer = 0;
|
|
|
|
if (this._codeMirrorMarkedText || !this._mouseOverDelayDuration)
|
|
this._processNewHoveredToken(tokenInfo);
|
|
else
|
|
this._tokenHoverTimer = setTimeout(this._processNewHoveredToken.bind(this, tokenInfo), this._mouseOverDelayDuration);
|
|
}
|
|
|
|
_getTokenInfoForPosition(position)
|
|
{
|
|
var token = this._codeMirror.getTokenAt(position);
|
|
var innerMode = CodeMirror.innerMode(this._codeMirror.getMode(), token.state);
|
|
var codeMirrorModeName = innerMode.mode.alternateName || innerMode.mode.name;
|
|
return {
|
|
token,
|
|
position,
|
|
innerMode,
|
|
modeName: codeMirrorModeName
|
|
};
|
|
}
|
|
|
|
_mouseMovedOutOfEditor(event)
|
|
{
|
|
if (this._tokenHoverTimer)
|
|
clearTimeout(this._tokenHoverTimer);
|
|
|
|
this._tokenHoverTimer = 0;
|
|
this._previousTokenInfo = null;
|
|
this._selectionMayBeInProgress = false;
|
|
}
|
|
|
|
_mouseButtonWasPressedOverEditor(event)
|
|
{
|
|
this._selectionMayBeInProgress = true;
|
|
}
|
|
|
|
_mouseButtonWasReleasedOverEditor(event)
|
|
{
|
|
this._selectionMayBeInProgress = false;
|
|
this._mouseMovedOverEditor(event);
|
|
|
|
if (this._codeMirrorMarkedText && this._previousTokenInfo) {
|
|
var position = this._codeMirror.coordsChar({left: event.pageX, top: event.pageY});
|
|
var marks = this._codeMirror.findMarksAt(position);
|
|
for (var i = 0; i < marks.length; ++i) {
|
|
if (marks[i] === this._codeMirrorMarkedText) {
|
|
if (this._delegate && typeof this._delegate.tokenTrackingControllerHighlightedRangeWasClicked === "function")
|
|
this._delegate.tokenTrackingControllerHighlightedRangeWasClicked(this);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_windowLostFocus(event)
|
|
{
|
|
this._resetTrackingStates();
|
|
}
|
|
|
|
_processNewHoveredToken(tokenInfo)
|
|
{
|
|
console.assert(tokenInfo);
|
|
|
|
if (this._selectionMayBeInProgress)
|
|
return;
|
|
|
|
this._candidate = null;
|
|
|
|
switch (this._mode) {
|
|
case WI.CodeMirrorTokenTrackingController.Mode.NonSymbolTokens:
|
|
this._candidate = this._processNonSymbolToken(tokenInfo);
|
|
break;
|
|
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptExpression:
|
|
case WI.CodeMirrorTokenTrackingController.Mode.JavaScriptTypeInformation:
|
|
this._candidate = this._processJavaScriptExpression(tokenInfo);
|
|
break;
|
|
case WI.CodeMirrorTokenTrackingController.Mode.MarkedTokens:
|
|
this._candidate = this._processMarkedToken(tokenInfo);
|
|
break;
|
|
}
|
|
|
|
if (!this._candidate)
|
|
return;
|
|
|
|
this._candidate.triggeredBy = tokenInfo.triggeredBy;
|
|
|
|
if (this._markedTextMouseoutTimer)
|
|
clearTimeout(this._markedTextMouseoutTimer);
|
|
|
|
this._markedTextMouseoutTimer = 0;
|
|
|
|
if (this._delegate && typeof this._delegate.tokenTrackingControllerNewHighlightCandidate === "function")
|
|
this._delegate.tokenTrackingControllerNewHighlightCandidate(this, this._candidate);
|
|
}
|
|
|
|
_processNonSymbolToken(tokenInfo)
|
|
{
|
|
// Ignore any symbol tokens.
|
|
var type = tokenInfo.token.type;
|
|
if (!type)
|
|
return null;
|
|
|
|
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
|
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
|
|
|
return {
|
|
hoveredToken: tokenInfo.token,
|
|
hoveredTokenRange: {start: startPosition, end: endPosition},
|
|
};
|
|
}
|
|
|
|
_processJavaScriptExpression(tokenInfo)
|
|
{
|
|
// Only valid within JavaScript.
|
|
if (tokenInfo.modeName !== "javascript")
|
|
return null;
|
|
|
|
var startPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
|
var endPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
|
|
|
function tokenIsInRange(token, range)
|
|
{
|
|
return token.line >= range.start.line && token.ch >= range.start.ch &&
|
|
token.line <= range.end.line && token.ch <= range.end.ch;
|
|
}
|
|
|
|
// If the hovered token is within a selection, use the selection as our expression.
|
|
if (this._codeMirror.somethingSelected()) {
|
|
var selectionRange = {
|
|
start: this._codeMirror.getCursor("start"),
|
|
end: this._codeMirror.getCursor("end")
|
|
};
|
|
|
|
if (tokenIsInRange(startPosition, selectionRange) || tokenIsInRange(endPosition, selectionRange)) {
|
|
return {
|
|
hoveredToken: tokenInfo.token,
|
|
hoveredTokenRange: selectionRange,
|
|
expression: this._codeMirror.getSelection(),
|
|
expressionRange: selectionRange,
|
|
};
|
|
}
|
|
}
|
|
|
|
// We only handle vars, definitions, properties, and the keyword 'this'.
|
|
var type = tokenInfo.token.type;
|
|
var isProperty = type.indexOf("property") !== -1;
|
|
var isKeyword = type.indexOf("keyword") !== -1;
|
|
if (!isProperty && !isKeyword && type.indexOf("variable") === -1 && type.indexOf("def") === -1)
|
|
return null;
|
|
|
|
// Not object literal property names, but yes if an object literal shorthand property, which is a variable.
|
|
let state = tokenInfo.innerMode.state;
|
|
if (isProperty && state.lexical && state.lexical.type === "}") {
|
|
// Peek ahead to see if the next token is "}" or ",". If it is, we are a shorthand and therefore a variable.
|
|
let shorthand = false;
|
|
let mode = tokenInfo.innerMode.mode;
|
|
let position = {line: tokenInfo.position.line, ch: tokenInfo.token.end};
|
|
WI.walkTokens(this._codeMirror, mode, position, function(tokenType, string) {
|
|
if (tokenType)
|
|
return false;
|
|
if (string === "(")
|
|
return false;
|
|
if (string === "," || string === "}") {
|
|
shorthand = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (!shorthand)
|
|
return null;
|
|
}
|
|
|
|
// Only the "this" keyword.
|
|
if (isKeyword && tokenInfo.token.string !== "this")
|
|
return null;
|
|
|
|
// Work out the full hovered expression.
|
|
var expression = tokenInfo.token.string;
|
|
var expressionStartPosition = {line: tokenInfo.position.line, ch: tokenInfo.token.start};
|
|
while (true) {
|
|
var token = this._codeMirror.getTokenAt(expressionStartPosition);
|
|
if (!token)
|
|
break;
|
|
|
|
var isDot = !token.type && token.string === ".";
|
|
var isExpression = token.type && token.type.includes("m-javascript");
|
|
if (!isDot && !isExpression)
|
|
break;
|
|
|
|
if (isExpression) {
|
|
// Disallow operators. We want the hovered expression to be just a single operand.
|
|
// Also, some operators can modify values, such as pre-increment and assignment operators.
|
|
if (token.type.includes("operator"))
|
|
break;
|
|
|
|
// Don't break out of a template string quasi group.
|
|
if (token.type.includes("string-2"))
|
|
break;
|
|
}
|
|
|
|
expression = token.string + expression;
|
|
expressionStartPosition.ch = token.start;
|
|
}
|
|
|
|
// Return the candidate for this token and expression.
|
|
return {
|
|
hoveredToken: tokenInfo.token,
|
|
hoveredTokenRange: {start: startPosition, end: endPosition},
|
|
expression,
|
|
expressionRange: {start: expressionStartPosition, end: endPosition},
|
|
};
|
|
}
|
|
|
|
_processMarkedToken(tokenInfo)
|
|
{
|
|
return this._processNonSymbolToken(tokenInfo);
|
|
}
|
|
|
|
_resetTrackingStates()
|
|
{
|
|
if (this._tokenHoverTimer)
|
|
clearTimeout(this._tokenHoverTimer);
|
|
|
|
this._tokenHoverTimer = 0;
|
|
|
|
this._selectionMayBeInProgress = false;
|
|
this._previousTokenInfo = null;
|
|
this.removeHighlightedRange();
|
|
}
|
|
};
|
|
|
|
WI.CodeMirrorTokenTrackingController.JumpToSymbolHighlightStyleClassName = "jump-to-symbol-highlight";
|
|
|
|
WI.CodeMirrorTokenTrackingController.Mode = {
|
|
None: "none",
|
|
NonSymbolTokens: "non-symbol-tokens",
|
|
JavaScriptExpression: "javascript-expression",
|
|
JavaScriptTypeInformation: "javascript-type-information",
|
|
MarkedTokens: "marked-tokens"
|
|
};
|
|
|
|
WI.CodeMirrorTokenTrackingController.TriggeredBy = {
|
|
Keyboard: "keyboard",
|
|
Hover: "hover"
|
|
};
|