|
Packit |
f0b94e |
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
Packit |
f0b94e |
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
Packit |
f0b94e |
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
Packit |
f0b94e |
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
Packit |
f0b94e |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
Packit |
f0b94e |
/* globals document, window */
|
|
Packit |
f0b94e |
/* import-globals-from ./debugger-controller.js */
|
|
Packit |
f0b94e |
"use strict";
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Maps known URLs to friendly source group names and put them at the
|
|
Packit |
f0b94e |
// bottom of source list.
|
|
Packit |
f0b94e |
var KNOWN_SOURCE_GROUPS = {
|
|
Packit |
f0b94e |
"Add-on SDK": "resource://gre/modules/commonjs/",
|
|
Packit |
f0b94e |
};
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
KNOWN_SOURCE_GROUPS[L10N.getStr("anonymousSourcesLabel")] = "anonymous";
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
var XULUtils = {
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Create <command> elements within `commandset` with event handlers
|
|
Packit |
f0b94e |
* bound to the `command` event
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param commandset HTML Element
|
|
Packit |
f0b94e |
* A <commandset> element
|
|
Packit |
f0b94e |
* @param commands Object
|
|
Packit |
f0b94e |
* An object where keys specify <command> ids and values
|
|
Packit |
f0b94e |
* specify event handlers to be bound on the `command` event
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
addCommands: function (commandset, commands) {
|
|
Packit |
f0b94e |
Object.keys(commands).forEach(name => {
|
|
Packit |
f0b94e |
let node = document.createElement("command");
|
|
Packit |
f0b94e |
node.id = name;
|
|
Packit |
f0b94e |
// XXX bug 371900: the command element must have an oncommand
|
|
Packit |
f0b94e |
// attribute as a string set by `setAttribute` for keys to use it
|
|
Packit |
f0b94e |
node.setAttribute("oncommand", " ");
|
|
Packit |
f0b94e |
node.addEventListener("command", commands[name]);
|
|
Packit |
f0b94e |
commandset.appendChild(node);
|
|
Packit |
f0b94e |
});
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
};
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Used to detect minification for automatic pretty printing
|
|
Packit |
f0b94e |
const SAMPLE_SIZE = 50; // no of lines
|
|
Packit |
f0b94e |
const INDENT_COUNT_THRESHOLD = 5; // percentage
|
|
Packit |
f0b94e |
const CHARACTER_LIMIT = 250; // line character limit
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Utility functions for handling sources.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
var SourceUtils = {
|
|
Packit |
f0b94e |
_labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
|
|
Packit |
f0b94e |
_groupsCache: new Map(),
|
|
Packit |
f0b94e |
_minifiedCache: new Map(),
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Returns true if the specified url and/or content type are specific to
|
|
Packit |
f0b94e |
* javascript files.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @return boolean
|
|
Packit |
f0b94e |
* True if the source is likely javascript.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
isJavaScript: function (aUrl, aContentType = "") {
|
|
Packit |
f0b94e |
return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
|
|
Packit |
f0b94e |
aContentType.includes("javascript");
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Determines if the source text is minified by using
|
|
Packit |
f0b94e |
* the percentage indented of a subset of lines
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @return object
|
|
Packit |
f0b94e |
* A promise that resolves to true if source text is minified.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
isMinified: function (key, text) {
|
|
Packit |
f0b94e |
if (this._minifiedCache.has(key)) {
|
|
Packit |
f0b94e |
return this._minifiedCache.get(key);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let isMinified;
|
|
Packit |
f0b94e |
let lineEndIndex = 0;
|
|
Packit |
f0b94e |
let lineStartIndex = 0;
|
|
Packit |
f0b94e |
let lines = 0;
|
|
Packit |
f0b94e |
let indentCount = 0;
|
|
Packit |
f0b94e |
let overCharLimit = false;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Strip comments.
|
|
Packit |
f0b94e |
text = text.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
while (lines++ < SAMPLE_SIZE) {
|
|
Packit |
f0b94e |
lineEndIndex = text.indexOf("\n", lineStartIndex);
|
|
Packit |
f0b94e |
if (lineEndIndex == -1) {
|
|
Packit |
f0b94e |
break;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
if (/^\s+/.test(text.slice(lineStartIndex, lineEndIndex))) {
|
|
Packit |
f0b94e |
indentCount++;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// For files with no indents but are not minified.
|
|
Packit |
f0b94e |
if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
|
|
Packit |
f0b94e |
overCharLimit = true;
|
|
Packit |
f0b94e |
break;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
lineStartIndex = lineEndIndex + 1;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
isMinified =
|
|
Packit |
f0b94e |
((indentCount / lines) * 100) < INDENT_COUNT_THRESHOLD || overCharLimit;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
this._minifiedCache.set(key, isMinified);
|
|
Packit |
f0b94e |
return isMinified;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Clears the labels, groups and minify cache, populated by methods like
|
|
Packit |
f0b94e |
* SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
|
|
Packit |
f0b94e |
* This should be done every time the content location changes.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
clearCache: function () {
|
|
Packit |
f0b94e |
this._labelsCache.clear();
|
|
Packit |
f0b94e |
this._groupsCache.clear();
|
|
Packit |
f0b94e |
this._minifiedCache.clear();
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Gets a unique, simplified label from a source url.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param string aUrl
|
|
Packit |
f0b94e |
* The source url.
|
|
Packit |
f0b94e |
* @return string
|
|
Packit |
f0b94e |
* The simplified label.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
getSourceLabel: function (aUrl) {
|
|
Packit |
f0b94e |
let cachedLabel = this._labelsCache.get(aUrl);
|
|
Packit |
f0b94e |
if (cachedLabel) {
|
|
Packit |
f0b94e |
return cachedLabel;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let sourceLabel = null;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
|
|
Packit |
f0b94e |
if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
|
|
Packit |
f0b94e |
sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
if (!sourceLabel) {
|
|
Packit |
f0b94e |
sourceLabel = this.trimUrl(aUrl);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
|
|
Packit |
f0b94e |
this._labelsCache.set(aUrl, unicodeLabel);
|
|
Packit |
f0b94e |
return unicodeLabel;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Gets as much information as possible about the hostname and directory paths
|
|
Packit |
f0b94e |
* of an url to create a short url group identifier.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param string aUrl
|
|
Packit |
f0b94e |
* The source url.
|
|
Packit |
f0b94e |
* @return string
|
|
Packit |
f0b94e |
* The simplified group.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
getSourceGroup: function (aUrl) {
|
|
Packit |
f0b94e |
let cachedGroup = this._groupsCache.get(aUrl);
|
|
Packit |
f0b94e |
if (cachedGroup) {
|
|
Packit |
f0b94e |
return cachedGroup;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
try {
|
|
Packit |
f0b94e |
// Use an nsIURL to parse all the url path parts.
|
|
Packit |
f0b94e |
var uri = Services.io.newURI(aUrl).QueryInterface(Ci.nsIURL);
|
|
Packit |
f0b94e |
} catch (e) {
|
|
Packit |
f0b94e |
// This doesn't look like a url, or nsIURL can't handle it.
|
|
Packit |
f0b94e |
return "";
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let groupLabel = uri.prePath;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
|
|
Packit |
f0b94e |
if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
|
|
Packit |
f0b94e |
groupLabel = name;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
|
|
Packit |
f0b94e |
this._groupsCache.set(aUrl, unicodeLabel);
|
|
Packit |
f0b94e |
return unicodeLabel;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Trims the url by shortening it if it exceeds a certain length, adding an
|
|
Packit |
f0b94e |
* ellipsis at the end.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param string aUrl
|
|
Packit |
f0b94e |
* The source url.
|
|
Packit |
f0b94e |
* @param number aLength [optional]
|
|
Packit |
f0b94e |
* The expected source url length.
|
|
Packit |
f0b94e |
* @param number aSection [optional]
|
|
Packit |
f0b94e |
* The section to trim. Supported values: "start", "center", "end"
|
|
Packit |
f0b94e |
* @return string
|
|
Packit |
f0b94e |
* The shortened url.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
trimUrlLength: function (aUrl, aLength, aSection) {
|
|
Packit |
f0b94e |
aLength = aLength || SOURCE_URL_DEFAULT_MAX_LENGTH;
|
|
Packit |
f0b94e |
aSection = aSection || "end";
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
if (aUrl.length > aLength) {
|
|
Packit |
f0b94e |
switch (aSection) {
|
|
Packit |
f0b94e |
case "start":
|
|
Packit |
f0b94e |
return ELLIPSIS + aUrl.slice(-aLength);
|
|
Packit |
f0b94e |
break;
|
|
Packit |
f0b94e |
case "center":
|
|
Packit |
f0b94e |
return aUrl.substr(0, aLength / 2 - 1) + ELLIPSIS + aUrl.slice(-aLength / 2 + 1);
|
|
Packit |
f0b94e |
break;
|
|
Packit |
f0b94e |
case "end":
|
|
Packit |
f0b94e |
return aUrl.substr(0, aLength) + ELLIPSIS;
|
|
Packit |
f0b94e |
break;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
return aUrl;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Trims the query part or reference identifier of a url string, if necessary.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param string aUrl
|
|
Packit |
f0b94e |
* The source url.
|
|
Packit |
f0b94e |
* @return string
|
|
Packit |
f0b94e |
* The shortened url.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
trimUrlQuery: function (aUrl) {
|
|
Packit |
f0b94e |
let length = aUrl.length;
|
|
Packit |
f0b94e |
let q1 = aUrl.indexOf("?");
|
|
Packit |
f0b94e |
let q2 = aUrl.indexOf("&";;
|
|
Packit |
f0b94e |
let q3 = aUrl.indexOf("#");
|
|
Packit |
f0b94e |
let q = Math.min(q1 != -1 ? q1 : length,
|
|
Packit |
f0b94e |
q2 != -1 ? q2 : length,
|
|
Packit |
f0b94e |
q3 != -1 ? q3 : length);
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
return aUrl.slice(0, q);
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
/**
|
|
Packit |
f0b94e |
* Trims as much as possible from a url, while keeping the label unique
|
|
Packit |
f0b94e |
* in the sources container.
|
|
Packit |
f0b94e |
*
|
|
Packit |
f0b94e |
* @param string | nsIURL aUrl
|
|
Packit |
f0b94e |
* The source url.
|
|
Packit |
f0b94e |
* @param string aLabel [optional]
|
|
Packit |
f0b94e |
* The resulting label at each step.
|
|
Packit |
f0b94e |
* @param number aSeq [optional]
|
|
Packit |
f0b94e |
* The current iteration step.
|
|
Packit |
f0b94e |
* @return string
|
|
Packit |
f0b94e |
* The resulting label at the final step.
|
|
Packit |
f0b94e |
*/
|
|
Packit |
f0b94e |
trimUrl: function (aUrl, aLabel, aSeq) {
|
|
Packit |
f0b94e |
if (!(aUrl instanceof Ci.nsIURL)) {
|
|
Packit |
f0b94e |
try {
|
|
Packit |
f0b94e |
// Use an nsIURL to parse all the url path parts.
|
|
Packit |
f0b94e |
aUrl = Services.io.newURI(aUrl).QueryInterface(Ci.nsIURL);
|
|
Packit |
f0b94e |
} catch (e) {
|
|
Packit |
f0b94e |
// This doesn't look like a url, or nsIURL can't handle it.
|
|
Packit |
f0b94e |
return aUrl;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
if (!aSeq) {
|
|
Packit |
f0b94e |
let name = aUrl.fileName;
|
|
Packit |
f0b94e |
if (name) {
|
|
Packit |
f0b94e |
// This is a regular file url, get only the file name (contains the
|
|
Packit |
f0b94e |
// base name and extension if available).
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// If this url contains an invalid query, unfortunately nsIURL thinks
|
|
Packit |
f0b94e |
// it's part of the file extension. It must be removed.
|
|
Packit |
f0b94e |
aLabel = aUrl.fileName.replace(/\&.*/, "");
|
|
Packit |
f0b94e |
} else {
|
|
Packit |
f0b94e |
// This is not a file url, hence there is no base name, nor extension.
|
|
Packit |
f0b94e |
// Proceed using other available information.
|
|
Packit |
f0b94e |
aLabel = "";
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
aSeq = 1;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// If we have a label and it doesn't only contain a query...
|
|
Packit |
f0b94e |
if (aLabel && aLabel.indexOf("?") != 0) {
|
|
Packit |
f0b94e |
// A page may contain multiple requests to the same url but with different
|
|
Packit |
f0b94e |
// queries. It is *not* redundant to show each one.
|
|
Packit |
f0b94e |
if (!DebuggerView.Sources.getItemForAttachment(e => e.label == aLabel)) {
|
|
Packit |
f0b94e |
return aLabel;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Append the url query.
|
|
Packit |
f0b94e |
if (aSeq == 1) {
|
|
Packit |
f0b94e |
let query = aUrl.query;
|
|
Packit |
f0b94e |
if (query) {
|
|
Packit |
f0b94e |
return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
aSeq++;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// Append the url reference.
|
|
Packit |
f0b94e |
if (aSeq == 2) {
|
|
Packit |
f0b94e |
let ref = aUrl.ref;
|
|
Packit |
f0b94e |
if (ref) {
|
|
Packit |
f0b94e |
return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
aSeq++;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// Prepend the url directory.
|
|
Packit |
f0b94e |
if (aSeq == 3) {
|
|
Packit |
f0b94e |
let dir = aUrl.directory;
|
|
Packit |
f0b94e |
if (dir) {
|
|
Packit |
f0b94e |
return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
aSeq++;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// Prepend the hostname and port number.
|
|
Packit |
f0b94e |
if (aSeq == 4) {
|
|
Packit |
f0b94e |
let host;
|
|
Packit |
f0b94e |
try {
|
|
Packit |
f0b94e |
// Bug 1261860: jar: URLs throw when accessing `hostPost`
|
|
Packit |
f0b94e |
host = aUrl.hostPort;
|
|
Packit |
f0b94e |
} catch (e) {}
|
|
Packit |
f0b94e |
if (host) {
|
|
Packit |
f0b94e |
return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
aSeq++;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// Use the whole url spec but ignoring the reference.
|
|
Packit |
f0b94e |
if (aSeq == 5) {
|
|
Packit |
f0b94e |
return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
// Give up.
|
|
Packit |
f0b94e |
return aUrl.spec;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
parseSource: function (aDebuggerView, aParser) {
|
|
Packit |
f0b94e |
let editor = aDebuggerView.editor;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let contents = editor.getText();
|
|
Packit |
f0b94e |
let location = aDebuggerView.Sources.selectedValue;
|
|
Packit |
f0b94e |
let parsedSource = aParser.get(contents, location);
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
return parsedSource;
|
|
Packit |
f0b94e |
},
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
findIdentifier: function (aEditor, parsedSource, x, y) {
|
|
Packit |
f0b94e |
let editor = aEditor;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Calculate the editor's line and column at the current x and y coords.
|
|
Packit |
f0b94e |
let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
|
|
Packit |
f0b94e |
let hoveredOffset = editor.getOffset(hoveredPos);
|
|
Packit |
f0b94e |
let hoveredLine = hoveredPos.line;
|
|
Packit |
f0b94e |
let hoveredColumn = hoveredPos.ch;
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// If the script length is negative, we're not hovering JS source code.
|
|
Packit |
f0b94e |
if (scriptInfo.length == -1) {
|
|
Packit |
f0b94e |
return;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
// Using the script offset, determine the actual line and column inside the
|
|
Packit |
f0b94e |
// script, to use when finding identifiers.
|
|
Packit |
f0b94e |
let scriptStart = editor.getPosition(scriptInfo.start);
|
|
Packit |
f0b94e |
let scriptLineOffset = scriptStart.line;
|
|
Packit |
f0b94e |
let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
let scriptLine = hoveredLine - scriptLineOffset;
|
|
Packit |
f0b94e |
let scriptColumn = hoveredColumn - scriptColumnOffset;
|
|
Packit |
f0b94e |
let identifierInfo = parsedSource.getIdentifierAt({
|
|
Packit |
f0b94e |
line: scriptLine + 1,
|
|
Packit |
f0b94e |
column: scriptColumn,
|
|
Packit |
f0b94e |
scriptIndex: scriptInfo.index
|
|
Packit |
f0b94e |
});
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
return identifierInfo;
|
|
Packit |
f0b94e |
}
|
|
Packit |
f0b94e |
};
|