diff --git a/src/plugins/debugger/qml/qmldebuggerclient.h b/src/plugins/debugger/qml/qmldebuggerclient.h index 8b92d17d396d0ce4c59618316b7b8796647098f2..11eafe213cb48566674b10301a2976b69795f925 100644 --- a/src/plugins/debugger/qml/qmldebuggerclient.h +++ b/src/plugins/debugger/qml/qmldebuggerclient.h @@ -83,6 +83,8 @@ public: virtual void setEngine(QmlEngine *engine) = 0; + virtual void getSourceFiles() {} + void flushSendBuffer(); signals: diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index d9054312a25487c1ca129fa5889c96f1cf02ed04..66620897b267310d4b338e3d71acaee63bdb8779 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -38,6 +38,7 @@ #include "debuggerconstants.h" #include "debuggercore.h" #include "debuggerdialogs.h" +#include "debuggerinternalconstants.h" #include "debuggermainwindow.h" #include "debuggerrunner.h" #include "debuggerstringutils.h" @@ -48,18 +49,24 @@ #include "registerhandler.h" #include "stackhandler.h" #include "watchhandler.h" +#include "sourcefileshandler.h" #include "watchutils.h" #include <extensionsystem/pluginmanager.h> #include <projectexplorer/applicationlauncher.h> #include <qmljsdebugclient/qdeclarativeoutputparser.h> +#include <qmljseditor/qmljseditorconstants.h> #include <utils/environment.h> #include <utils/qtcassert.h> #include <utils/fileinprojectfinder.h> -#include <coreplugin/icore.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/helpmanager.h> +#include <coreplugin/icore.h> + +#include <texteditor/itexteditor.h> #include <QtCore/QDateTime> #include <QtCore/QDebug> @@ -71,6 +78,7 @@ #include <QtGui/QApplication> #include <QtGui/QMainWindow> #include <QtGui/QMessageBox> +#include <QtGui/QPlainTextEdit> #include <QtGui/QToolTip> #include <QtGui/QTextDocument> @@ -102,6 +110,8 @@ private: Utils::FileInProjectFinder fileFinder; QTimer m_noDebugOutputTimer; QmlJsDebugClient::QDeclarativeOutputParser m_outputParser; + QHash<QString, QTextDocument*> m_sourceDocuments; + QHash<QString, QWeakPointer<TextEditor::ITextEditor> > m_sourceEditors; }; QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q) @@ -170,6 +180,16 @@ QmlEngine::~QmlEngine() 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; } @@ -322,6 +342,46 @@ void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const 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() { 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) { Q_UNUSED(moduleName) @@ -783,6 +850,61 @@ void QmlEngine::logMessage(const QString &service, LogDirection direction, const 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 { return &d->m_adapter; diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h index cc165bf1a1cb1755f1e3cf32529966623f48212a..2500d55df56a17ecc67172aa3c1f963e12ab0723 100644 --- a/src/plugins/debugger/qml/qmlengine.h +++ b/src/plugins/debugger/qml/qmlengine.h @@ -68,12 +68,17 @@ public: void showMessage(const QString &msg, int channel = LogDebug, int timeout = -1) const; + void gotoLocation(const Internal::Location &location); + void filterApplicationMessage(const QString &msg, int channel); QString toFileInProject(const QUrl &fileUrl); void inferiorSpontaneousStop(); 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; public slots: @@ -120,12 +125,14 @@ private: void assignValueInDebugger(const WatchData *data, const QString &expr, const QVariant &value); + + void loadSymbols(const QString &moduleName); void loadAllSymbols(); void requestModuleSymbols(const QString &moduleName); void reloadModules(); void reloadRegisters() {} - void reloadSourceFiles() {} + void reloadSourceFiles(); void reloadFullStack() {} bool supportsThreads() const { return false; } diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp index c22bb5736c6192b2596786f535b38d690116c886..a76f8fef5f120003489f0e6731c2a820d16b063e 100644 --- a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp +++ b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp @@ -141,6 +141,7 @@ public: QScriptValue parser; QScriptValue stringifier; + QStringList scriptSourceRequests; QHash<int, QString> evaluatingExpression; QHash<int, QByteArray> localsAndWatchers; @@ -447,7 +448,7 @@ void QmlV8DebuggerClientPrivate::scopes(int frameNumber) } void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool includeSource, - const QVariant /*filter*/) + const QVariant filter) { // { "seq" : <number>, // "type" : "request", @@ -482,6 +483,16 @@ void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool i if (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); const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); @@ -1173,6 +1184,11 @@ void QmlV8DebuggerClient::setEngine(QmlEngine *engine) d->engine = engine; } +void QmlV8DebuggerClient::getSourceFiles() +{ + d->scripts(4, QList<int>(), true, QVariant()); +} + void QmlV8DebuggerClient::messageReceived(const QByteArray &data) { QDataStream ds(data); @@ -1305,6 +1321,53 @@ void QmlV8DebuggerClient::messageReceived(const QByteArray &data) } else if (debugCommand == _(SCOPES)) { } else if (debugCommand == _(SOURCE)) { } 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)) { d->logReceiveMessage(QString(_("Using V8 Version: %1")).arg( resp.value(_(BODY)).toMap(). diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.h b/src/plugins/debugger/qml/qmlv8debuggerclient.h index 1f8cec0271d34a46003e65ebfa3ebd224b066b8d..944f1cda986ad902f8f92f80e52d46a56e44d3a3 100644 --- a/src/plugins/debugger/qml/qmlv8debuggerclient.h +++ b/src/plugins/debugger/qml/qmlv8debuggerclient.h @@ -96,6 +96,8 @@ public: void setEngine(QmlEngine *engine); + void getSourceFiles(); + protected: void messageReceived(const QByteArray &data);