forked from qt-creator/qt-creator
Debugger: Populate "Source Files" for QML, showing shadow .js structure
Request the sources actually seen by v8, and show them in a separate file (click on the leftmost column). Automatically populating 'Source Files' and also making the functionality available for mixed debugging requires some more thought ... Change-Id: Id8f55eed9c842b545434a6c608fb34d370a8f8bb Reviewed-by: Aurindam Jana <aurindam.jana@nokia.com>
This commit is contained in:
@@ -83,6 +83,8 @@ public:
|
|||||||
|
|
||||||
virtual void setEngine(QmlEngine *engine) = 0;
|
virtual void setEngine(QmlEngine *engine) = 0;
|
||||||
|
|
||||||
|
virtual void getSourceFiles() {}
|
||||||
|
|
||||||
void flushSendBuffer();
|
void flushSendBuffer();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "debuggerconstants.h"
|
#include "debuggerconstants.h"
|
||||||
#include "debuggercore.h"
|
#include "debuggercore.h"
|
||||||
#include "debuggerdialogs.h"
|
#include "debuggerdialogs.h"
|
||||||
|
#include "debuggerinternalconstants.h"
|
||||||
#include "debuggermainwindow.h"
|
#include "debuggermainwindow.h"
|
||||||
#include "debuggerrunner.h"
|
#include "debuggerrunner.h"
|
||||||
#include "debuggerstringutils.h"
|
#include "debuggerstringutils.h"
|
||||||
@@ -48,18 +49,24 @@
|
|||||||
#include "registerhandler.h"
|
#include "registerhandler.h"
|
||||||
#include "stackhandler.h"
|
#include "stackhandler.h"
|
||||||
#include "watchhandler.h"
|
#include "watchhandler.h"
|
||||||
|
#include "sourcefileshandler.h"
|
||||||
#include "watchutils.h"
|
#include "watchutils.h"
|
||||||
|
|
||||||
#include <extensionsystem/pluginmanager.h>
|
#include <extensionsystem/pluginmanager.h>
|
||||||
#include <projectexplorer/applicationlauncher.h>
|
#include <projectexplorer/applicationlauncher.h>
|
||||||
#include <qmljsdebugclient/qdeclarativeoutputparser.h>
|
#include <qmljsdebugclient/qdeclarativeoutputparser.h>
|
||||||
|
#include <qmljseditor/qmljseditorconstants.h>
|
||||||
|
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/qtcassert.h>
|
#include <utils/qtcassert.h>
|
||||||
#include <utils/fileinprojectfinder.h>
|
#include <utils/fileinprojectfinder.h>
|
||||||
|
|
||||||
#include <coreplugin/icore.h>
|
#include <coreplugin/coreconstants.h>
|
||||||
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/helpmanager.h>
|
#include <coreplugin/helpmanager.h>
|
||||||
|
#include <coreplugin/icore.h>
|
||||||
|
|
||||||
|
#include <texteditor/itexteditor.h>
|
||||||
|
|
||||||
#include <QtCore/QDateTime>
|
#include <QtCore/QDateTime>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
@@ -71,6 +78,7 @@
|
|||||||
#include <QtGui/QApplication>
|
#include <QtGui/QApplication>
|
||||||
#include <QtGui/QMainWindow>
|
#include <QtGui/QMainWindow>
|
||||||
#include <QtGui/QMessageBox>
|
#include <QtGui/QMessageBox>
|
||||||
|
#include <QtGui/QPlainTextEdit>
|
||||||
#include <QtGui/QToolTip>
|
#include <QtGui/QToolTip>
|
||||||
#include <QtGui/QTextDocument>
|
#include <QtGui/QTextDocument>
|
||||||
|
|
||||||
@@ -102,6 +110,8 @@ private:
|
|||||||
Utils::FileInProjectFinder fileFinder;
|
Utils::FileInProjectFinder fileFinder;
|
||||||
QTimer m_noDebugOutputTimer;
|
QTimer m_noDebugOutputTimer;
|
||||||
QmlJsDebugClient::QDeclarativeOutputParser m_outputParser;
|
QmlJsDebugClient::QDeclarativeOutputParser m_outputParser;
|
||||||
|
QHash<QString, QTextDocument*> m_sourceDocuments;
|
||||||
|
QHash<QString, QWeakPointer<TextEditor::ITextEditor> > m_sourceEditors;
|
||||||
};
|
};
|
||||||
|
|
||||||
QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q)
|
QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q)
|
||||||
@@ -170,6 +180,16 @@ QmlEngine::~QmlEngine()
|
|||||||
pluginManager->removeObject(this);
|
pluginManager->removeObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<Core::IEditor *> editorsToClose;
|
||||||
|
|
||||||
|
QHash<QString, QWeakPointer<TextEditor::ITextEditor> >::iterator iter;
|
||||||
|
for (iter = d->m_sourceEditors.begin(); iter != d->m_sourceEditors.end(); ++iter) {
|
||||||
|
QWeakPointer<TextEditor::ITextEditor> textEditPtr = iter.value();
|
||||||
|
if (textEditPtr)
|
||||||
|
editorsToClose << textEditPtr.data();
|
||||||
|
}
|
||||||
|
Core::EditorManager::instance()->closeEditors(editorsToClose);
|
||||||
|
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,6 +342,46 @@ void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const
|
|||||||
DebuggerEngine::showMessage(msg, channel, timeout);
|
DebuggerEngine::showMessage(msg, channel, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlEngine::gotoLocation(const Location &location)
|
||||||
|
{
|
||||||
|
const QString fileName = location.fileName();
|
||||||
|
if (QUrl(fileName).isLocalFile()) {
|
||||||
|
// internal file from source files -> show generated .js
|
||||||
|
QString fileName = location.fileName();
|
||||||
|
QTC_ASSERT(d->m_sourceDocuments.contains(fileName), return);
|
||||||
|
const QString jsSource = d->m_sourceDocuments.value(fileName)->toPlainText();
|
||||||
|
|
||||||
|
Core::IEditor *editor = 0;
|
||||||
|
|
||||||
|
Core::EditorManager *editorManager = Core::EditorManager::instance();
|
||||||
|
QList<Core::IEditor *> editors = editorManager->editorsForFileName(location.fileName());
|
||||||
|
if (editors.isEmpty()) {
|
||||||
|
QString titlePattern = tr("JS Source for %1").arg(fileName);
|
||||||
|
editor = editorManager->openEditorWithContents(QmlJSEditor::Constants::C_QMLJSEDITOR_ID,
|
||||||
|
&titlePattern);
|
||||||
|
if (editor) {
|
||||||
|
editor->setProperty(Constants::OPENED_BY_DEBUGGER, true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
editor = editors.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor);
|
||||||
|
if (!textEditor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QPlainTextEdit *plainTextEdit =
|
||||||
|
qobject_cast<QPlainTextEdit *>(editor->widget());
|
||||||
|
if (!plainTextEdit)
|
||||||
|
return;
|
||||||
|
plainTextEdit->setPlainText(jsSource);
|
||||||
|
plainTextEdit->setReadOnly(true);
|
||||||
|
editorManager->activateEditor(editor);
|
||||||
|
} else {
|
||||||
|
DebuggerEngine::gotoLocation(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QmlEngine::closeConnection()
|
void QmlEngine::closeConnection()
|
||||||
{
|
{
|
||||||
disconnect(watchersModel(),SIGNAL(layoutChanged()),this,SLOT(synchronizeWatchers()));
|
disconnect(watchersModel(),SIGNAL(layoutChanged()),this,SLOT(synchronizeWatchers()));
|
||||||
@@ -643,6 +703,13 @@ void QmlEngine::reloadModules()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlEngine::reloadSourceFiles()
|
||||||
|
{
|
||||||
|
if (d->m_adapter.activeDebuggerClient()) {
|
||||||
|
d->m_adapter.activeDebuggerClient()->getSourceFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QmlEngine::requestModuleSymbols(const QString &moduleName)
|
void QmlEngine::requestModuleSymbols(const QString &moduleName)
|
||||||
{
|
{
|
||||||
Q_UNUSED(moduleName)
|
Q_UNUSED(moduleName)
|
||||||
@@ -783,6 +850,61 @@ void QmlEngine::logMessage(const QString &service, LogDirection direction, const
|
|||||||
showMessage(msg, LogDebug);
|
showMessage(msg, LogDebug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlEngine::setSourceFiles(const QStringList &fileNames)
|
||||||
|
{
|
||||||
|
QMap<QString,QString> files;
|
||||||
|
foreach (const QString &file, fileNames) {
|
||||||
|
QString shortName = file;
|
||||||
|
QString fullName = d->fileFinder.findFile(file);
|
||||||
|
files.insert(shortName, fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFilesHandler()->setSourceFiles(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QmlEngine::updateScriptSource(const QString &fileName, int lineOffset, int columnOffset,
|
||||||
|
const QString &source)
|
||||||
|
{
|
||||||
|
QTextDocument *document = 0;
|
||||||
|
if (d->m_sourceDocuments.contains(fileName)) {
|
||||||
|
document = d->m_sourceDocuments.value(fileName);
|
||||||
|
} else {
|
||||||
|
document = new QTextDocument(this);
|
||||||
|
d->m_sourceDocuments.insert(fileName, document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're getting an unordered set of snippets that can even interleave
|
||||||
|
// Therefore we've to carefully update the existing document
|
||||||
|
|
||||||
|
QTextCursor cursor(document);
|
||||||
|
for (int i = 0; i < lineOffset; ++i) {
|
||||||
|
if (!cursor.movePosition(QTextCursor::NextBlock))
|
||||||
|
cursor.insertBlock();
|
||||||
|
}
|
||||||
|
QTC_CHECK(cursor.blockNumber() == lineOffset);
|
||||||
|
|
||||||
|
for (int i = 0; i < columnOffset; ++i) {
|
||||||
|
if (!cursor.movePosition(QTextCursor::NextCharacter))
|
||||||
|
cursor.insertText(QLatin1String(" "));
|
||||||
|
}
|
||||||
|
QTC_CHECK(cursor.positionInBlock() == columnOffset);
|
||||||
|
|
||||||
|
QStringList lines = source.split(QLatin1Char('\n'));
|
||||||
|
foreach (QString line, lines) {
|
||||||
|
if (line.endsWith(QLatin1Char('\r')))
|
||||||
|
line.remove(line.size() -1, 1);
|
||||||
|
|
||||||
|
// line already there?
|
||||||
|
QTextCursor existingCursor(cursor);
|
||||||
|
existingCursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
|
||||||
|
if (existingCursor.selectedText() != line)
|
||||||
|
cursor.insertText(line);
|
||||||
|
|
||||||
|
if (!cursor.movePosition(QTextCursor::NextBlock))
|
||||||
|
cursor.insertBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QmlAdapter *QmlEngine::adapter() const
|
QmlAdapter *QmlEngine::adapter() const
|
||||||
{
|
{
|
||||||
return &d->m_adapter;
|
return &d->m_adapter;
|
||||||
|
|||||||
@@ -68,12 +68,17 @@ public:
|
|||||||
|
|
||||||
void showMessage(const QString &msg, int channel = LogDebug,
|
void showMessage(const QString &msg, int channel = LogDebug,
|
||||||
int timeout = -1) const;
|
int timeout = -1) const;
|
||||||
|
void gotoLocation(const Internal::Location &location);
|
||||||
|
|
||||||
void filterApplicationMessage(const QString &msg, int channel);
|
void filterApplicationMessage(const QString &msg, int channel);
|
||||||
QString toFileInProject(const QUrl &fileUrl);
|
QString toFileInProject(const QUrl &fileUrl);
|
||||||
void inferiorSpontaneousStop();
|
void inferiorSpontaneousStop();
|
||||||
|
|
||||||
void logMessage(const QString &service, LogDirection direction, const QString &str);
|
void logMessage(const QString &service, LogDirection direction, const QString &str);
|
||||||
|
|
||||||
|
void setSourceFiles(const QStringList &fileNames);
|
||||||
|
void updateScriptSource(const QString &fileName, int lineOffset, int columnOffset, const QString &source);
|
||||||
|
|
||||||
QmlAdapter *adapter() const;
|
QmlAdapter *adapter() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
@@ -120,12 +125,14 @@ private:
|
|||||||
|
|
||||||
void assignValueInDebugger(const WatchData *data,
|
void assignValueInDebugger(const WatchData *data,
|
||||||
const QString &expr, const QVariant &value);
|
const QString &expr, const QVariant &value);
|
||||||
|
|
||||||
|
|
||||||
void loadSymbols(const QString &moduleName);
|
void loadSymbols(const QString &moduleName);
|
||||||
void loadAllSymbols();
|
void loadAllSymbols();
|
||||||
void requestModuleSymbols(const QString &moduleName);
|
void requestModuleSymbols(const QString &moduleName);
|
||||||
void reloadModules();
|
void reloadModules();
|
||||||
void reloadRegisters() {}
|
void reloadRegisters() {}
|
||||||
void reloadSourceFiles() {}
|
void reloadSourceFiles();
|
||||||
void reloadFullStack() {}
|
void reloadFullStack() {}
|
||||||
|
|
||||||
bool supportsThreads() const { return false; }
|
bool supportsThreads() const { return false; }
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ public:
|
|||||||
|
|
||||||
QScriptValue parser;
|
QScriptValue parser;
|
||||||
QScriptValue stringifier;
|
QScriptValue stringifier;
|
||||||
|
QStringList scriptSourceRequests;
|
||||||
|
|
||||||
QHash<int, QString> evaluatingExpression;
|
QHash<int, QString> evaluatingExpression;
|
||||||
QHash<int, QByteArray> localsAndWatchers;
|
QHash<int, QByteArray> localsAndWatchers;
|
||||||
@@ -447,7 +448,7 @@ void QmlV8DebuggerClientPrivate::scopes(int frameNumber)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool includeSource,
|
void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool includeSource,
|
||||||
const QVariant /*filter*/)
|
const QVariant filter)
|
||||||
{
|
{
|
||||||
// { "seq" : <number>,
|
// { "seq" : <number>,
|
||||||
// "type" : "request",
|
// "type" : "request",
|
||||||
@@ -482,6 +483,16 @@ void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool i
|
|||||||
if (includeSource)
|
if (includeSource)
|
||||||
args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource));
|
args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource));
|
||||||
|
|
||||||
|
QScriptValue filterValue;
|
||||||
|
if (filter.type() == QVariant::String)
|
||||||
|
filterValue = QScriptValue(filter.toString());
|
||||||
|
else if (filter.type() == QVariant::Int)
|
||||||
|
filterValue = QScriptValue(filter.toInt());
|
||||||
|
else
|
||||||
|
QTC_CHECK(!filter.isValid());
|
||||||
|
|
||||||
|
args.setProperty(_(FILTER), filterValue);
|
||||||
|
|
||||||
jsonVal.setProperty(_(ARGUMENTS), args);
|
jsonVal.setProperty(_(ARGUMENTS), args);
|
||||||
|
|
||||||
const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal);
|
const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal);
|
||||||
@@ -1173,6 +1184,11 @@ void QmlV8DebuggerClient::setEngine(QmlEngine *engine)
|
|||||||
d->engine = engine;
|
d->engine = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QmlV8DebuggerClient::getSourceFiles()
|
||||||
|
{
|
||||||
|
d->scripts(4, QList<int>(), true, QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
void QmlV8DebuggerClient::messageReceived(const QByteArray &data)
|
void QmlV8DebuggerClient::messageReceived(const QByteArray &data)
|
||||||
{
|
{
|
||||||
QDataStream ds(data);
|
QDataStream ds(data);
|
||||||
@@ -1305,6 +1321,53 @@ void QmlV8DebuggerClient::messageReceived(const QByteArray &data)
|
|||||||
} else if (debugCommand == _(SCOPES)) {
|
} else if (debugCommand == _(SCOPES)) {
|
||||||
} else if (debugCommand == _(SOURCE)) {
|
} else if (debugCommand == _(SOURCE)) {
|
||||||
} else if (debugCommand == _(SCRIPTS)) {
|
} else if (debugCommand == _(SCRIPTS)) {
|
||||||
|
// { "seq" : <number>,
|
||||||
|
// "type" : "response",
|
||||||
|
// "request_seq" : <number>,
|
||||||
|
// "command" : "scripts",
|
||||||
|
// "body" : [ { "name" : <name of the script>,
|
||||||
|
// "id" : <id of the script>
|
||||||
|
// "lineOffset" : <line offset within the containing resource>
|
||||||
|
// "columnOffset" : <column offset within the containing resource>
|
||||||
|
// "lineCount" : <number of lines in the script>
|
||||||
|
// "data" : <optional data object added through the API>
|
||||||
|
// "source" : <source of the script if includeSource was specified in the request>
|
||||||
|
// "sourceStart" : <first 80 characters of the script if includeSource was not specified in the request>
|
||||||
|
// "sourceLength" : <total length of the script in characters>
|
||||||
|
// "scriptType" : <script type (see request for values)>
|
||||||
|
// "compilationType" : < How was this script compiled:
|
||||||
|
// 0 if script was compiled through the API
|
||||||
|
// 1 if script was compiled through eval
|
||||||
|
// >
|
||||||
|
// "evalFromScript" : <if "compilationType" is 1 this is the script from where eval was called>
|
||||||
|
// "evalFromLocation" : { line : < if "compilationType" is 1 this is the line in the script from where eval was called>
|
||||||
|
// column : < if "compilationType" is 1 this is the column in the script from where eval was called>
|
||||||
|
// ]
|
||||||
|
// "running" : <is the VM running after sending this response>
|
||||||
|
// "success" : true
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
const QVariantList body = resp.value(_(BODY)).toList();
|
||||||
|
|
||||||
|
QStringList sourceFiles;
|
||||||
|
for (int i = 0; i < body.size(); ++i) {
|
||||||
|
const QVariantMap entryMap = body.at(i).toMap();
|
||||||
|
const int lineOffset = entryMap.value("lineOffset").toInt();
|
||||||
|
const int columnOffset = entryMap.value("columnOffset").toInt();
|
||||||
|
const QString name = entryMap.value("name").toString();
|
||||||
|
const QString source = entryMap.value("source").toString();
|
||||||
|
|
||||||
|
if (name.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!sourceFiles.contains(name))
|
||||||
|
sourceFiles << name;
|
||||||
|
|
||||||
|
d->engine->updateScriptSource(name, lineOffset, columnOffset, source);
|
||||||
|
}
|
||||||
|
d->engine->setSourceFiles(sourceFiles);
|
||||||
|
}
|
||||||
} else if (debugCommand == _(VERSION)) {
|
} else if (debugCommand == _(VERSION)) {
|
||||||
d->logReceiveMessage(QString(_("Using V8 Version: %1")).arg(
|
d->logReceiveMessage(QString(_("Using V8 Version: %1")).arg(
|
||||||
resp.value(_(BODY)).toMap().
|
resp.value(_(BODY)).toMap().
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ public:
|
|||||||
|
|
||||||
void setEngine(QmlEngine *engine);
|
void setEngine(QmlEngine *engine);
|
||||||
|
|
||||||
|
void getSourceFiles();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void messageReceived(const QByteArray &data);
|
void messageReceived(const QByteArray &data);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user