Bug 852931 - cmd+click jumps to function defintion in the debugger. r=jlongster

This commit is contained in:
mitchfriedman 2015-10-11 20:24:31 +02:00
parent 8b5747c4b8
commit f46b45a818
7 changed files with 256 additions and 2 deletions

View File

@ -31,6 +31,7 @@ const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_
const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
const FUNCTION_SEARCH_POPUP_POSITION = "topcenter bottomleft";
const RESIZE_REFRESH_RATE = 50; // ms
const PROMISE_DEBUGGER_URL =
"chrome://devtools/content/promisedebugger/promise-debugger.xhtml";

View File

@ -18,6 +18,7 @@ support-files =
code_breakpoints-other-tabs.js
code_bug-896139.js
code_frame-script.js
code_function-jump-01.js
code_function-search-01.js
code_function-search-02.js
code_function-search-03.js
@ -69,6 +70,7 @@ support-files =
doc_event-listeners-04.html
doc_frame-parameters.html
doc_function-display-name.html
doc_function-jump.html
doc_function-search.html
doc_global-method-override.html
doc_iframes.html
@ -257,6 +259,8 @@ skip-if = e10s # TODO
skip-if = e10s
[browser_dbg_host-layout.js]
skip-if = e10s && debug
[browser_dbg_jump-to-function-definition.js]
skip-if = e10s && debug
[browser_dbg_iframes.js]
skip-if = e10s # TODO
[browser_dbg_instruments-pane-collapse.js]

View File

@ -0,0 +1,45 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the jump to function definition works properly.
*/
const TAB_URL = EXAMPLE_URL + "doc_function-jump.html";
const SCRIPT_URI = EXAMPLE_URL + "code_function-jump-01.js";
function test() {
let gTab, gPanel, gDebugger, gSources;
initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
gTab = aTab;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceShown(gPanel, "-01.js")
.then(jumpToFunctionDefinition)
.then(() => closeDebuggerAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
});
function jumpToFunctionDefinition() {
let callLocation = {line: 5, ch: 0};
let editor = gDebugger.DebuggerView.editor;
let coords = editor.getCoordsFromPosition(callLocation);
gDebugger.DebuggerView.Sources._onMouseDown({ clientX: coords.left,
clientY: coords.top,
metaKey: true });
let deferred = promise.defer();
executeSoon(() => {
is(editor.getDebugLocation(), 1, "foo definition should be highlighted");
deferred.resolve();
});
return deferred.promise;
}
}

View File

@ -0,0 +1,6 @@
function foo() {
//some function
}
foo();

View File

@ -0,0 +1,17 @@
<!-- Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Debugger test page</title>
</head>
<body>
<p>Foo bar, bar, bazz, bar foo bar!</p>
<script type="text/javascript" src="code_function-jump-01.js"></script>
</body>
</html>

View File

@ -316,6 +316,48 @@ var SourceUtils = {
}
// Give up.
return aUrl.spec;
},
parseSource: function(aDebuggerView, aParser) {
let editor = aDebuggerView.editor;
let contents = editor.getText();
let location = aDebuggerView.Sources.selectedValue;
let parsedSource = aParser.get(contents, location);
return parsedSource;
},
findIdentifier: function(aEditor, parsedSource, x, y) {
let editor = aEditor;
// Calculate the editor's line and column at the current x and y coords.
let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
let hoveredOffset = editor.getOffset(hoveredPos);
let hoveredLine = hoveredPos.line;
let hoveredColumn = hoveredPos.ch;
let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
// If the script length is negative, we're not hovering JS source code.
if (scriptInfo.length == -1) {
return;
}
// Using the script offset, determine the actual line and column inside the
// script, to use when finding identifiers.
let scriptStart = editor.getPosition(scriptInfo.start);
let scriptLineOffset = scriptStart.line;
let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
let scriptLine = hoveredLine - scriptLineOffset;
let scriptColumn = hoveredColumn - scriptColumnOffset;
let identifierInfo = parsedSource.getIdentifierAt({
line: scriptLine + 1,
column: scriptColumn,
scriptIndex: scriptInfo.index
});
return identifierInfo;
}
};

View File

@ -20,6 +20,7 @@ function SourcesView(DebuggerController, DebuggerView) {
this.Breakpoints = DebuggerController.Breakpoints;
this.SourceScripts = DebuggerController.SourceScripts;
this.DebuggerView = DebuggerView;
this.Parser = DebuggerController.Parser;
this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
@ -28,6 +29,7 @@ function SourcesView(DebuggerController, DebuggerView) {
this._onEditorLoad = this._onEditorLoad.bind(this);
this._onEditorUnload = this._onEditorUnload.bind(this);
this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
this._onMouseDown = this._onMouseDown.bind(this);
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
@ -69,10 +71,16 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._newTabMenuItem = document.getElementById("debugger-sources-context-newtab");
this._copyUrlMenuItem = document.getElementById("debugger-sources-context-copyurl");
this._noResultsFoundToolTip = new Tooltip(document);
this._noResultsFoundToolTip.defaultPosition = FUNCTION_SEARCH_POPUP_POSITION;
if (Prefs.prettyPrintEnabled) {
this._prettyPrintButton.removeAttribute("hidden");
}
this._editorContainer = document.getElementById("editor");
this._editorContainer.addEventListener("mousedown", this._onMouseDown, false);
window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
this.widget.addEventListener("select", this._onSourceSelect, false);
@ -968,6 +976,81 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
aEditor.off("cursorActivity", this._onEditorCursorActivity);
},
_onMouseDown: function(e) {
this.hideNoResultsTooltip();
if (!e.metaKey) {
return;
}
let editor = this.DebuggerView.editor;
let identifier = this._findIdentifier(e.clientX, e.clientY);
if (!identifier) {
return;
}
let foundDefinitions = this._getFunctionDefinitions(identifier);
if (!foundDefinitions || !foundDefinitions.definitions) {
return;
}
this._showFunctionDefinitionResults(identifier, foundDefinitions.definitions, editor);
},
/**
* Searches for function definition of a function in a given source file
*/
_findDefinition: function(parsedSource, aName) {
let functionDefinitions = parsedSource.getNamedFunctionDefinitions(aName);
let resultList = [];
if (!functionDefinitions || !functionDefinitions.length || !functionDefinitions[0].length) {
return {
definitions: resultList
};
}
//functionDefinitions is a list with an object full of metadata, extract the
//data and use to construct a more useful, less cluttered, contextual list
for (let i=0; i<functionDefinitions.length; i++) {
let functionDefinition = {
source: functionDefinitions[i].sourceUrl,
startLine: functionDefinitions[i][0].functionLocation.start.line,
startColumn: functionDefinitions[i][0].functionLocation.start.column,
name: functionDefinitions[i][0].functionName
}
resultList.push(functionDefinition)
}
return {
definitions: resultList
};
},
/**
* Searches for an identifier underneath the specified position in the
* source editor.
*
* @param number x, y
* The left/top coordinates where to look for an identifier.
*/
_findIdentifier: function(x, y) {
let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
let identifierInfo = SourceUtils.findIdentifier(this.DebuggerView.editor, parsedSource, x, y);
// Not hovering over an identifier
if (!identifierInfo) {
return;
}
return identifierInfo;
},
/**
* The selection listener for the source editor.
*/
@ -986,6 +1069,60 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
}
},
/*
* Uses function definition data to perform actions in different
* cases of how many locations were found: zero, one, or mulitple definitions
*/
_showFunctionDefinitionResults: function(aHoveredFunction, aDefinitionList, aEditor) {
let definitions = aDefinitionList;
let hoveredFunction = aHoveredFunction;
//show a popup saying no results were found
if (definitions.length == 0) {
this._noResultsFoundToolTip.setTextContent({
messages: [L10N.getStr("noMatchingStringsText")]
});
this._markedIdentifier = aEditor.markText(
{ line: hoveredFunction.location.start.line - 1, ch: hoveredFunction.location.start.column },
{ line: hoveredFunction.location.end.line - 1, ch: hoveredFunction.location.end.column });
this._noResultsFoundToolTip.show(this._markedIdentifier.anchor);
} else if(definitions.length == 1) {
this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
} else {
//multiple definitions found, do something else
this.DebuggerView.setEditorLocation(definitions[0].source, definitions[0].startLine);
}
},
/**
* Hides the tooltip and clear marked text popup.
*/
hideNoResultsTooltip: function() {
this._noResultsFoundToolTip.hide();
if (this._markedIdentifier) {
this._markedIdentifier.clear();
this._markedIdentifier = null;
}
},
/*
* Gets the definition locations from function metadata
*/
_getFunctionDefinitions: function(aIdentifierInfo) {
let parsedSource = SourceUtils.parseSource(this.DebuggerView, this.Parser);
let definition_info = this._findDefinition(parsedSource, aIdentifierInfo.name);
//Did not find any definitions for the identifier
if (!definition_info) {
return;
}
return definition_info;
},
/**
* The select listener for the sources container.
*/
@ -1291,7 +1428,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
_cbPanel: null,
_cbTextbox: null,
_selectedBreakpointItem: null,
_conditionalPopupVisible: false
_conditionalPopupVisible: false,
_noResultsFoundToolTip: null,
_markedIdentifier: null
});
DebuggerView.Sources = new SourcesView(DebuggerController, DebuggerView);