diff options
Diffstat (limited to 'src/plugins/debugger/qml')
-rw-r--r-- | src/plugins/debugger/qml/interactiveinterpreter.cpp | 90 | ||||
-rw-r--r-- | src/plugins/debugger/qml/interactiveinterpreter.h (renamed from src/plugins/debugger/qml/scriptconsole.h) | 67 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qml.pri | 14 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmladapter.cpp | 33 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmladapter.h | 8 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmldebuggerclient.h | 4 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmlengine.cpp | 113 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmlengine.h | 6 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmljsscriptconsole.cpp | 556 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmljsscriptconsole.h | 130 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmlv8debuggerclient.cpp | 1988 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmlv8debuggerclient.h | 50 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qmlv8debuggerclientconstants.h | 121 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qscriptdebuggerclient.cpp | 6 | ||||
-rw-r--r-- | src/plugins/debugger/qml/qscriptdebuggerclient.h | 7 | ||||
-rw-r--r-- | src/plugins/debugger/qml/scriptconsole.cpp | 241 |
16 files changed, 2599 insertions, 835 deletions
diff --git a/src/plugins/debugger/qml/interactiveinterpreter.cpp b/src/plugins/debugger/qml/interactiveinterpreter.cpp new file mode 100644 index 0000000000..2f43649ad0 --- /dev/null +++ b/src/plugins/debugger/qml/interactiveinterpreter.cpp @@ -0,0 +1,90 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "interactiveinterpreter.h" + +namespace Debugger { +namespace Internal { + +bool InteractiveInterpreter::canEvaluate() +{ + int yyaction = 0; + int yytoken = -1; + int yytos = -1; + + setCode(m_code, 1); + m_tokens.append(T_FEED_JS_PROGRAM); + + do { + if (++yytos == m_stateStack.size()) + m_stateStack.resize(m_stateStack.size() * 2); + + m_stateStack[yytos] = yyaction; + +again: + if (yytoken == -1 && action_index[yyaction] != -TERMINAL_COUNT) { + if (m_tokens.isEmpty()) + yytoken = lex(); + else + yytoken = m_tokens.takeFirst(); + } + + yyaction = t_action(yyaction, yytoken); + if (yyaction > 0) { + if (yyaction == ACCEPT_STATE) { + --yytos; + return true; + } + yytoken = -1; + } else if (yyaction < 0) { + const int ruleno = -yyaction - 1; + yytos -= rhs[ruleno]; + yyaction = nt_action(m_stateStack[yytos], lhs[ruleno] - TERMINAL_COUNT); + } + } while (yyaction); + + const int errorState = m_stateStack[yytos]; + if (t_action(errorState, T_AUTOMATIC_SEMICOLON) && canInsertAutomaticSemicolon(yytoken)) { + yyaction = errorState; + m_tokens.prepend(yytoken); + yytoken = T_SEMICOLON; + goto again; + } + + if (yytoken != EOF_SYMBOL) + return true; + + return false; +} + +} +} diff --git a/src/plugins/debugger/qml/scriptconsole.h b/src/plugins/debugger/qml/interactiveinterpreter.h index 058b62e8bf..57962ab62a 100644 --- a/src/plugins/debugger/qml/scriptconsole.h +++ b/src/plugins/debugger/qml/interactiveinterpreter.h @@ -4,7 +4,7 @@ ** ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). ** -** Contact: Nokia Corporation (qt-info@nokia.com) +** Contact: Nokia Corporation (info@qt.nokia.com) ** ** ** GNU Lesser General Public License Usage @@ -26,61 +26,46 @@ ** conditions contained in a signed written agreement between you and Nokia. ** ** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. +** Nokia at info@qt.nokia.com. ** **************************************************************************/ -#ifndef QMLJSSCRIPTCONSOLE_H -#define QMLJSSCRIPTCONSOLE_H +#ifndef INTERACTIVEINTERPRETER_H +#define INTERACTIVEINTERPRETER_H -#include <QtGui/QWidget> -#include <QtGui/QToolButton> -#include <QtGui/QPlainTextEdit> +#include <qmljs/parser/qmljslexer_p.h> +#include <qmljs/parser/qmljsengine_p.h> -#include <utils/fancylineedit.h> - -namespace QmlJSEditor { -class Highlighter; -} +#include <QtCore/QVector> +#include <QtCore/QString> +#include <QtCore/QList> namespace Debugger { namespace Internal { -class ScriptConsole : public QWidget +class InteractiveInterpreter: QmlJS::Lexer { - Q_OBJECT public: - ScriptConsole(QWidget *parent = 0); - -public slots: - void appendResult(const QString &result); -signals: - void expressionEntered(const QString &expr); - -protected slots: - void clearTextEditor(); - void executeExpression(); + InteractiveInterpreter() + : Lexer(&m_engine), + m_stateStack(128) + { -protected: - bool eventFilter(QObject *obj, QEvent *event); - void setFontSettings(); - void clear(); + } -// QToolButton *m_clearButton; - QPlainTextEdit *m_textEdit; - Utils::FancyLineEdit *m_lineEdit; - QString m_prompt; - QString m_expr; - QString m_lastExpr; + void clearText() { m_code.clear(); } + void appendText(const QString &text) { m_code += text; } - QString m_title; - QmlJSEditor::Highlighter *m_highlighter; + QString code() const { return m_code; } + bool canEvaluate(); +private: + QmlJS::Engine m_engine; + QVector<int> m_stateStack; + QList<int> m_tokens; + QString m_code; }; - } -} //end namespaces - - -#endif +} +#endif // INTERACTIVEINTERPRETER_H diff --git a/src/plugins/debugger/qml/qml.pri b/src/plugins/debugger/qml/qml.pri index 396f247a9b..a34c120add 100644 --- a/src/plugins/debugger/qml/qml.pri +++ b/src/plugins/debugger/qml/qml.pri @@ -1,6 +1,4 @@ include($$PWD/../../../libs/qmljsdebugclient/qmljsdebugclient.pri) -include($$PWD/../../../shared/json/json.pri) -DEFINES += JSON_INCLUDE_PRI HEADERS += \ $$PWD/qmlengine.h \ @@ -8,15 +6,19 @@ HEADERS += \ $$PWD/qmldebuggerclient.h \ $$PWD/qmljsprivateapi.h \ $$PWD/qmlcppengine.h \ - $$PWD/scriptconsole.h \ + $$PWD/qmljsscriptconsole.h \ $$PWD/qscriptdebuggerclient.h \ - $$PWD/qmlv8debuggerclient.h + $$PWD/qmlv8debuggerclient.h \ + $$PWD/interactiveinterpreter.h \ + $$PWD/qmlv8debuggerclientconstants.h SOURCES += \ $$PWD/qmlengine.cpp \ $$PWD/qmladapter.cpp \ $$PWD/qmldebuggerclient.cpp \ $$PWD/qmlcppengine.cpp \ - $$PWD/scriptconsole.cpp \ + $$PWD/qmljsscriptconsole.cpp \ $$PWD/qscriptdebuggerclient.cpp \ - $$PWD/qmlv8debuggerclient.cpp + $$PWD/qmlv8debuggerclient.cpp \ + $$PWD/interactiveinterpreter.cpp + diff --git a/src/plugins/debugger/qml/qmladapter.cpp b/src/plugins/debugger/qml/qmladapter.cpp index 99d5d69583..81c8eeca47 100644 --- a/src/plugins/debugger/qml/qmladapter.cpp +++ b/src/plugins/debugger/qml/qmladapter.cpp @@ -54,7 +54,9 @@ public: explicit QmlAdapterPrivate(DebuggerEngine *engine) : m_engine(engine) , m_qmlClient(0) + , m_engineDebugClient(0) , m_conn(0) + , m_currentSelectedDebugId(-1) { m_connectionTimer.setInterval(4000); m_connectionTimer.setSingleShot(true); @@ -62,9 +64,12 @@ public: QWeakPointer<DebuggerEngine> m_engine; QmlDebuggerClient *m_qmlClient; + QmlJsDebugClient::QDeclarativeEngineDebug *m_engineDebugClient; QTimer m_connectionTimer; QDeclarativeDebugConnection *m_conn; QHash<QString, QmlDebuggerClient*> debugClients; + int m_currentSelectedDebugId; + QString m_currentSelectedDebugName; }; } // namespace Internal @@ -272,6 +277,34 @@ QHash<QString, Internal::QmlDebuggerClient*> QmlAdapter::debuggerClients() { return d->debugClients; } + +QmlJsDebugClient::QDeclarativeEngineDebug *QmlAdapter::engineDebugClient() const +{ + return d->m_engineDebugClient; +} + +void QmlAdapter::setEngineDebugClient(QmlJsDebugClient::QDeclarativeEngineDebug *client) +{ + d->m_engineDebugClient = client; +} + +int QmlAdapter::currentSelectedDebugId() const +{ + return d->m_currentSelectedDebugId; +} + +QString QmlAdapter::currentSelectedDisplayName() const +{ + return d->m_currentSelectedDebugName; +} + +void QmlAdapter::setCurrentSelectedDebugInfo(int currentDebugId, const QString &displayName) +{ + d->m_currentSelectedDebugId = currentDebugId; + d->m_currentSelectedDebugName = displayName; + emit selectionChanged(); +} + void QmlAdapter::logServiceStatusChange(const QString &service, QDeclarativeDebugClient::Status newStatus) { diff --git a/src/plugins/debugger/qml/qmladapter.h b/src/plugins/debugger/qml/qmladapter.h index c19994796a..10750f009c 100644 --- a/src/plugins/debugger/qml/qmladapter.h +++ b/src/plugins/debugger/qml/qmladapter.h @@ -73,6 +73,13 @@ public: Internal::QmlDebuggerClient *activeDebuggerClient(); QHash<QString, Internal::QmlDebuggerClient*> debuggerClients(); + QmlJsDebugClient::QDeclarativeEngineDebug *engineDebugClient() const; + void setEngineDebugClient(QmlJsDebugClient::QDeclarativeEngineDebug *client); + + int currentSelectedDebugId() const; + QString currentSelectedDisplayName() const; + void setCurrentSelectedDebugInfo(int debugId, const QString &displayName = QString()); + public slots: void logServiceStatusChange(const QString &service, QDeclarativeDebugClient::Status newStatus); void logServiceActivity(const QString &service, const QString &logMessage); @@ -83,6 +90,7 @@ signals: void connectionStartupFailed(); void connectionError(QAbstractSocket::SocketError socketError); void serviceConnectionError(const QString serviceName); + void selectionChanged(); private slots: void connectionErrorOccurred(QAbstractSocket::SocketError socketError); diff --git a/src/plugins/debugger/qml/qmldebuggerclient.h b/src/plugins/debugger/qml/qmldebuggerclient.h index 521df7ed79..e190ce3a78 100644 --- a/src/plugins/debugger/qml/qmldebuggerclient.h +++ b/src/plugins/debugger/qml/qmldebuggerclient.h @@ -69,12 +69,12 @@ public: virtual void insertBreakpoint(const BreakpointModelId &id) = 0; virtual void removeBreakpoint(const BreakpointModelId &id) = 0; virtual void changeBreakpoint(const BreakpointModelId &id) = 0; - virtual void updateBreakpoints() = 0; + virtual void synchronizeBreakpoints() = 0; virtual void assignValueInDebugger(const QByteArray expr, const quint64 &id, const QString &property, const QString &value) = 0; - virtual void updateWatchData(const WatchData *data) = 0; + virtual void updateWatchData(const WatchData &data) = 0; virtual void executeDebuggerCommand(const QString &command) = 0; virtual void synchronizeWatchers(const QStringList &watchers) = 0; diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp index cafd726c77..826e4c81c5 100644 --- a/src/plugins/debugger/qml/qmlengine.cpp +++ b/src/plugins/debugger/qml/qmlengine.cpp @@ -52,6 +52,7 @@ #include <extensionsystem/pluginmanager.h> #include <projectexplorer/applicationlauncher.h> +#include <qmljsdebugclient/qdeclarativeoutputparser.h> #include <utils/environment.h> #include <utils/qtcassert.h> @@ -100,6 +101,7 @@ private: ApplicationLauncher m_applicationLauncher; Utils::FileInProjectFinder fileFinder; QTimer m_noDebugOutputTimer; + QmlJsDebugClient::QDeclarativeOutputParser m_outputParser; }; QmlEnginePrivate::QmlEnginePrivate(QmlEngine *q) @@ -144,6 +146,14 @@ QmlEngine::QmlEngine(const DebuggerStartParameters &startParameters, &d->m_noDebugOutputTimer, SLOT(start())); + d->m_outputParser.setNoOutputText(ApplicationLauncher::msgWinCannotRetrieveDebuggingOutput()); + connect(&d->m_outputParser, SIGNAL(waitingForConnectionMessage()), + this, SLOT(beginConnection())); + connect(&d->m_outputParser, SIGNAL(noOutputMessage()), + this, SLOT(beginConnection())); + connect(&d->m_outputParser, SIGNAL(errorMessage(QString)), + this, SLOT(wrongSetupMessageBox(QString))); + // Only wait 8 seconds for the 'Waiting for connection' on application ouput, then just try to connect // (application output might be redirected / blocked) d->m_noDebugOutputTimer.setSingleShot(true); @@ -258,6 +268,28 @@ void QmlEngine::retryMessageBoxFinished(int result) } } +void QmlEngine::wrongSetupMessageBox(const QString &errorMessage) +{ + d->m_noDebugOutputTimer.stop(); + notifyEngineRunFailed(); + + Core::ICore * const core = Core::ICore::instance(); + QMessageBox *infoBox = new QMessageBox(core->mainWindow()); + infoBox->setIcon(QMessageBox::Critical); + infoBox->setWindowTitle(tr("Qt Creator")); + //: %1 is detailed error message + infoBox->setText(tr("Could not connect to the in-process QML debugger:\n%1") + .arg(errorMessage)); + infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help); + infoBox->setDefaultButton(QMessageBox::Ok); + infoBox->setModal(true); + + connect(infoBox, SIGNAL(finished(int)), + this, SLOT(wrongSetupMessageBoxFinished(int))); + + infoBox->show(); +} + void QmlEngine::connectionError(QAbstractSocket::SocketError socketError) { if (socketError == QAbstractSocket::RemoteHostClosedError) @@ -280,63 +312,9 @@ bool QmlEngine::canDisplayTooltip() const return state() == InferiorRunOk || state() == InferiorStopOk; } -void QmlEngine::filterApplicationMessage(const QString &msg, int /*channel*/) +void QmlEngine::filterApplicationMessage(const QString &output, int /*channel*/) { - static const QString qddserver = QLatin1String("QDeclarativeDebugServer: "); - static const QString cannotRetrieveDebuggingOutput = ApplicationLauncher::msgWinCannotRetrieveDebuggingOutput(); - - const int index = msg.indexOf(qddserver); - if (index != -1) { - // we're actually getting debug output - d->m_noDebugOutputTimer.stop(); - - QString status = msg; - status.remove(0, index + qddserver.length()); // chop of 'QDeclarativeDebugServer: ' - - static QString waitingForConnection = QLatin1String("Waiting for connection "); - static QString unableToListen = QLatin1String("Unable to listen "); - static QString debuggingNotEnabled = QLatin1String("Ignoring \"-qmljsdebugger="); - static QString debuggingNotEnabled2 = QLatin1String("Ignoring\"-qmljsdebugger="); // There is (was?) a bug in one of the error strings - safest to handle both - static QString connectionEstablished = QLatin1String("Connection established"); - - QString errorMessage; - if (status.startsWith(waitingForConnection)) { - beginConnection(); - } else if (status.startsWith(unableToListen)) { - //: Error message shown after 'Could not connect ... debugger:" - errorMessage = tr("The port seems to be in use."); - } else if (status.startsWith(debuggingNotEnabled) || status.startsWith(debuggingNotEnabled2)) { - //: Error message shown after 'Could not connect ... debugger:" - errorMessage = tr("The application is not set up for QML/JS debugging."); - } else if (status.startsWith(connectionEstablished)) { - // nothing to do - } else { - qWarning() << "Unknown QDeclarativeDebugServer status message: " << status; - } - - if (!errorMessage.isEmpty()) { - notifyEngineRunFailed(); - - Core::ICore * const core = Core::ICore::instance(); - QMessageBox *infoBox = new QMessageBox(core->mainWindow()); - infoBox->setIcon(QMessageBox::Critical); - infoBox->setWindowTitle(tr("Qt Creator")); - //: %1 is detailed error message - infoBox->setText(tr("Could not connect to the in-process QML debugger:\n%1") - .arg(errorMessage)); - infoBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Help); - infoBox->setDefaultButton(QMessageBox::Ok); - infoBox->setModal(true); - - connect(infoBox, SIGNAL(finished(int)), - this, SLOT(wrongSetupMessageBoxFinished(int))); - - infoBox->show(); - } - } else if (msg.contains(cannotRetrieveDebuggingOutput)) { - // we won't get debugging output, so just try to connect ... - beginConnection(); - } + d->m_outputParser.processOutput(output); } void QmlEngine::showMessage(const QString &msg, int channel, int timeout) const @@ -638,10 +616,10 @@ void QmlEngine::attemptBreakpointSynchronization() DebuggerEngine::attemptBreakpointSynchronization(); if (d->m_adapter.activeDebuggerClient()) { - d->m_adapter.activeDebuggerClient()->updateBreakpoints(); + d->m_adapter.activeDebuggerClient()->synchronizeBreakpoints(); } else { foreach (QmlDebuggerClient *client, d->m_adapter.debuggerClients()) { - client->updateBreakpoints(); + client->synchronizeBreakpoints(); } } } @@ -689,7 +667,7 @@ void QmlEngine::requestModuleSymbols(const QString &moduleName) bool QmlEngine::setToolTipExpression(const QPoint &mousePos, TextEditor::ITextEditor *editor, const DebuggerToolTipContext &ctx) { - // This is processed by QML inspector, which has dependencies to + // This is processed by QML inspector, which has dependencies to // the qml js editor. Makes life easier. emit tooltipRequested(mousePos, editor, ctx.position); return true; @@ -724,7 +702,7 @@ void QmlEngine::updateWatchData(const WatchData &data, if (data.isValueNeeded()) { logMessage(LogSend, QString("%1 %2 %3").arg(QString("EXEC"), QString(data.iname), QString(data.name))); - d->m_adapter.activeDebuggerClient()->updateWatchData(&data); + d->m_adapter.activeDebuggerClient()->updateWatchData(data); } if (data.isChildrenNeeded() && watchHandler()->isExpandedIName(data.iname)) { @@ -768,12 +746,10 @@ unsigned QmlEngine::debuggerCapabilities() const QString QmlEngine::toFileInProject(const QUrl &fileUrl) { - if (startParameters().startMode != AttachToQmlPort) { - if (d->fileFinder.projectDirectory().isEmpty()) { - d->fileFinder.setProjectDirectory(startParameters().projectSourceDirectory); - d->fileFinder.setProjectFiles(startParameters().projectSourceFiles); - } - } + // make sure file finder is properly initialized + d->fileFinder.setProjectDirectory(startParameters().projectSourceDirectory); + d->fileFinder.setProjectFiles(startParameters().projectSourceFiles); + d->fileFinder.setSysroot(startParameters().sysroot); return d->fileFinder.findFile(fileUrl); } @@ -826,6 +802,11 @@ void QmlEngine::logMessage(LogDirection direction, const QString &message) showMessage(msg, LogDebug); } +QmlAdapter *QmlEngine::adapter() const +{ + return &d->m_adapter; +} + QmlEngine *createQmlEngine(const DebuggerStartParameters &sp, DebuggerEngine *masterEngine) { diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h index 2b77d8575b..226792e085 100644 --- a/src/plugins/debugger/qml/qmlengine.h +++ b/src/plugins/debugger/qml/qmlengine.h @@ -40,6 +40,9 @@ #include <QtNetwork/QAbstractSocket> namespace Debugger { + +class QmlAdapter; + namespace Internal { class QmlEnginePrivate; @@ -71,11 +74,14 @@ public: void logMessage(LogDirection direction, const QString &str); + QmlAdapter *adapter() const; + public slots: void disconnected(); private slots: void retryMessageBoxFinished(int result); + void wrongSetupMessageBox(const QString &errorMessage); void wrongSetupMessageBoxFinished(int result); private: diff --git a/src/plugins/debugger/qml/qmljsscriptconsole.cpp b/src/plugins/debugger/qml/qmljsscriptconsole.cpp new file mode 100644 index 0000000000..5e28b5eb7e --- /dev/null +++ b/src/plugins/debugger/qml/qmljsscriptconsole.cpp @@ -0,0 +1,556 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmljsscriptconsole.h" +#include "interactiveinterpreter.h" +#include "qmladapter.h" +#include "debuggerstringutils.h" + +#include <texteditor/fontsettings.h> +#include <texteditor/texteditorsettings.h> + +#include <extensionsystem/pluginmanager.h> +#include <coreplugin/coreconstants.h> +#include <utils/statuslabel.h> + +#include <QtGui/QMenu> +#include <QtGui/QTextBlock> +#include <QtGui/QHBoxLayout> +#include <QtGui/QVBoxLayout> +#include <QtGui/QToolButton> + +namespace Debugger { +namespace Internal { + +class QmlJSScriptConsolePrivate +{ +public: + QmlJSScriptConsolePrivate() + : prompt(_("> ")), + startOfEditableArea(-1), + lastKnownPosition(0), + inferiorStopped(false) + { + resetCache(); + } + + void resetCache(); + void appendToHistory(const QString &script); + bool canEvaluateScript(const QString &script); + + QWeakPointer<QmlAdapter> adapter; + + QString prompt; + int startOfEditableArea; + int lastKnownPosition; + + QStringList scriptHistory; + int scriptHistoryIndex; + + InteractiveInterpreter interpreter; + + bool inferiorStopped; + QList<QTextEdit::ExtraSelection> selections; +}; + +void QmlJSScriptConsolePrivate::resetCache() +{ + scriptHistory.clear(); + scriptHistory.append(QString()); + scriptHistoryIndex = scriptHistory.count(); + + selections.clear(); +} + +void QmlJSScriptConsolePrivate::appendToHistory(const QString &script) +{ + scriptHistoryIndex = scriptHistory.count(); + scriptHistory.replace(scriptHistoryIndex - 1,script); + scriptHistory.append(QString()); + scriptHistoryIndex = scriptHistory.count(); +} + +bool QmlJSScriptConsolePrivate::canEvaluateScript(const QString &script) +{ + interpreter.clearText(); + interpreter.appendText(script); + return interpreter.canEvaluate(); +} + +/////////////////////////////////////////////////////////////////////// +// +// QmlJSScriptConsoleWidget +// +/////////////////////////////////////////////////////////////////////// + +QmlJSScriptConsoleWidget::QmlJSScriptConsoleWidget(QWidget *parent) + : QWidget(parent) +{ + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->setMargin(0); + vbox->setSpacing(0); + + QWidget *statusbarContainer = new QWidget; + + QHBoxLayout *hbox = new QHBoxLayout(statusbarContainer); + hbox->setMargin(0); + hbox->setSpacing(0); + + //Clear Button + QToolButton *clearButton = new QToolButton; + QAction *clearAction = new QAction(tr("Clear Console"), this); + clearAction->setIcon(QIcon(_(Core::Constants::ICON_CLEAN_PANE))); + + clearButton->setDefaultAction(clearAction); + + //Status Label + m_statusLabel = new Utils::StatusLabel; + + hbox->addWidget(m_statusLabel, 20, Qt::AlignLeft); + hbox->addWidget(clearButton, 0, Qt::AlignRight); + + m_console = new QmlJSScriptConsole; + connect(m_console, SIGNAL(evaluateExpression(QString)), this, + SIGNAL(evaluateExpression(QString))); + connect(m_console, SIGNAL(updateStatusMessage(const QString &, int)), m_statusLabel, + SLOT(showStatusMessage(const QString &, int))); + connect(clearAction, SIGNAL(triggered()), m_console, SLOT(clear())); + vbox->addWidget(statusbarContainer); + vbox->addWidget(m_console); + +} + +void QmlJSScriptConsoleWidget::setQmlAdapter(QmlAdapter *adapter) +{ + m_console->setQmlAdapter(adapter); +} + +void QmlJSScriptConsoleWidget::setInferiorStopped(bool inferiorStopped) +{ + m_console->setInferiorStopped(inferiorStopped); +} + +void QmlJSScriptConsoleWidget::appendResult(const QString &result) +{ + m_console->appendResult(result); +} + +/////////////////////////////////////////////////////////////////////// +// +// QmlJSScriptConsole +// +/////////////////////////////////////////////////////////////////////// + +QmlJSScriptConsole::QmlJSScriptConsole(QWidget *parent) + : QPlainTextEdit(parent), + d(new QmlJSScriptConsolePrivate()) +{ + connect(this, SIGNAL(cursorPositionChanged()), SLOT(onCursorPositionChanged())); + + setFrameStyle(QFrame::NoFrame); + setUndoRedoEnabled(false); + setBackgroundVisible(false); + const TextEditor::FontSettings &fs = TextEditor::TextEditorSettings::instance()->fontSettings(); + setFont(fs.font()); + + displayPrompt(); +} + +QmlJSScriptConsole::~QmlJSScriptConsole() +{ + delete d; +} + +void QmlJSScriptConsole::setPrompt(const QString &prompt) +{ + d->prompt = prompt; +} + +QString QmlJSScriptConsole::prompt() const +{ + return d->prompt; +} + +void QmlJSScriptConsole::setInferiorStopped(bool inferiorStopped) +{ + d->inferiorStopped = inferiorStopped; + onSelectionChanged(); +} + +void QmlJSScriptConsole::setQmlAdapter(QmlAdapter *adapter) +{ + d->adapter = adapter; + clear(); +} + +void QmlJSScriptConsole::appendResult(const QString &result) +{ + QString currentScript = getCurrentScript(); + d->appendToHistory(currentScript); + + QTextCursor cur = textCursor(); + cur.movePosition(QTextCursor::End); + cur.insertText(_("\n")); + cur.insertText(result); + cur.movePosition(QTextCursor::EndOfLine); + cur.insertText(_("\n")); + setTextCursor(cur); + displayPrompt(); + + QTextEdit::ExtraSelection sel; + + QTextCharFormat resultFormat; + resultFormat.setForeground(QBrush(QColor(Qt::darkGray))); + + QTextCursor c(document()->findBlockByNumber(cur.blockNumber()-1)); + c.movePosition(QTextCursor::StartOfBlock); + c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); + + sel.format = resultFormat; + sel.cursor = c; + + d->selections.append(sel); + + setExtraSelections(d->selections); +} + +void QmlJSScriptConsole::clear() +{ + d->resetCache(); + + QPlainTextEdit::clear(); + displayPrompt(); +} + +void QmlJSScriptConsole::onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State state) +{ + QDeclarativeDebugExpressionQuery *query = qobject_cast<QDeclarativeDebugExpressionQuery *>(sender()); + + if (query && state != QDeclarativeDebugQuery::Error) { + QString result(query->result().toString()); + if (result == _("<undefined>") && d->inferiorStopped) { + //don't give up. check if we can still evaluate using javascript engine + emit evaluateExpression(getCurrentScript()); + } else { + appendResult(result); + } + } else { + QPlainTextEdit::appendPlainText(QString()); + moveCursor(QTextCursor::EndOfLine); + } + delete query; +} + +void QmlJSScriptConsole::onSelectionChanged() +{ + if (!d->adapter.isNull()) { + QString status; + if (!d->inferiorStopped) { + status.append(tr("Current Selected Object: ")); + status.append(d->adapter.data()->currentSelectedDisplayName()); + } + emit updateStatusMessage(status, 0); + } +} + +void QmlJSScriptConsole::keyPressEvent(QKeyEvent *e) +{ + bool keyConsumed = false; + switch (e->key()) { + + case Qt::Key_Return: + case Qt::Key_Enter: + if (isEditableArea()) { + handleReturnKey(); + keyConsumed = true; + } + break; + + case Qt::Key_Backspace: { + QTextCursor cursor = textCursor(); + bool hasSelection = cursor.hasSelection(); + int selectionStart = cursor.selectionStart(); + if ((hasSelection && selectionStart < d->startOfEditableArea) + || (!hasSelection && selectionStart == d->startOfEditableArea)) { + keyConsumed = true; + } + break; + } + + case Qt::Key_Delete: + if (textCursor().selectionStart() < d->startOfEditableArea) { + keyConsumed = true; + } + break; + + case Qt::Key_Tab: + case Qt::Key_Backtab: + keyConsumed = true; + break; + + case Qt::Key_Left: + if (textCursor().position() == d->startOfEditableArea) { + keyConsumed = true; + } else if (e->modifiers() & Qt::ControlModifier && isEditableArea()) { + handleHomeKey(); + keyConsumed = true; + } + break; + + case Qt::Key_Up: + if (isEditableArea()) { + handleUpKey(); + keyConsumed = true; + } + break; + + case Qt::Key_Down: + if (isEditableArea()) { + handleDownKey(); + keyConsumed = true; + } + break; + + case Qt::Key_Home: + if (isEditableArea()) { + handleHomeKey(); + keyConsumed = true; + } + break; + + case Qt::Key_C: + case Qt::Key_Insert: { + //Fair to assume that for any selection beyond startOfEditableArea + //only copy function is allowed. + QTextCursor cursor = textCursor(); + bool hasSelection = cursor.hasSelection(); + int selectionStart = cursor.selectionStart(); + if (hasSelection && selectionStart < d->startOfEditableArea) { + if (!(e->modifiers() & Qt::ControlModifier)) + keyConsumed = true; + } + break; + } + + default: { + QTextCursor cursor = textCursor(); + bool hasSelection = cursor.hasSelection(); + int selectionStart = cursor.selectionStart(); + if (hasSelection && selectionStart < d->startOfEditableArea) { + keyConsumed = true; + } + break; + } + } + + if (!keyConsumed) + QPlainTextEdit::keyPressEvent(e); +} + +void QmlJSScriptConsole::contextMenuEvent(QContextMenuEvent *event) +{ + QTextCursor cursor = textCursor(); + Qt::TextInteractionFlags flags = textInteractionFlags(); + bool hasSelection = cursor.hasSelection(); + int selectionStart = cursor.selectionStart(); + bool canBeEdited = true; + if (hasSelection && selectionStart < d->startOfEditableArea) { + canBeEdited = false; + } + + QMenu *menu = new QMenu(); + QAction *a; + + if ((flags & Qt::TextEditable) && canBeEdited) { + a = menu->addAction(tr("Cut"), this, SLOT(cut())); + a->setEnabled(cursor.hasSelection()); + } + + a = menu->addAction(tr("Copy"), this, SLOT(copy())); + a->setEnabled(cursor.hasSelection()); + + if ((flags & Qt::TextEditable) && canBeEdited) { + a = menu->addAction(tr("Paste"), this, SLOT(paste())); + a->setEnabled(canPaste()); + } + + menu->addSeparator(); + a = menu->addAction(tr("Select All"), this, SLOT(selectAll())); + a->setEnabled(!document()->isEmpty()); + + menu->addSeparator(); + menu->addAction(tr("Clear"), this, SLOT(clear())); + + menu->exec(event->globalPos()); + + delete menu; +} + +void QmlJSScriptConsole::mouseReleaseEvent(QMouseEvent *e) +{ + QPlainTextEdit::mouseReleaseEvent(e); + QTextCursor cursor = textCursor(); + if (e->button() == Qt::LeftButton && !cursor.hasSelection() && !isEditableArea()) { + cursor.setPosition(d->lastKnownPosition); + setTextCursor(cursor); + } +} + +void QmlJSScriptConsole::onCursorPositionChanged() +{ + if (!isEditableArea()) { + setTextInteractionFlags(Qt::TextSelectableByMouse); + } else { + d->lastKnownPosition = textCursor().position(); + setTextInteractionFlags(Qt::TextEditorInteraction); + } +} + +void QmlJSScriptConsole::displayPrompt() +{ + d->startOfEditableArea = textCursor().position() + d->prompt.length(); + QTextCursor cur = textCursor(); + cur.insertText(d->prompt); + cur.movePosition(QTextCursor::EndOfWord); + setTextCursor(cur); +} + +void QmlJSScriptConsole::handleReturnKey() +{ + QString currentScript = getCurrentScript(); + bool scriptEvaluated = false; + + //Check if string is only white spaces + if (currentScript.trimmed().isEmpty()) { + QTextCursor cur = textCursor(); + cur.movePosition(QTextCursor::EndOfLine); + cur.insertText(_("\n")); + setTextCursor(cur); + displayPrompt(); + scriptEvaluated = true; + } + + if (!scriptEvaluated) { + //check if it can be evaluated + if (d->canEvaluateScript(currentScript)) { + + //Select the engine for evaluation based on + //inferior state + if (!d->inferiorStopped) { + if (!d->adapter.isNull()) { + QDeclarativeEngineDebug *engineDebug = d->adapter.data()->engineDebugClient(); + int id = d->adapter.data()->currentSelectedDebugId(); + if (engineDebug && id != -1) { + QDeclarativeDebugExpressionQuery *query = + engineDebug->queryExpressionResult(id, currentScript, this); + connect(query, SIGNAL(stateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)), + this, SLOT(onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State))); + scriptEvaluated = true; + } + } + } + + if (!scriptEvaluated) { + emit evaluateExpression(currentScript); + scriptEvaluated = true; + } + } + } + if (!scriptEvaluated) { + QPlainTextEdit::appendPlainText(QString()); + moveCursor(QTextCursor::EndOfLine); + } + +} + +void QmlJSScriptConsole::handleUpKey() +{ + //get the current script and update in script history + QString currentScript = getCurrentScript(); + d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript); + + if (d->scriptHistoryIndex > 1) + d->scriptHistoryIndex--; + + replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1)); +} + +void QmlJSScriptConsole::handleDownKey() +{ + //get the current script and update in script history + QString currentScript = getCurrentScript(); + d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript); + + if (d->scriptHistoryIndex < d->scriptHistory.count()) + d->scriptHistoryIndex++; + + replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1)); +} + +void QmlJSScriptConsole::handleHomeKey() +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(d->startOfEditableArea); + setTextCursor(cursor); +} + +QString QmlJSScriptConsole::getCurrentScript() const +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(d->startOfEditableArea); + while (cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor)) ; + QString script = cursor.selectedText(); + cursor.clearSelection(); + //remove trailing white space + int end = script.size() - 1; + while (end > 0 && script[end].isSpace()) + end--; + return script.left(end + 1); +} + +void QmlJSScriptConsole::replaceCurrentScript(const QString &script) +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(d->startOfEditableArea); + while (cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor)) ; + cursor.deleteChar(); + cursor.insertText(script); + setTextCursor(cursor); +} + +bool QmlJSScriptConsole::isEditableArea() const +{ + return textCursor().position() >= d->startOfEditableArea; +} + +} //Internal +} //Debugger diff --git a/src/plugins/debugger/qml/qmljsscriptconsole.h b/src/plugins/debugger/qml/qmljsscriptconsole.h new file mode 100644 index 0000000000..5240112309 --- /dev/null +++ b/src/plugins/debugger/qml/qmljsscriptconsole.h @@ -0,0 +1,130 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLJSSCRIPTCONSOLE_H +#define QMLJSSCRIPTCONSOLE_H + +#include <qmljsdebugclient/qdeclarativeenginedebug.h> +#include <QtGui/QPlainTextEdit> + +namespace Utils { +class StatusLabel; +} + +namespace Debugger { + +class QmlAdapter; + +namespace Internal { + +class QmlJSScriptConsolePrivate; +class QmlJSScriptConsole; + +class QmlJSScriptConsoleWidget : public QWidget +{ + Q_OBJECT +public: + QmlJSScriptConsoleWidget(QWidget *parent = 0); + + void setQmlAdapter(QmlAdapter *adapter); + void setInferiorStopped(bool inferiorStopped); + +public slots: + void appendResult(const QString &result); + +signals: + void evaluateExpression(const QString &expr); + +private: + QmlJSScriptConsole *m_console; + Utils::StatusLabel *m_statusLabel; + +}; + +class QmlJSScriptConsole : public QPlainTextEdit +{ + Q_OBJECT + +public: + explicit QmlJSScriptConsole(QWidget *parent = 0); + ~QmlJSScriptConsole(); + + inline void setTitle(const QString &title) + { setDocumentTitle(title); } + + inline QString title() const + { return documentTitle(); } + + void setPrompt(const QString &prompt); + QString prompt() const; + + void setInferiorStopped(bool inferiorStopped); + + void setQmlAdapter(QmlAdapter *adapter); + + void appendResult(const QString &result); + +public slots: + void clear(); + void onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State); + void onSelectionChanged(); + +protected: + void keyPressEvent(QKeyEvent *e); + void contextMenuEvent(QContextMenuEvent *event); + void mouseReleaseEvent(QMouseEvent *e); + +signals: + void evaluateExpression(const QString &expr); + void updateStatusMessage(const QString &message, int timeoutMS); + +private slots: + void onCursorPositionChanged(); + +private: + void displayPrompt(); + void handleReturnKey(); + void handleUpKey(); + void handleDownKey(); + void handleHomeKey(); + QString getCurrentScript() const; + void replaceCurrentScript(const QString &script); + bool isEditableArea() const; + +private: + QmlJSScriptConsolePrivate *d; +}; + +} //Internal +} //Debugger + +#endif diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp index 0be6a496fe..9e0caac222 100644 --- a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp +++ b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp @@ -31,281 +31,898 @@ **************************************************************************/ #include "qmlv8debuggerclient.h" +#include "qmlv8debuggerclientconstants.h" +#include "debuggerstringutils.h" -#include "watchdata.h" #include "watchhandler.h" #include "breakpoint.h" #include "breakhandler.h" -#include "debuggerconstants.h" #include "qmlengine.h" #include "stackhandler.h" -#include "debuggercore.h" -#include <extensionsystem/pluginmanager.h> #include <utils/qtcassert.h> - #include <coreplugin/editormanager/editormanager.h> #include <texteditor/basetexteditor.h> #include <QtGui/QTextBlock> #include <QtCore/QVariant> +#include <QtCore/QStack> +#include <QtCore/QQueue> #include <QtCore/QFileInfo> #include <QtGui/QTextDocument> -#include <QtGui/QMessageBox> +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptValue> -#define INITIALPARAMS "seq" << ':' << ++d->sequence << ',' << "type" << ':' << "request" +#define DEBUG_QML 0 +#if DEBUG_QML +# define SDEBUG(s) qDebug() << s << '\n' +#else +# define SDEBUG(s) +#endif using namespace Core; -using namespace Json; namespace Debugger { namespace Internal { -struct ExceptionInfo -{ - int sourceLine; - QString filePath; - QString errorMessage; +typedef QPair<QByteArray, QByteArray> WatchDataPair; + +struct QmlV8ObjectData { + QByteArray type; + QVariant value; + QVariant properties; }; class QmlV8DebuggerClientPrivate { public: - explicit QmlV8DebuggerClientPrivate(QmlV8DebuggerClient *) : - handleException(false), - sequence(0), - ping(0), - engine(0) + explicit QmlV8DebuggerClientPrivate(QmlV8DebuggerClient *q) : + q(q), + sequence(-1), + engine(0), + debugServiceState(QmlV8DebuggerClient::RunningState), + requestListBreakpoints(false), + requestBacktrace(true) { + parser = m_scriptEngine.evaluate(_("JSON.parse")); + stringifier = m_scriptEngine.evaluate(_("JSON.stringify")); } - bool handleException; + void connect(); + void disconnect(); + + void interrupt(); + void continueDebugging(QmlV8DebuggerClient::StepAction stepAction, int stepCount = 1); + + void evaluate(const QString expr, bool global = false, bool disableBreak = false, + int frame = -1, bool addContext = false); + void lookup(const QList<int> handles, bool includeSource = false); + void backtrace(int fromFrame = -1, int toFrame = -1, bool bottom = false); + void frame(int number = -1); + void scope(int number = -1, int frameNumber = -1); + void scopes(int frameNumber = -1); + void scripts(int types = 4, const QList<int> ids = QList<int>(), + bool includeSource = false, const QVariant filter = QVariant()); + void source(int frame = -1, int fromLine = -1, int toLine = -1); + + void setBreakpoint(const QString type, const QString target, int line = -1, + int column = -1, bool enabled = true, + const QString condition = QString(), int ignoreCount = -1); + void changeBreakpoint(int breakpoint, bool enabled = true, + const QString condition = QString(), int ignoreCount = -1); + void clearBreakpoint(int breakpoint); + void setExceptionBreak(QmlV8DebuggerClient::Exceptions type, bool enabled = false); + void listBreakpoints(); + + void v8flags(const QString flags); + void version(); + //void profile(ProfileCommand command); //NOT SUPPORTED + void gc(); + + QmlV8ObjectData extractData(const QVariant &data); + void clearCache(); + +private: + QByteArray packMessage(const QByteArray &message); + QScriptValue initObject(); + +public: + QmlV8DebuggerClient *q; + int sequence; - int ping; QmlEngine *engine; - QHash<BreakpointModelId,int> breakpoints; - QHash<int,BreakpointModelId> breakpointsSync; - QHash<int,QByteArray> locals; - QHash<int,QByteArray> watches; - QByteArray frames; - QScopedPointer<ExceptionInfo> exceptionInfo; + QHash<BreakpointModelId, int> breakpoints; + QHash<int, BreakpointModelId> breakpointsSync; + + QScriptValue parser; + QScriptValue stringifier; + + //State Information + QmlV8DebuggerClient::V8DebugServiceStates debugServiceState; + int currentFrameIndex; + QStringList watchedExpressions; + + //Flags + bool requestListBreakpoints; + bool requestBacktrace; + + //Cache + QHash<int, QByteArray> localsAndWatchers; + QHash<int, QString> evaluatingWatches; + QStack<QString> watchesToEvaluate; + QStack<int> currentFrameScopes; + QList<WatchData> localDataList; + QVariant refsVal; + QHash<int, QString> evaluatingExpression; + QQueue<QByteArray> requestQueue; +private: + QScriptEngine m_scriptEngine; }; -QmlV8DebuggerClient::QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection* client) - : QmlDebuggerClient(client, QLatin1String("V8Debugger")), - d(new QmlV8DebuggerClientPrivate(this)) +/////////////////////////////////////////////////////////////////////// +// +// QmlV8DebuggerClientPrivate +// +/////////////////////////////////////////////////////////////////////// + +void QmlV8DebuggerClientPrivate::connect() { + // { "seq" : <number>, + // "type" : "request", + // "command" : "connect", + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(CONNECT))); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -QmlV8DebuggerClient::~QmlV8DebuggerClient() +void QmlV8DebuggerClientPrivate::disconnect() { - delete d; + // { "seq" : <number>, + // "type" : "request", + // "command" : "disconnect", + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(DISCONNECT))); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -QByteArray QmlV8DebuggerClient::packMessage(const QByteArray &message) +void QmlV8DebuggerClientPrivate::interrupt() { - QByteArray reply; - QDataStream rs(&reply, QIODevice::WriteOnly); - QByteArray cmd = "V8DEBUG"; - rs << cmd << message; - return reply; + // { "seq" : <number>, + // "type" : "request", + // "command" : "interrupt", + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(INTERRUPT))); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::breakOnException(Exceptions exceptionsType, bool enabled) +void QmlV8DebuggerClientPrivate::continueDebugging(QmlV8DebuggerClient::StepAction action, + int count) { - //TODO: Have to deal with NoExceptions - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "setexceptionbreak"; + //First reset + q->resetDebugger(); + + // { "seq" : <number>, + // "type" : "request", + // "command" : "continue", + // "arguments" : { "stepaction" : <"in", "next" or "out">, + // "stepcount" : <number of steps (default 1)> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(CONTINEDEBUGGING))); + + if (action != QmlV8DebuggerClient::Continue) { + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + switch (action) { + case QmlV8DebuggerClient::In: + args.setProperty(_(STEPACTION), QScriptValue(_(IN))); + break; + case QmlV8DebuggerClient::Out: + args.setProperty(_(STEPACTION), QScriptValue(_(OUT))); + break; + case QmlV8DebuggerClient::Next: + args.setProperty(_(STEPACTION), QScriptValue(_(NEXT))); + break; + default:break; + } + if (count != 1) + args.setProperty(_(STEPCOUNT), QScriptValue(count)); + jsonVal.setProperty(_(ARGUMENTS), args); - JsonInputStream(request) << ',' << "arguments" << ':'; - if (exceptionsType == AllExceptions) - JsonInputStream(request) << '{' << "type" << ':' << "all"; - else if (exceptionsType == UncaughtExceptions) - JsonInputStream(request) << '{' << "type" << ':' << "uncaught"; + } + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} - JsonInputStream(request) << ',' << "enabled" << ':' << enabled; - JsonInputStream(request) << '}'; +void QmlV8DebuggerClientPrivate::evaluate(const QString expr, bool global, + bool disableBreak, int frame, + bool addContext) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "evaluate", + // "arguments" : { "expression" : <expression to evaluate>, + // "frame" : <number>, + // "global" : <boolean>, + // "disable_break" : <boolean>, + // "additional_context" : [ + // { "name" : <name1>, "handle" : <handle1> }, + // { "name" : <name2>, "handle" : <handle2> }, + // ... + // ] + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(EVALUATE))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + args.setProperty(_(EXPRESSION), QScriptValue(expr)); + + if (frame != -1) + args.setProperty(_(FRAME), QScriptValue(frame)); + + if (global) + args.setProperty(_(GLOBAL), QScriptValue(global)); + + if (disableBreak) + args.setProperty(_(DISABLE_BREAK), QScriptValue(disableBreak)); + + if (addContext) { + QAbstractItemModel *localsModel = engine->localsModel(); + int rowCount = localsModel->rowCount(); + + QScriptValue ctxtList = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY )); + while (rowCount) { + QModelIndex index = localsModel->index(--rowCount, 0); + const WatchData *data = engine->watchHandler()->watchData(LocalsWatch, index); + QScriptValue ctxt = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + ctxt.setProperty(_(NAME), QScriptValue(data->name)); + ctxt.setProperty(_(HANDLE), QScriptValue(int(data->id))); + + ctxtList.setProperty(rowCount, ctxt); + } - JsonInputStream(request) << '}'; + args.setProperty(_(ADDITIONAL_CONTEXT), QScriptValue(ctxtList)); + } + jsonVal.setProperty(_(ARGUMENTS), args); - sendMessage(packMessage(request)); + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::storeExceptionInformation(const QByteArray &message) +void QmlV8DebuggerClientPrivate::lookup(QList<int> handles, bool includeSource) { - JsonValue response(message); + // { "seq" : <number>, + // "type" : "request", + // "command" : "lookup", + // "arguments" : { "handles" : <array of handles>, + // "includeSource" : <boolean indicating whether + // the source will be included when + // script objects are returned>, + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(LOOKUP))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); + int index = 0; + foreach (int handle, handles) { + array.setProperty(index++, QScriptValue(handle)); + } + args.setProperty(_(HANDLES), array); + + if (includeSource) + args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); - JsonValue body = response.findChild("body"); + jsonVal.setProperty(_(ARGUMENTS), args); - d->exceptionInfo.reset(new ExceptionInfo); - d->exceptionInfo->sourceLine = body.findChild("sourceLine").toVariant().toInt(); - QUrl fileUrl(body.findChild("script").findChild("name").toVariant().toString()); - d->exceptionInfo->filePath = d->engine->toFileInProject(fileUrl); - d->exceptionInfo->errorMessage = body.findChild("exception").findChild("text").toVariant().toString(); + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::handleException() +void QmlV8DebuggerClientPrivate::backtrace(int fromFrame, int toFrame, bool bottom) { - EditorManager *editorManager = EditorManager::instance(); - QList<IEditor *> openedEditors = editorManager->openedEditors(); + // { "seq" : <number>, + // "type" : "request", + // "command" : "backtrace", + // "arguments" : { "fromFrame" : <number> + // "toFrame" : <number> + // "bottom" : <boolean, set to true if the bottom of the + // stack is requested> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(BACKTRACE))); - // set up the format for the errors - QTextCharFormat errorFormat; - errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); - errorFormat.setUnderlineColor(Qt::red); + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); - foreach (IEditor *editor, openedEditors) { - if (editor->file()->fileName() == d->exceptionInfo->filePath) { - TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); - if (!ed) - continue; + if (fromFrame != -1) + args.setProperty(_(FROMFRAME), QScriptValue(fromFrame)); - QList<QTextEdit::ExtraSelection> selections; - QTextEdit::ExtraSelection sel; - sel.format = errorFormat; - QTextCursor c(ed->document()->findBlockByNumber(d->exceptionInfo->sourceLine)); - const QString text = c.block().text(); - for (int i = 0; i < text.size(); ++i) { - if (! text.at(i).isSpace()) { - c.setPosition(c.position() + i); - break; - } - } - c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); - sel.cursor = c; + if (toFrame != -1) + args.setProperty(_(TOFRAME), QScriptValue(toFrame)); - sel.format.setToolTip(d->exceptionInfo->errorMessage); + if (bottom) + args.setProperty(_(BOTTOM), QScriptValue(bottom)); - selections.append(sel); - ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections); + jsonVal.setProperty(_(ARGUMENTS), args); - d->engine->showMessage(d->exceptionInfo->errorMessage, ScriptConsoleOutput); - } + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} + +void QmlV8DebuggerClientPrivate::frame(int number) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "frame", + // "arguments" : { "number" : <frame number> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(FRAME))); + + if (number != -1) { + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + args.setProperty(_(NUMBER), QScriptValue(number)); + + jsonVal.setProperty(_(ARGUMENTS), args); } - //Delete the info even if the code hasnt been highlighted - d->exceptionInfo.reset(); + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::clearExceptionSelection() +void QmlV8DebuggerClientPrivate::scope(int number, int frameNumber) { - //Check if break was due to exception - if (d->handleException) { - EditorManager *editorManager = EditorManager::instance(); - QList<IEditor *> openedEditors = editorManager->openedEditors(); - QList<QTextEdit::ExtraSelection> selections; + // { "seq" : <number>, + // "type" : "request", + // "command" : "scope", + // "arguments" : { "number" : <scope number> + // "frameNumber" : <frame number, optional uses selected + // frame if missing> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPE))); + + if (number != -1) { + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + args.setProperty(_(NUMBER), QScriptValue(number)); + + if (frameNumber != -1) + args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); + + jsonVal.setProperty(_(ARGUMENTS), args); + } - foreach (IEditor *editor, openedEditors) { - TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); - if (!ed) - continue; + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} - ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections); +void QmlV8DebuggerClientPrivate::scopes(int frameNumber) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "scopes", + // "arguments" : { "frameNumber" : <frame number, optional uses selected + // frame if missing> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCOPES))); + + if (frameNumber != -1) { + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + args.setProperty(_(FRAMENUMBER), QScriptValue(frameNumber)); + + jsonVal.setProperty(_(ARGUMENTS), args); + } + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} + +void QmlV8DebuggerClientPrivate::scripts(int types, const QList<int> ids, bool includeSource, + const QVariant /*filter*/) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "scripts", + // "arguments" : { "types" : <types of scripts to retrieve + // set bit 0 for native scripts + // set bit 1 for extension scripts + // set bit 2 for normal scripts + // (default is 4 for normal scripts)> + // "ids" : <array of id's of scripts to return. If this is not specified all scripts are requrned> + // "includeSource" : <boolean indicating whether the source code should be included for the scripts returned> + // "filter" : <string or number: filter string or script id. + // If a number is specified, then only the script with the same number as its script id will be retrieved. + // If a string is specified, then only scripts whose names contain the filter string will be retrieved.> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(SCRIPTS))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + args.setProperty(_(TYPES), QScriptValue(types)); + + if (ids.count()) { + QScriptValue array = parser.call(QScriptValue(), QScriptValueList() << _(ARRAY)); + int index = 0; + foreach (int id, ids) { + array.setProperty(index++, QScriptValue(id)); } - d->handleException = false; + args.setProperty(_(IDS), array); } + + if (includeSource) + args.setProperty(_(INCLUDESOURCE), QScriptValue(includeSource)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::continueDebugging(StepAction type) +void QmlV8DebuggerClientPrivate::source(int frame, int fromLine, int toLine) { - clearExceptionSelection(); + // { "seq" : <number>, + // "type" : "request", + // "command" : "source", + // "arguments" : { "frame" : <frame number (default selected frame)> + // "fromLine" : <from line within the source default is line 0> + // "toLine" : <to line within the source this line is not included in + // the result default is the number of lines in the script> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(SOURCE))); - QByteArray request; + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "continue"; + if (frame != -1) + args.setProperty(_(FRAME), QScriptValue(frame)); - if (type != Continue) { - JsonInputStream(request) << ',' << "arguments" << ':'; + if (fromLine != -1) + args.setProperty(_(FROMLINE), QScriptValue(fromLine)); - switch (type) { - case In: JsonInputStream(request) << '{' << "stepaction" << ':' << "in"; - break; - case Out: JsonInputStream(request) << '{' << "stepaction" << ':' << "out"; - break; - case Next: JsonInputStream(request) << '{' << "stepaction" << ':' << "next"; - break; - default:break; - } + if (toLine != -1) + args.setProperty(_(TOLINE), QScriptValue(toLine)); - JsonInputStream(request) << '}'; - } + jsonVal.setProperty(_(ARGUMENTS), args); - JsonInputStream(request) << '}'; + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} - sendMessage(packMessage(request)); +void QmlV8DebuggerClientPrivate::setBreakpoint(const QString type, const QString target, + int line, int column, bool enabled, + const QString condition, int ignoreCount) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "setbreakpoint", + // "arguments" : { "type" : <"function" or "script" or "scriptId" or "scriptRegExp"> + // "target" : <function expression or script identification> + // "line" : <line in script or function> + // "column" : <character position within the line> + // "enabled" : <initial enabled state. True or false, default is true> + // "condition" : <string with break point condition> + // "ignoreCount" : <number specifying the number of break point hits to ignore, default value is 0> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(SETBREAKPOINT))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + args.setProperty(_(TYPE), QScriptValue(type)); + args.setProperty(_(TARGET), QScriptValue(target)); + + if (line != -1) + args.setProperty(_(LINE), QScriptValue(line)); + + if (column != -1) + args.setProperty(_(COLUMN), QScriptValue(column)); + + args.setProperty(_(ENABLED), QScriptValue(enabled)); + + if (!condition.isEmpty()) + args.setProperty(_(CONDITION), QScriptValue(condition)); + + if (ignoreCount != -1) + args.setProperty(_(IGNORECOUNT), QScriptValue(ignoreCount)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::executeStep() +void QmlV8DebuggerClientPrivate::changeBreakpoint(int breakpoint, bool enabled, + const QString condition, int ignoreCount) { - continueDebugging(In); + // { "seq" : <number>, + // "type" : "request", + // "command" : "changebreakpoint", + // "arguments" : { "breakpoint" : <number of the break point to clear> + // "enabled" : <initial enabled state. True or false, + // default is true> + // "condition" : <string with break point condition> + // "ignoreCount" : <number specifying the number of break point hits } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(CHANGEBREAKPOINT))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + args.setProperty(_(BREAKPOINT), QScriptValue(breakpoint)); + + args.setProperty(_(ENABLED), QScriptValue(enabled)); + + if (!condition.isEmpty()) + args.setProperty(_(CONDITION), QScriptValue(condition)); + + if (ignoreCount != -1) + args.setProperty(_(IGNORECOUNT), QScriptValue(ignoreCount)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::executeStepOut() +void QmlV8DebuggerClientPrivate::clearBreakpoint(int breakpoint) { - continueDebugging(Out); + // { "seq" : <number>, + // "type" : "request", + // "command" : "clearbreakpoint", + // "arguments" : { "breakpoint" : <number of the break point to clear> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(CLEARBREAKPOINT))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + args.setProperty(_(BREAKPOINT), QScriptValue(breakpoint)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::executeNext() +void QmlV8DebuggerClientPrivate::setExceptionBreak(QmlV8DebuggerClient::Exceptions type, + bool enabled) { - continueDebugging(Next); + // { "seq" : <number>, + // "type" : "request", + // "command" : "setexceptionbreak", + // "arguments" : { "type" : <string: "all", or "uncaught">, + // "enabled" : <optional bool: enables the break type if true> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(SETEXCEPTIONBREAK))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + if (type == QmlV8DebuggerClient::AllExceptions) + args.setProperty(_(TYPE), QScriptValue(_(ALL))); + //Not Supported + // else if (type == QmlV8DebuggerClient::UncaughtExceptions) + // args.setProperty(_(TYPE),QScriptValue(_(UNCAUGHT))); + + if (enabled) + args.setProperty(_(ENABLED), QScriptValue(enabled)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::executeStepI() +void QmlV8DebuggerClientPrivate::listBreakpoints() { - continueDebugging(In); + // { "seq" : <number>, + // "type" : "request", + // "command" : "listbreakpoints", + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(LISTBREAKPOINTS))); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::continueInferior() +void QmlV8DebuggerClientPrivate::v8flags(const QString flags) +{ + // { "seq" : <number>, + // "type" : "request", + // "command" : "v8flags", + // "arguments" : { "flags" : <string: a sequence of v8 flags just like + // those used on the command line> + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(V8FLAGS))); + + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + + args.setProperty(_(FLAGS), QScriptValue(flags)); + + jsonVal.setProperty(_(ARGUMENTS), args); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +} + +void QmlV8DebuggerClientPrivate::version() { - continueDebugging(Continue); + // { "seq" : <number>, + // "type" : "request", + // "command" : "version", + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), QScriptValue(_(VERSION))); + + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::interruptInferior() +//void QmlV8DebuggerClientPrivate::profile(ProfileCommand command) +//{ +//// { "seq" : <number>, +//// "type" : "request", +//// "command" : "profile", +//// "arguments" : { "command" : "resume" or "pause" } +//// } +// QScriptValue jsonVal = initObject(); +// jsonVal.setProperty(_(COMMAND), QScriptValue(_(PROFILE))); + +// QScriptValue args = m_parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); + +// if (command == Resume) +// args.setProperty(_(COMMAND), QScriptValue(_(RESUME))); +// else +// args.setProperty(_(COMMAND), QScriptValue(_(PAUSE))); + +// args.setProperty(_("modules"), QScriptValue(1)); +// jsonVal.setProperty(_(ARGUMENTS), args); + +// const QScriptValue jsonMessage = m_stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); +// q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); +//} + +void QmlV8DebuggerClientPrivate::gc() { - QByteArray request; + // { "seq" : <number>, + // "type" : "request", + // "command" : "gc", + // "arguments" : { "type" : <string: "all">, + // } + // } + QScriptValue jsonVal = initObject(); + jsonVal.setProperty(_(COMMAND), + QScriptValue(_(GARBAGECOLLECTOR))); - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "interrupt"; + QScriptValue args = parser.call(QScriptValue(), QScriptValueList() << QScriptValue(_(OBJECT))); - JsonInputStream(request) << '}'; + args.setProperty(_(TYPE), QScriptValue(_(ALL))); - sendMessage(packMessage(request)); + jsonVal.setProperty(_(ARGUMENTS), args); + const QScriptValue jsonMessage = stringifier.call(QScriptValue(), QScriptValueList() << jsonVal); + q->sendMessage(packMessage(jsonMessage.toString().toUtf8())); } -void QmlV8DebuggerClient::startSession() +QmlV8ObjectData QmlV8DebuggerClientPrivate::extractData(const QVariant &data) { - QByteArray request; + // { "handle" : <handle>, + // "type" : <"undefined", "null", "boolean", "number", "string", "object", "function" or "frame"> + // } + + // {"handle":<handle>,"type":"undefined"} + + // {"handle":<handle>,"type":"null"} + + // { "handle":<handle>, + // "type" : <"boolean", "number" or "string"> + // "value" : <JSON encoded value> + // } + + // {"handle":7,"type":"boolean","value":true} + + // {"handle":8,"type":"number","value":42} + + // { "handle" : <handle>, + // "type" : "object", + // "className" : <Class name, ECMA-262 property [[Class]]>, + // "constructorFunction" : {"ref":<handle>}, + // "protoObject" : {"ref":<handle>}, + // "prototypeObject" : {"ref":<handle>}, + // "properties" : [ {"name" : <name>, + // "ref" : <handle> + // }, + // ... + // ] + // } + + // { "handle" : <handle>, + // "type" : "function", + // "className" : "Function", + // "constructorFunction" : {"ref":<handle>}, + // "protoObject" : {"ref":<handle>}, + // "prototypeObject" : {"ref":<handle>}, + // "name" : <function name>, + // "inferredName" : <inferred function name for anonymous functions> + // "source" : <function source>, + // "script" : <reference to function script>, + // "scriptId" : <id of function script>, + // "position" : <function begin position in script>, + // "line" : <function begin source line in script>, + // "column" : <function begin source column in script>, + // "properties" : [ {"name" : <name>, + // "ref" : <handle> + // }, + // ... + // ] + // } + + QmlV8ObjectData objectData; + const QVariantMap dataMap = data.toMap(); + QString type = dataMap.value(_(TYPE)).toString(); + + if (type == _("undefined")) { + objectData.type = QByteArray("undefined"); + objectData.value = QVariant(_("undefined")); + + } else if (type == _("null")) { + objectData.type = QByteArray("null"); + objectData.value= QVariant(_("null")); + + } else if (type == _("boolean")) { + objectData.type = QByteArray("boolean"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("number")) { + objectData.type = QByteArray("number"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("string")) { + objectData.type = QByteArray("string"); + objectData.value = dataMap.value(_(VALUE)); + + } else if (type == _("object")) { + objectData.type = QByteArray("object"); + objectData.value = dataMap.value(_("className")); + objectData.properties = dataMap.value(_("properties")); + + } else if (type == _("function")) { + objectData.type = QByteArray("function"); + objectData.value = dataMap.value(_(NAME)); + objectData.properties = dataMap.value(_("properties")); + + } else if (type == _("script")) { + objectData.type = QByteArray("script"); + objectData.value = dataMap.value(_(NAME)); + } - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "connect"; + return objectData; +} - JsonInputStream(request) << '}'; +void QmlV8DebuggerClientPrivate::clearCache() +{ + localsAndWatchers.clear(); + evaluatingWatches.clear(); + watchesToEvaluate.clear(); + currentFrameScopes.clear(); + localDataList.clear(); + refsVal.clear(); +} - sendMessage(packMessage(request)); +QByteArray QmlV8DebuggerClientPrivate::packMessage(const QByteArray &message) +{ + SDEBUG(message); + QByteArray reply; + QDataStream rs(&reply, QIODevice::WriteOnly); + QByteArray cmd = V8DEBUG; + rs << cmd << message; + return reply; +} + +QScriptValue QmlV8DebuggerClientPrivate::initObject() +{ + QScriptValue jsonVal = parser.call(QScriptValue(), + QScriptValueList() << QScriptValue(_(OBJECT))); + jsonVal.setProperty(_(SEQ), QScriptValue(++sequence)); + jsonVal.setProperty(_(TYPE), _(REQUEST)); + return jsonVal; +} + +/////////////////////////////////////////////////////////////////////// +// +// QmlV8DebuggerClient +// +/////////////////////////////////////////////////////////////////////// + +QmlV8DebuggerClient::QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection *client) + : QmlDebuggerClient(client, QLatin1String("V8Debugger")), + d(new QmlV8DebuggerClientPrivate(this)) +{ + resetDebugger(); +} + +QmlV8DebuggerClient::~QmlV8DebuggerClient() +{ + delete d; +} + +void QmlV8DebuggerClient::startSession() +{ + resetDebugger(); + d->debugServiceState = QmlV8DebuggerClient::RunningState; + d->connect(); } void QmlV8DebuggerClient::endSession() { - clearExceptionSelection(); + d->disconnect(); +} - QByteArray request; +void QmlV8DebuggerClient::executeStep() +{ + d->continueDebugging(In); +} - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "disconnect"; +void QmlV8DebuggerClient::executeStepOut() +{ + d->continueDebugging(Out); +} - JsonInputStream(request) << '}'; +void QmlV8DebuggerClient::executeNext() +{ + d->continueDebugging(Next); +} + +void QmlV8DebuggerClient::executeStepI() +{ + d->continueDebugging(In); +} + +void QmlV8DebuggerClient::continueInferior() +{ + d->continueDebugging(Continue); +} - sendMessage(packMessage(request)); +void QmlV8DebuggerClient::interruptInferior() +{ + d->interrupt(); } void QmlV8DebuggerClient::activateFrame(int index) { - setLocals(index); + d->backtrace(index); } bool QmlV8DebuggerClient::acceptsBreakpoint(const BreakpointModelId &id) @@ -323,57 +940,38 @@ void QmlV8DebuggerClient::insertBreakpoint(const BreakpointModelId &id) if (params.type == BreakpointAtJavaScriptThrow) { handler->notifyBreakpointInsertOk(id); - return breakOnException(AllExceptions, params.enabled); - } + d->setExceptionBreak(AllExceptions, params.enabled); - QByteArray request; + } else if (params.type == BreakpointByFileAndLine) { + d->setBreakpoint(QString(_(SCRIPT)), QFileInfo(params.fileName).fileName(), + params.lineNumber - 1, -1, params.enabled, + QString(params.condition), params.ignoreCount); - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "setbreakpoint"; - JsonInputStream(request) << ',' << "arguments" << ':' << '{'; - if (params.type == BreakpointByFileAndLine) { - JsonInputStream(request) << "type" << ':' << "script"; - JsonInputStream(request) << ',' << "target" << ':' << QFileInfo(params.fileName).fileName().toUtf8(); - JsonInputStream(request) << ',' << "line" << ':' << params.lineNumber - 1; } else if (params.type == BreakpointByFunction) { - JsonInputStream(request) << "type" << ':' << "function"; - JsonInputStream(request) << ',' << "target" << ':' << params.functionName.toUtf8(); + d->setBreakpoint(QString(_(FUNCTION)), params.functionName, + -1, -1, params.enabled, QString(params.condition), + params.ignoreCount); + } else if (params.type == BreakpointOnQmlSignalHandler) { - JsonInputStream(request) << "type" << ':' << "event"; - JsonInputStream(request) << ',' << "target" << ':' << params.functionName.toUtf8(); + d->setBreakpoint(QString(_(EVENT)), params.functionName, + -1, -1, params.enabled); } - JsonInputStream(request) << ',' << "enabled" << ':' << params.enabled; - - JsonInputStream(request) << '}'; - JsonInputStream(request) << '}'; - d->breakpointsSync.insert(d->sequence,id); - sendMessage(packMessage(request)); + d->breakpointsSync.insert(d->sequence, id); } void QmlV8DebuggerClient::removeBreakpoint(const BreakpointModelId &id) { BreakHandler *handler = d->engine->breakHandler(); - if (handler->breakpointData(id).type == BreakpointAtJavaScriptThrow) { - return breakOnException(AllExceptions, false); - } - int breakpoint = d->breakpoints.value(id); d->breakpoints.remove(id); - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "clearbreakpoint"; - - JsonInputStream(request) << ',' << "arguments" << ':'; - JsonInputStream(request) << '{' << "breakpoint" << ':' << breakpoint; - JsonInputStream(request) << '}'; - - JsonInputStream(request) << '}'; - - sendMessage(packMessage(request)); + if (handler->breakpointData(id).type == BreakpointAtJavaScriptThrow) { + d->setExceptionBreak(AllExceptions); + } else { + d->clearBreakpoint(breakpoint); + } } void QmlV8DebuggerClient::changeBreakpoint(const BreakpointModelId &id) @@ -382,107 +980,90 @@ void QmlV8DebuggerClient::changeBreakpoint(const BreakpointModelId &id) const BreakpointParameters ¶ms = handler->breakpointData(id); if (params.type == BreakpointAtJavaScriptThrow) { - return breakOnException(AllExceptions, params.enabled); + d->setExceptionBreak(AllExceptions, params.enabled); } + + int breakpoint = d->breakpoints.value(id); + d->changeBreakpoint(breakpoint, params.enabled, QString(params.condition), + params.ignoreCount); + + BreakpointResponse br = handler->response(id); + br.enabled = params.enabled; + br.condition = params.condition; + br.ignoreCount = params.ignoreCount; + handler->setResponse(id, br); } -void QmlV8DebuggerClient::updateBreakpoints() +void QmlV8DebuggerClient::synchronizeBreakpoints() { + //NOT USED } void QmlV8DebuggerClient::assignValueInDebugger(const QByteArray /*expr*/, const quint64 &/*id*/, - const QString &/*property*/, const QString &/*value*/) + const QString &property, const QString &value) { - //TODO:: + StackHandler *stackHandler = d->engine->stackHandler(); + QString expression = QString(_("%1 = %2;")).arg(property).arg(value); + if (stackHandler->isContentsValid()) { + d->evaluate(expression, false, false, stackHandler->currentIndex()); + } } -void QmlV8DebuggerClient::updateWatchData(const WatchData *data) +void QmlV8DebuggerClient::updateWatchData(const WatchData &data) { - if (!data->iname.startsWith("watch.")) - return; - - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "evaluate"; - - JsonInputStream(request) << ',' << "arguments" << ':'; - JsonInputStream(request) << '{' << "expression" << ':' << data->exp; - JsonInputStream(request) << ',' << "frame" << ':' << d->engine->stackHandler()->currentFrame().level; - JsonInputStream(request) << '}'; - - JsonInputStream(request) << '}'; - - d->watches.insert(d->sequence,data->iname); - - sendMessage(packMessage(request)); - + if (data.isWatcher()) { + QString exp(data.exp); + if (!d->watchedExpressions.contains(exp)) { + //Push new expression to the stack + d->watchesToEvaluate.push(exp); + } + } } void QmlV8DebuggerClient::executeDebuggerCommand(const QString &command) { - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "evaluate"; - - JsonInputStream(request) << ',' << "arguments" << ':'; - JsonInputStream(request) << '{' << "expression" << ':' << command; - JsonInputStream(request) << ',' << "global" << ':' << true; - JsonInputStream(request) << '}'; - - JsonInputStream(request) << '}'; - - sendMessage(packMessage(request)); - + StackHandler *stackHandler = d->engine->stackHandler(); + if (stackHandler->isContentsValid()) { + d->evaluate(command, false, false, stackHandler->currentIndex()); + d->evaluatingExpression.insert(d->sequence, command); + } else { + //Currently cannot evaluate if not in a javascript break + d->engine->showMessage(_("Request Was Unsuccessful"), ScriptConsoleOutput); + // d->evaluate(command); + } } -void QmlV8DebuggerClient::synchronizeWatchers(const QStringList &/*watchers*/) +void QmlV8DebuggerClient::synchronizeWatchers(const QStringList &watchers) { - //TODO:: send watchers list + SDEBUG(watchers); + //Cache the watched expression List + d->watchedExpressions = watchers; + //Evaluate new expressions one at a time. + if (!d->watchesToEvaluate.isEmpty()) { + StackHandler *stackHandler = d->engine->stackHandler(); + const QString exp = d->watchesToEvaluate.pop(); + if (stackHandler->isContentsValid()) { + d->evaluate(exp, false, false, stackHandler->currentIndex()); + } else { + d->evaluate(exp); + } + d->evaluatingWatches.insert(d->sequence, exp); + } } void QmlV8DebuggerClient::expandObject(const QByteArray &iname, quint64 objectId) { - d->locals.insert(objectId,iname); - QList<int> ids; - ids.append(objectId); - - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "lookup"; - - JsonInputStream(request) << ',' << "arguments" << ':'; - JsonInputStream(request) << '{' << "handles" << ':' << ids; - JsonInputStream(request) << '}'; - - JsonInputStream(request) << '}'; - - sendMessage(packMessage(request)); - -} - -void QmlV8DebuggerClient::listBreakpoints() -{ - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "listbreakpoints"; - JsonInputStream(request) << '}'; - - sendMessage(packMessage(request)); + if (d->debugServiceState != QmlV8DebuggerClient::RunningState) { + if (!d->localsAndWatchers.contains(objectId)) { + d->lookup(QList<int>() << objectId); + d->localsAndWatchers.insert(objectId, iname); + } + } } -void QmlV8DebuggerClient::backtrace() +void QmlV8DebuggerClient::setEngine(QmlEngine *engine) { - QByteArray request; - - JsonInputStream(request) << '{' << INITIALPARAMS ; - JsonInputStream(request) << ',' << "command" << ':' << "backtrace"; - JsonInputStream(request) << '}'; - - sendMessage(packMessage(request)); + d->engine = engine; } void QmlV8DebuggerClient::messageReceived(const QByteArray &data) @@ -491,292 +1072,801 @@ void QmlV8DebuggerClient::messageReceived(const QByteArray &data) QByteArray command; ds >> command; - if (command == "V8DEBUG") { + if (command == V8DEBUG) { QByteArray response; ds >> response; + QString responseString(response); + + SDEBUG(responseString); - JsonValue value(response); - const QString type = value.findChild("type").toVariant().toString(); + const QVariantMap resp = d->parser.call(QScriptValue(), + QScriptValueList() << + QScriptValue(responseString)).toVariant().toMap(); + bool isV8Running = resp.value(_("running")).toBool(); + const QString type(resp.value(_(TYPE)).toString()); - if (type == "response") { + if (type == _("response")) { - if (!value.findChild("success").toVariant().toBool()) { - //TODO:: have to handle this case for each command - d->engine->logMessage(QmlEngine::LogReceive, QString("V8 Response Error: %1").arg(QString(value.toString(true,2)))); - return; + bool success = resp.value(_("success")).toBool(); + if (!success) { + SDEBUG("Request was unsuccessful"); + d->engine->logMessage(QmlEngine::LogReceive, + QString(_("V8 Response Error: %1")).arg( + resp.value(_("message")).toString())); } - const QString debugCommand(value.findChild("command").toVariant().toString()); - if (debugCommand == "backtrace") { - setStackFrames(response); + const QString debugCommand(resp.value(_(COMMAND)).toString()); + + if (debugCommand == _(CONNECT)) { + //debugging session started + + } else if (debugCommand == _(DISCONNECT)) { + //debugging session ended + + } else if (debugCommand == _(CONTINEDEBUGGING)) { + d->requestBacktrace = true; - } else if (debugCommand == "lookup") { - expandLocal(response); + } else if (debugCommand == _(BACKTRACE)) { + if (success && d->debugServiceState != QmlV8DebuggerClient::RunningState) { + updateStack(resp.value(_(BODY)), resp.value(_(REFS))); + } + + } else if (debugCommand == _(LOOKUP)) { + if (success && d->debugServiceState != QmlV8DebuggerClient::RunningState) { + expandLocalsAndWatchers(resp.value(_(BODY)), resp.value(_(REFS))); + } - } else if (debugCommand == "setbreakpoint") { - int sequence = value.findChild("request_seq").toVariant().toInt(); - int breakpoint = value.findChild("body").findChild("breakpoint").toVariant().toInt(); - BreakpointModelId id = d->breakpointsSync.take(sequence); - d->breakpoints.insert(id,breakpoint); + } else if (debugCommand == _(EVALUATE)) { + int seq = resp.value(_("request_seq")).toInt(); + if (success) { + d->requestBacktrace = true; + updateEvaluationResult(seq, success, resp.value(_(BODY)), resp.value(_(REFS))); + } else { + QVariantMap map; + map.insert(_(TYPE), QVariant(_("string"))); + map.insert(_(VALUE), resp.value(_("message"))); + updateEvaluationResult(seq, success, QVariant(map), QVariant()); + } - //If this is an event breakpoint then set state = BreakpointInsertOk - const QString breakpointType = value.findChild("body").findChild("type").toVariant().toString(); - if (breakpointType == "event") { - d->engine->breakHandler()->notifyBreakpointInsertOk(id); + } else if (debugCommand == _(LISTBREAKPOINTS)) { + if (success && d->debugServiceState != QmlV8DebuggerClient::RunningState) { + updateBreakpoints(resp.value(_(BODY))); } - } else if (debugCommand == "evaluate") { - setExpression(response); + } else if (debugCommand == _(SETBREAKPOINT)) { + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "setbreakpoint", + // "body" : { "type" : <"function" or "script"> + // "breakpoint" : <break point number of the new break point> + // } + // "running" : <is the VM running after sending this response> + // "success" : true + // } + + int seq = resp.value(_("request_seq")).toInt(); + const QVariantMap breakpointData = resp.value(_(BODY)).toMap(); + int index = breakpointData.value(_("breakpoint")).toInt(); + + BreakpointModelId id = d->breakpointsSync.take(seq); + d->breakpoints.insert(id, index); + + d->engine->breakHandler()->notifyBreakpointInsertOk(id); + + + } else if (debugCommand == _(CHANGEBREAKPOINT)) { + // DO NOTHING + + } else if (debugCommand == _(CLEARBREAKPOINT)) { + // DO NOTHING + + } else if (debugCommand == _(SETEXCEPTIONBREAK)) { + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "setexceptionbreak", + // “body” : { "type" : <string: "all" or "uncaught" corresponding to the request.>, + // "enabled" : <bool: true if the break type is currently enabled as a result of the request> + // } + // "running" : true + // "success" : true + // } + //TODO:: + + } else if (debugCommand == _(FRAME)) { + if (success && d->debugServiceState != QmlV8DebuggerClient::RunningState) { + const QVariant body = resp.value(_(BODY)); + const QVariant refs = resp.value(_(REFS)); + const QVariant locals = body.toMap().value(_("locals")); + StackFrame frame = createStackFrame(body, refs); + updateLocals(locals, refs); + d->engine->stackHandler()->setCurrentIndex(frame.level); + } - } else if (debugCommand == "listbreakpoints") { - updateBreakpoints(response); - backtrace(); + } else if (debugCommand == _(SCOPE)) { + if (success && d->debugServiceState != QmlV8DebuggerClient::RunningState) { + const QVariant body = resp.value(_(BODY)).toMap().value(_("object")); + const QVariant refs = resp.value(_(REFS)); + updateScope(body, refs); + } + } else if (debugCommand == _(SCOPES)) { + } else if (debugCommand == _(SOURCE)) { + } else if (debugCommand == _(SCRIPTS)) { + } else if (debugCommand == _(VERSION)) { + } else if (debugCommand == _(V8FLAGS)) { + } else if (debugCommand == _(GARBAGECOLLECTOR)) { } else { - d->engine->logMessage(QmlEngine::LogReceive, value.toString(true,2)); + // DO NOTHING } - } else if (type == "event") { - const QString event(value.findChild("event").toVariant().toString()); - - if (event == "break") { - d->engine->inferiorSpontaneousStop(); - listBreakpoints(); - } else if (event == "exception") { - d->handleException = true; - d->engine->inferiorSpontaneousStop(); - storeExceptionInformation(response); - backtrace(); - } - } - } -} + if (!isV8Running + && d->debugServiceState == QmlV8DebuggerClient::ProcessingRequestState) + d->debugServiceState = QmlV8DebuggerClient::WaitingForRequestState; -void QmlV8DebuggerClient::setStackFrames(const QByteArray &message) -{ - d->frames = message; - JsonValue response(message); + } else if (type == _(EVENT)) { + const QString eventType(resp.value(_(EVENT)).toString()); - JsonValue refs = response.findChild("refs"); - JsonValue body = response.findChild("body"); + if (eventType == _("break")) { + if (d->engine->state() == InferiorRunOk) + d->engine->inferiorSpontaneousStop(); + isV8Running = false; - int totalFrames = body.findChild("totalFrames").toVariant().toInt(); - JsonValue stackFrames = body.findChild("frames"); + } else if (eventType == _("exception")) { + const QVariantMap body = resp.value(_(BODY)).toMap(); + int lineNumber = body.value(_("sourceLine")).toInt() + 1; - StackFrames ideStackFrames; - for (int i = 0; i != totalFrames; ++i) { + const QVariantMap script = body.value(_("script")).toMap(); + QUrl fileUrl(script.value(_(NAME)).toString()); + QString filePath = d->engine->toFileInProject(fileUrl); - JsonValue stackFrame = stackFrames.childAt(i); + const QVariantMap exception = body.value(_("exception")).toMap(); + QString errorMessage = exception.value(_("text")).toString(); - StackFrame frame; + highlightExceptionCode(lineNumber, filePath, errorMessage); - int frameIndex = stackFrame.findChild("index").toVariant().toInt(); - frame.level = frameIndex; + if (d->engine->state() == InferiorRunOk) + d->engine->inferiorSpontaneousStop(); + isV8Running = false; + d->requestBacktrace = true; - frame.line = stackFrame.findChild("line").toVariant().toInt() + 1; + } else if (eventType == _("afterCompile")) { + d->requestListBreakpoints = true; + } + + //Sometimes we do not get event type! + //This is most probably due to a wrong eval expression. + //Redirect output to console. + if (eventType.isEmpty()) { + bool success = resp.value(_("success")).toBool(); + QVariantMap map; + map.insert(_(TYPE), QVariant(_("string"))); + map.insert(_(VALUE), resp.value(_("message"))); + //Since there is no sequence value, best estimate is + //last sequence value + updateEvaluationResult(d->sequence, success, QVariant(map), QVariant()); + if (!isV8Running + && d->debugServiceState == QmlV8DebuggerClient::ProcessingRequestState) + d->debugServiceState = QmlV8DebuggerClient::WaitingForRequestState; + } + + if (!isV8Running + && d->debugServiceState == QmlV8DebuggerClient::RunningState) + d->debugServiceState = QmlV8DebuggerClient::WaitingForRequestState; + } + + if (isV8Running) { + resetDebugger(); + d->debugServiceState = QmlV8DebuggerClient::RunningState; + + } else { + if (d->requestListBreakpoints) { + d->listBreakpoints(); + d->requestListBreakpoints = false; + } + + if (d->requestBacktrace) { + d->backtrace(d->currentFrameIndex); + d->requestBacktrace = false; + } + + if (d->debugServiceState == QmlV8DebuggerClient::WaitingForRequestState + && !d->requestQueue.isEmpty()) { + QmlDebuggerClient::sendMessage(d->requestQueue.dequeue()); + d->debugServiceState = QmlV8DebuggerClient::ProcessingRequestState; + } - int index = indexInRef(refs,stackFrame.findChild("func").findChild("ref").toVariant().toInt()); - if (index != -1) { - JsonValue func = refs.childAt(index); - frame.function = func.findChild("name").toVariant().toString(); } - index = indexInRef(refs,stackFrame.findChild("script").findChild("ref").toVariant().toInt()); - if (index != -1) { - JsonValue script = refs.childAt(index); - frame.file = d->engine->toFileInProject(script.findChild("name").toVariant().toString()); - frame.usable = QFileInfo(frame.file).isReadable(); + } else { + //DO NOTHING + } + SDEBUG(QString(_("State: %1")).arg(d->debugServiceState)); +} + +void QmlV8DebuggerClient::sendMessage(const QByteArray &msg) +{ + if (d->debugServiceState == QmlV8DebuggerClient::RunningState) { + QmlDebuggerClient::sendMessage(msg); + } else if (d->debugServiceState == QmlV8DebuggerClient::WaitingForRequestState) { + QmlDebuggerClient::sendMessage(msg); + d->debugServiceState = QmlV8DebuggerClient::ProcessingRequestState; + } else { + d->requestQueue.enqueue(msg); + } + +} + +void QmlV8DebuggerClient::updateStack(const QVariant &bodyVal, const QVariant &refsVal) +{ + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "backtrace", + // "body" : { "fromFrame" : <number> + // "toFrame" : <number> + // "totalFrames" : <number> + // "frames" : <array of frames - see frame request for details> + // } + // "running" : <is the VM running after sending this response> + // "success" : true + // } + + const QVariantMap body = bodyVal.toMap(); + const QVariantList frames = body.value(_("frames")).toList(); + + int fromFrameIndex = body.value(_("fromFrame")).toInt(); + + + if (0 == fromFrameIndex) { + StackFrames stackFrames; + foreach (const QVariant &frame, frames) { + stackFrames << createStackFrame(frame, refsVal); } - ideStackFrames << frame; + d->engine->stackHandler()->setFrames(stackFrames); } - d->engine->stackHandler()->setFrames(ideStackFrames); + if (d->currentFrameIndex != fromFrameIndex) { + StackHandler *stackHandler = d->engine->stackHandler(); + stackHandler->setCurrentIndex(fromFrameIndex); + d->engine->gotoLocation(stackHandler->currentFrame()); + d->currentFrameIndex = fromFrameIndex; + } - if (!ideStackFrames.isEmpty()) { - setLocals(0); - d->engine->gotoLocation(ideStackFrames.value(0)); + //Update all Locals visible in current scope + //Traverse the scope chain and store the local properties + //in a list and show them in the Locals Window. + const QVariantMap currentFrame = frames.value(0).toMap(); + d->clearCache(); + d->refsVal = refsVal; + //Set "this" variable + { + WatchData data; + data.exp = QByteArray("this"); + data.name = QString(data.exp); + data.iname = QByteArray("local.") + data.exp; + QVariantMap receiver = currentFrame.value(_("receiver")).toMap(); + if (receiver.contains(_(REF))) { + receiver = valueFromRef(receiver.value(_(REF)).toInt(), refsVal).toMap(); + } + data.id = receiver.value(_("handle")).toInt(); + QmlV8ObjectData receiverData = d->extractData(QVariant(receiver)); + data.type = receiverData.type; + data.value = receiverData.value.toString(); + data.setHasChildren(receiverData.properties.toList().count()); + d->localDataList << data; } - if (d->handleException) { - handleException(); + const QVariantList currentFrameScopes = currentFrame.value(_("scopes")).toList(); + foreach (const QVariant &scope, currentFrameScopes) { + //Do not query for global types (0) + //Showing global properties increases clutter. + if (scope.toMap().value(_("type")).toInt() == 0) + continue; + d->currentFrameScopes.push(scope.toMap().value(_("index")).toInt()); + } + if (!d->currentFrameScopes.isEmpty()) { + d->scope(d->currentFrameScopes.pop(), d->currentFrameIndex); + } else { + updateLocalsAndWatchers(); } } -void QmlV8DebuggerClient::setLocals(int frameIndex) +StackFrame QmlV8DebuggerClient::createStackFrame(const QVariant &bodyVal, const QVariant &refsVal) { - JsonValue response(d->frames); + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "frame", + // "body" : { "index" : <frame number>, + // "receiver" : <frame receiver>, + // "func" : <function invoked>, + // "script" : <script for the function>, + // "constructCall" : <boolean indicating whether the function was called as constructor>, + // "debuggerFrame" : <boolean indicating whether this is an internal debugger frame>, + // "arguments" : [ { name: <name of the argument - missing of anonymous argument>, + // value: <value of the argument> + // }, + // ... <the array contains all the arguments> + // ], + // "locals" : [ { name: <name of the local variable>, + // value: <value of the local variable> + // }, + // ... <the array contains all the locals> + // ], + // "position" : <source position>, + // "line" : <source line>, + // "column" : <source column within the line>, + // "sourceLineText" : <text for current source line>, + // "scopes" : [ <array of scopes, see scope request below for format> ], + + // } + // "running" : <is the VM running after sending this response> + // "success" : true + // } + + const QVariantMap body = bodyVal.toMap(); + + StackFrame stackFrame; + stackFrame.level = body.value(_("index")).toInt(); + + QVariantMap func = body.value(_("func")).toMap(); + if (func.contains(_(REF))) { + func = valueFromRef(func.value(_(REF)).toInt(), refsVal).toMap(); + } + stackFrame.function = d->extractData(QVariant(func)).value.toString(); - JsonValue refs = response.findChild("refs"); - JsonValue body = response.findChild("body"); + QVariantMap file = body.value(_("script")).toMap(); + if (file.contains(_(REF))) { + file = valueFromRef(file.value(_(REF)).toInt(), refsVal).toMap(); + } + stackFrame.file = d->engine->toFileInProject(d->extractData(QVariant(file)).value.toString()); + stackFrame.usable = QFileInfo(stackFrame.file).isReadable(); - int totalFrames = body.findChild("totalFrames").toVariant().toInt(); - JsonValue stackFrames = body.findChild("frames"); + QVariantMap receiver = body.value(_("receiver")).toMap(); + if (receiver.contains(_(REF))) { + receiver = valueFromRef(receiver.value(_(REF)).toInt(), refsVal).toMap(); + } + stackFrame.to = d->extractData(QVariant(receiver)).value.toString(); + stackFrame.line = body.value(_("line")).toInt() + 1; - for (int i = 0; i != totalFrames; ++i) { + return stackFrame; +} - JsonValue stackFrame = stackFrames.childAt(i); - int index = stackFrame.findChild("index").toVariant().toInt(); - if (index != frameIndex) +void QmlV8DebuggerClient::updateLocals(const QVariant &localsVal, const QVariant &refsVal) +{ + //Add Locals + const QVariantList locals = localsVal.toList(); + QList<WatchData> localDataList; + foreach (const QVariant &localValue, locals) { + QVariantMap localData = localValue.toMap(); + WatchData data; + data.exp = localData.value(_(NAME)).toByteArray(); + //Check for v8 specific local data + if (data.exp.startsWith(".") || data.exp.isEmpty()) continue; - JsonValue locals = stackFrame.findChild("locals"); + data.name = QString(data.exp); + data.iname = QByteArray("local.") + data.exp; - d->engine->watchHandler()->beginCycle(); + localData = valueFromRef(localData.value(_(VALUE)).toMap() + .value(_(REF)).toInt(), refsVal).toMap(); + data.id = localData.value(_(HANDLE)).toInt(); - int localsCount = locals.childCount(); - for (int j = 0; j != localsCount; ++j) { - JsonValue local = locals.childAt(j); + QmlV8ObjectData objectData = d->extractData(QVariant(localData)); + data.type = objectData.type; + data.value = objectData.value.toString(); - WatchData data; - data.exp = local.findChild("name").toVariant().toByteArray(); - //Check for v8 specific local - if (data.exp.startsWith(".")) - continue; + data.setHasChildren(objectData.properties.toList().count()); - data.name = data.exp; - data.iname = "local." + data.exp; - JsonValue val = refs.childAt(indexInRef(refs,local.findChild("value").findChild("ref").toVariant().toInt())); - data.type = val.findChild("type").toVariant().toByteArray(); + localDataList << data; + } - if (data.type == "object") { - data.hasChildren = true; - data.value = val.findChild("className").toVariant().toByteArray(); + d->engine->watchHandler()->insertBulkData(localDataList); - } else if (data.type == "function" || data.type == "undefined") { - data.hasChildren = false; - data.value = val.findChild("text").toVariant().toByteArray(); +} - } else { - data.hasChildren = false; - data.value = val.findChild("value").toVariant().toByteArray(); - } +void QmlV8DebuggerClient::updateScope(const QVariant &bodyVal, const QVariant &refsVal) +{ +// { "seq" : <number>, +// "type" : "response", +// "request_seq" : <number>, +// "command" : "scope", +// "body" : { "index" : <index of this scope in the scope chain. Index 0 is the top scope +// and the global scope will always have the highest index for a +// frame>, +// "frameIndex" : <index of the frame>, +// "type" : <type of the scope: +// 0: Global +// 1: Local +// 2: With +// 3: Closure +// 4: Catch >, +// "object" : <the scope object defining the content of the scope. +// For local and closure scopes this is transient objects, +// which has a negative handle value> +// } +// "running" : <is the VM running after sending this response> +// "success" : true +// } + QVariantMap bodyMap = bodyVal.toMap(); + if (bodyMap.contains(_(REF))) { + bodyMap = valueFromRef(bodyMap.value(_(REF)).toInt(), + refsVal).toMap(); + } - data.id = val.findChild("handle").toVariant().toInt(); + const QVariantList properties = bodyMap.value(_("properties")).toList(); - d->engine->watchHandler()->insertData(data); + foreach (const QVariant &property, properties) { + QVariantMap localData = property.toMap(); + WatchData data; + data.exp = localData.value(_(NAME)).toByteArray(); + //Check for v8 specific local data + if (data.exp.startsWith(".") || data.exp.isEmpty()) + continue; - if (d->engine->watchHandler()->expandedINames().contains(data.iname)) { - expandObject(data.iname, data.id); + data.name = QString(data.exp); + data.iname = QByteArray("local.") + data.exp; + + int handle = localData.value(_(REF)).toInt(); + localData = valueFromRef(handle, d->refsVal).toMap(); + if (localData.isEmpty()) { + //Fetch Data asynchronously and insert later + // see expandLocalsAndWatchers() + if (!d->localsAndWatchers.contains(handle)) { + d->lookup(QList<int>() << handle); + d->localsAndWatchers.insert(handle, data.exp); } + + } else { + data.id = localData.value(_(HANDLE)).toInt(); + + QmlV8ObjectData objectData = d->extractData(QVariant(localData)); + data.type = objectData.type; + data.value = objectData.value.toString(); + + data.setHasChildren(objectData.properties.toList().count()); + + d->localDataList << data; } + } - d->engine->watchHandler()->endCycle(); + if (!d->currentFrameScopes.isEmpty()) { + d->scope(d->currentFrameScopes.pop(), d->currentFrameIndex); + } else { + updateLocalsAndWatchers(); } } -void QmlV8DebuggerClient::expandLocal(const QByteArray &message) +void QmlV8DebuggerClient::updateEvaluationResult(int sequence, bool success, const QVariant &bodyVal, + const QVariant &refsVal) { - JsonValue response(message); + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "evaluate", + // "body" : ... + // "running" : <is the VM running after sending this response> + // "success" : true + // } + QVariantMap bodyMap = bodyVal.toMap(); + if (bodyMap.contains(_(REF))) { + bodyMap = valueFromRef(bodyMap.value(_(REF)).toInt(), + refsVal).toMap(); + } + + QmlV8ObjectData body = d->extractData(QVariant(bodyMap)); + + if (d->evaluatingExpression.contains(sequence)) { + d->evaluatingExpression.take(sequence); + //Console + d->engine->showMessage(body.value.toString(), ScriptConsoleOutput); + + } else if (d->evaluatingWatches.contains(sequence)) { + d->requestBacktrace = false; + QString exp = d->evaluatingWatches.take(sequence); + QByteArray iname = d->engine->watchHandler()->watcherName(exp.toLatin1()); + SDEBUG(QString(iname)); + WatchData data; + data.exp = exp.toLatin1(); + data.name = exp; + data.iname = iname; + data.id = bodyMap.value(_(HANDLE)).toInt(); + if (success) { + data.type = body.type; + data.value = body.value.toString(); + } else { + //Do not set type since it is unkown + data.setError(body.value.toString()); + } + + //TODO:: Fix expanding watched objects/expressions +// const QVariantList properties = body.properties.toList(); +// data.setHasChildren(properties.count()); + + //Insert the newly evaluated expression to the Watchers Window + d->engine->watchHandler()->beginCycle(false); + d->engine->watchHandler()->insertData(data); + d->engine->watchHandler()->endCycle(); + + //Check if there are more expressions to be evaluated + //Evaluate one at a time. + if (!d->watchesToEvaluate.isEmpty()) { + StackHandler *stackHandler = d->engine->stackHandler(); + const QString exp = d->watchesToEvaluate.pop(); + if (stackHandler->isContentsValid()) { + d->evaluate(exp, false, false, stackHandler->currentIndex()); + } else { + d->evaluate(exp); + } + d->evaluatingWatches.insert(d->sequence, exp); + + } + + + // foreach (const QVariant &property, properties) { + // QVariantMap propertyData = property.toMap(); + // WatchData data; + // data.exp = propertyData.value(_(NAME)).toByteArray(); - JsonValue refs = response.findChild("refs"); - JsonValue body = response.findChild("body"); - JsonValue details = body.childAt(0); + // //Check for v8 specific local data + // if (data.exp.startsWith(".") || data.exp.isEmpty()) + // continue; - int id = QString(details.name()).toInt(); - QByteArray prepend = d->locals.take(id); + // data.name = data.exp; + // data.iname = prepend + '.' + data.exp; + // propertyData = valueFromRef(propertyData.value(_(REF)).toInt(), + // refsVal).toMap(); + // data.id = propertyData.value(_(HANDLE)).toInt(); - JsonValue properties = details.findChild("properties"); - int propertiesCount = properties.childCount(); + // QmlV8ObjectData objectData = d->extractData(QVariant(propertyData)); + // data.type = objectData.type; + // data.value = objectData.value.toString(); - for (int k = 0; k != propertiesCount; ++k) { - JsonValue property = properties.childAt(k); - setPropertyValue(refs,property,prepend); + // data.setHasChildren(objectData.properties.toList().count()); + // d->engine->watchHandler()->insertData(data); + // } } } -void QmlV8DebuggerClient::setExpression(const QByteArray &message) +void QmlV8DebuggerClient::updateBreakpoints(const QVariant &bodyVal) { - JsonValue response(message); - JsonValue body = response.findChild("body"); + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "listbreakpoints", + // "body" : { "breakpoints": [ { "type" : <string: "scriptId" or "scriptName".>, + // "script_id" : <int: script id. Only defined if type is scriptId.>, + // "script_name" : <string: script name. Only defined if type is scriptName.>, + // "number" : <int: breakpoint number. Starts from 1.>, + // "line" : <int: line number of this breakpoint. Starts from 0.>, + // "column" : <int: column number of this breakpoint. Starts from 0.>, + // "groupId" : <int: group id of this breakpoint.>, + // "hit_count" : <int: number of times this breakpoint has been hit. Starts from 0.>, + // "active" : <bool: true if this breakpoint is enabled.>, + // "ignoreCount" : <int: remaining number of times to ignore breakpoint. Starts from 0.>, + // "actual_locations" : <actual locations of the breakpoint.>, + // } + // ], + // "breakOnExceptions" : <true if break on all exceptions is enabled>, + // "breakOnUncaughtExceptions" : <true if break on uncaught exceptions is enabled> + // } + // "running" : <is the VM running after sending this response> + // "success" : true + // } + + const QVariantMap body = bodyVal.toMap(); + const QVariantList breakpoints = body.value(_("breakpoints")).toList(); + BreakHandler *handler = d->engine->breakHandler(); + + foreach (const QVariant &breakpoint, breakpoints) { + const QVariantMap breakpointData = breakpoint.toMap(); + + int index = breakpointData.value(_("number")).toInt(); + BreakpointModelId id = d->breakpoints.key(index); + BreakpointResponse br = handler->response(id); - int seq = response.findChild("request_seq").toVariant().toInt(); + const QVariantList actualLocations = breakpointData.value(_("actual_locations")).toList(); + foreach (const QVariant &location, actualLocations) { + const QVariantMap locationData = location.toMap(); + br.lineNumber = locationData.value(_("line")).toInt() + 1;; + br.enabled = breakpointData.value(_("active")).toBool(); + br.hitCount = breakpointData.value(_("hit_count")).toInt(); + br.ignoreCount = breakpointData.value(_("ignoreCount")).toInt(); - //Console - if (!d->watches.contains(seq)) { - d->engine->showMessage(body.findChild("text").toVariant().toString(), ScriptConsoleOutput); - return; + handler->setResponse(id, br); + } } +} - //TODO: For watch point +QVariant QmlV8DebuggerClient::valueFromRef(int handle, const QVariant &refsVal) +{ + QVariant variant; + const QVariantList refs = refsVal.toList(); + foreach (const QVariant &ref, refs) { + const QVariantMap refData = ref.toMap(); + if (refData.value(_(HANDLE)).toInt() == handle) { + variant = refData; + break; + } + } + return variant; } -void QmlV8DebuggerClient::updateBreakpoints(const QByteArray &message) +void QmlV8DebuggerClient::expandLocalsAndWatchers(const QVariant &bodyVal, const QVariant &refsVal) { - JsonValue response(message); + // { "seq" : <number>, + // "type" : "response", + // "request_seq" : <number>, + // "command" : "lookup", + // "body" : <array of serialized objects indexed using their handle> + // "running" : <is the VM running after sending this response> + // "success" : true + // } + const QVariantMap body = bodyVal.toMap(); + + QList<WatchData> watchDataList; + QStringList handlesList = body.keys(); + foreach (const QString &handle, handlesList) { + QmlV8ObjectData bodyObjectData = d->extractData( + body.value(handle)); + QByteArray prepend = d->localsAndWatchers.take(handle.toInt()); + + + if (prepend.isEmpty()) + return; + + if (bodyObjectData.properties.isValid()) { + //Could be an object or function + const WatchData *parent = d->engine->watchHandler()->findItem(prepend); + const QVariantList properties = bodyObjectData.properties.toList(); + foreach (const QVariant &property, properties) { + QVariantMap propertyData = property.toMap(); + WatchData data; + data.name = propertyData.value(_(NAME)).toString(); + + //Check for v8 specific local data + if (data.name.startsWith(".") || data.name.isEmpty()) + continue; + if (parent && parent->type == "object") { + if (parent->value == _("Array")) + data.exp = parent->exp + QByteArray("[") + data.name.toLatin1() + QByteArray("]"); + else if (parent->value == _("Object")) + data.exp = parent->exp + QByteArray(".") + data.name.toLatin1(); + } else { + data.exp = data.name.toLatin1(); + } - JsonValue body = response.findChild("body"); + if (prepend.startsWith("local.")) + data.iname = prepend + '.' + data.name.toLatin1(); + if (prepend.startsWith("watch.")) + data.iname = prepend; + propertyData = valueFromRef(propertyData.value(_(REF)).toInt(), + refsVal).toMap(); + data.id = propertyData.value(_(HANDLE)).toInt(); - QList<JsonValue> breakpoints = body.findChild("breakpoints").children(); - BreakHandler *handler = d->engine->breakHandler(); + QmlV8ObjectData objectData = d->extractData(QVariant(propertyData)); + data.type = objectData.type; + data.value = objectData.value.toString(); - foreach (const JsonValue &bp, breakpoints) { + data.setHasChildren(objectData.properties.toList().count()); + watchDataList << data; + } + } else { + //rest + WatchData data; + data.exp = prepend; + data.name = data.exp; + data.iname = QByteArray("local.") + data.exp; + data.id = handle.toInt(); - int bpIndex = bp.findChild("number").toVariant().toInt(); - BreakpointModelId id = d->breakpoints.key(bpIndex); - BreakpointResponse br = handler->response(id); + data.type = bodyObjectData.type; + data.value = bodyObjectData.value.toString(); - if (!br.pending) - continue; + data.setHasChildren(bodyObjectData.properties.toList().count()); - br.hitCount = bp.findChild("hit_count").toVariant().toInt(); - - QList<JsonValue> actualLocations = bp.findChild("actual_locations").children(); - foreach (const JsonValue &location, actualLocations) { - int line = location.findChild("line").toVariant().toInt() + 1; //Add the offset - br.lineNumber = line; - br.correctedLineNumber = line; - handler->setResponse(id,br); - handler->notifyBreakpointInsertOk(id); + watchDataList << data; } } + + d->engine->watchHandler()->beginCycle(false); + d->engine->watchHandler()->insertBulkData(watchDataList); + d->engine->watchHandler()->endCycle(); + d->localDataList << watchDataList; } -void QmlV8DebuggerClient::setPropertyValue(const JsonValue &refs, const JsonValue &property, const QByteArray &prepend) +void QmlV8DebuggerClient::highlightExceptionCode(int lineNumber, + const QString &filePath, + const QString &errorMessage) { - WatchData data; - data.exp = property.findChild("name").toVariant().toByteArray(); - data.name = data.exp; - data.iname = prepend + '.' + data.exp; - JsonValue val = refs.childAt(indexInRef(refs,property.findChild("ref").toVariant().toInt())); - data.type = val.findChild("type").toVariant().toByteArray(); + EditorManager *editorManager = EditorManager::instance(); + QList<IEditor *> openedEditors = editorManager->openedEditors(); - if (data.type == "object") { - data.hasChildren = true; - data.value = val.findChild("className").toVariant().toByteArray(); + // set up the format for the errors + QTextCharFormat errorFormat; + errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); + errorFormat.setUnderlineColor(Qt::red); - } else if (data.type == "function") { - data.hasChildren = false; - data.value = val.findChild("text").toVariant().toByteArray(); + foreach (IEditor *editor, openedEditors) { + if (editor->file()->fileName() == filePath) { + TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); + if (!ed) + continue; - } else { - data.hasChildren = false; - data.value = val.findChild("value").toVariant().toByteArray(); - } + QList<QTextEdit::ExtraSelection> selections; + QTextEdit::ExtraSelection sel; + sel.format = errorFormat; + QTextCursor c(ed->document()->findBlockByNumber(lineNumber - 1)); + const QString text = c.block().text(); + for (int i = 0; i < text.size(); ++i) { + if (! text.at(i).isSpace()) { + c.setPosition(c.position() + i); + break; + } + } + c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + sel.cursor = c; - data.id = val.findChild("handle").toVariant().toInt(); + sel.format.setToolTip(errorMessage); - d->engine->watchHandler()->insertData(data); + selections.append(sel); + ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections); - if (d->engine->watchHandler()->expandedINames().contains(data.iname)) { - expandObject(data.iname, data.id); + QString message = QString(_("%1: %2: %3")).arg(filePath).arg(lineNumber) + .arg(errorMessage); + d->engine->showMessage(message, ScriptConsoleOutput); + } } } -int QmlV8DebuggerClient::indexInRef(const JsonValue &refs, int refIndex) +void QmlV8DebuggerClient::clearExceptionSelection() { - for (int i = 0; i != refs.childCount(); ++i) { - JsonValue ref = refs.childAt(i); - int index = ref.findChild("handle").toVariant().toInt(); - if (index == refIndex) - return i; + EditorManager *editorManager = EditorManager::instance(); + QList<IEditor *> openedEditors = editorManager->openedEditors(); + QList<QTextEdit::ExtraSelection> selections; + + foreach (IEditor *editor, openedEditors) { + TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); + if (!ed) + continue; + + ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections); } - return -1; + } -void QmlV8DebuggerClient::setEngine(QmlEngine *engine) +void QmlV8DebuggerClient::resetDebugger() { - d->engine = engine; + clearExceptionSelection(); + d->currentFrameIndex = -1; + SDEBUG(QString(_("State: %1")).arg(d->debugServiceState)); +} + +void QmlV8DebuggerClient::updateLocalsAndWatchers() +{ + d->engine->watchHandler()->beginCycle(); + d->engine->watchHandler()->insertBulkData(d->localDataList); + d->engine->watchHandler()->endCycle(); + + //Push all Watched expressions to a stack. + //Evaluate the expressions one at a time + //and append the evaluated result to the watchers + //window (see updateEvaluationResult()) + foreach (const QString &expr, d->watchedExpressions) + d->watchesToEvaluate.push(expr); + + if (!d->watchesToEvaluate.isEmpty()) { + StackHandler *stackHandler = d->engine->stackHandler(); + const QString exp = d->watchesToEvaluate.pop(); + if (stackHandler->isContentsValid()) { + d->evaluate(exp, false, false, stackHandler->currentIndex()); + } else { + d->evaluate(exp); + } + d->evaluatingWatches.insert(d->sequence, exp); + } } } // Internal diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.h b/src/plugins/debugger/qml/qmlv8debuggerclient.h index 806ec3b06e..a5ac30aa82 100644 --- a/src/plugins/debugger/qml/qmlv8debuggerclient.h +++ b/src/plugins/debugger/qml/qmlv8debuggerclient.h @@ -36,8 +36,6 @@ #include "qmldebuggerclient.h" #include "stackframe.h" #include "watchdata.h" -#include "qmlengine.h" -#include "json.h" namespace Debugger { namespace Internal { @@ -63,6 +61,13 @@ class QmlV8DebuggerClient : public QmlDebuggerClient Next }; + enum V8DebugServiceStates + { + RunningState, + WaitingForRequestState, + ProcessingRequestState + }; + public: explicit QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection *client); ~QmlV8DebuggerClient(); @@ -84,12 +89,12 @@ public: void insertBreakpoint(const BreakpointModelId &id); void removeBreakpoint(const BreakpointModelId &id); void changeBreakpoint(const BreakpointModelId &id); - void updateBreakpoints(); + void synchronizeBreakpoints(); void assignValueInDebugger(const QByteArray expr, const quint64 &id, const QString &property, const QString &value); - void updateWatchData(const WatchData *data); + void updateWatchData(const WatchData &data); void executeDebuggerCommand(const QString &command); void synchronizeWatchers(const QStringList &watchers); @@ -98,30 +103,31 @@ public: void setEngine(QmlEngine *engine); -signals: - void notifyDebuggerStopped(); - protected: void messageReceived(const QByteArray &data); + void sendMessage(const QByteArray &msg); private: - void listBreakpoints(); - void backtrace(); - void setStackFrames(const QByteArray &message); - void setLocals(int frameIndex); - void setExpression(const QByteArray &message); - void updateBreakpoints(const QByteArray &message); - void expandLocal(const QByteArray &message); - void setPropertyValue(const Json::JsonValue &refs, const Json::JsonValue &property, const QByteArray &prepend); - int indexInRef(const Json::JsonValue &refs, int refIndex); - QByteArray packMessage(const QByteArray &message); - - void breakOnException(Exceptions exceptionsType, bool enabled); - void storeExceptionInformation(const QByteArray &message); - void handleException(); + void updateStack(const QVariant &bodyVal, const QVariant &refsVal); + StackFrame createStackFrame(const QVariant &bodyVal, const QVariant &refsVal); + void updateLocals(const QVariant &localsVal, const QVariant &refsVal); + void updateScope(const QVariant &localsVal, const QVariant &refsVal); + + void updateEvaluationResult(int sequence, bool success, const QVariant &bodyVal, + const QVariant &refsVal); + void updateBreakpoints(const QVariant &bodyVal); + + QVariant valueFromRef(int handle, const QVariant &refsVal); + + void expandLocalsAndWatchers(const QVariant &bodyVal, const QVariant &refsVal); + + void highlightExceptionCode(int lineNumber, const QString &filePath, + const QString &errorMessage); void clearExceptionSelection(); - void continueDebugging(StepAction type); + void resetDebugger(); + + void updateLocalsAndWatchers(); private: QmlV8DebuggerClientPrivate *d; diff --git a/src/plugins/debugger/qml/qmlv8debuggerclientconstants.h b/src/plugins/debugger/qml/qmlv8debuggerclientconstants.h new file mode 100644 index 0000000000..40a39ddcde --- /dev/null +++ b/src/plugins/debugger/qml/qmlv8debuggerclientconstants.h @@ -0,0 +1,121 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLV8DEBUGGERCLIENTCONSTANTS_H +#define QMLV8DEBUGGERCLIENTCONSTANTS_H + +namespace Debugger { +namespace Internal { + +const char V8DEBUG[] = "V8DEBUG"; +const char SEQ[] = "seq"; +const char TYPE[] = "type"; +const char COMMAND[] = "command"; +const char ARGUMENTS[] = "arguments"; +const char STEPACTION[] = "stepaction"; +const char STEPCOUNT[] = "stepcount"; +const char EXPRESSION[] = "expression"; +const char FRAME[] = "frame"; +const char GLOBAL[] = "global"; +const char DISABLE_BREAK[] = "disable_break"; +const char ADDITIONAL_CONTEXT[] = "additional_context"; +const char HANDLES[] = "handles"; +const char INCLUDESOURCE[] = "includeSource"; +const char FROMFRAME[] = "fromFrame"; +const char TOFRAME[] = "toFrame"; +const char BOTTOM[] = "bottom"; +const char NUMBER[] = "number"; +const char FRAMENUMBER[] = "frameNumber"; +const char TYPES[] = "types"; +const char IDS[] = "ids"; +const char FILTER[] = "filter"; +const char FROMLINE[] = "fromLine"; +const char TOLINE[] = "toLine"; +const char TARGET[] = "target"; +const char LINE[] = "line"; +const char COLUMN[] = "column"; +const char ENABLED[] = "enabled"; +const char CONDITION[] = "condition"; +const char IGNORECOUNT[] = "ignoreCount"; +const char BREAKPOINT[] = "breakpoint"; +const char FLAGS[] = "flags"; + +const char CONTINEDEBUGGING[] = "continue"; +const char EVALUATE[] = "evaluate"; +const char LOOKUP[] = "lookup"; +const char BACKTRACE[] = "backtrace"; +const char SCOPE[] = "scope"; +const char SCOPES[] = "scopes"; +const char SCRIPTS[] = "scripts"; +const char SOURCE[] = "source"; +const char SETBREAKPOINT[] = "setbreakpoint"; +const char CHANGEBREAKPOINT[] = "changebreakpoint"; +const char CLEARBREAKPOINT[] = "clearbreakpoint"; +const char SETEXCEPTIONBREAK[] = "setexceptionbreak"; +const char V8FLAGS[] = "v8flags"; +const char VERSION[] = "version"; +const char DISCONNECT[] = "disconnect"; +const char LISTBREAKPOINTS[] = "listbreakpoints"; +const char GARBAGECOLLECTOR[] = "gc"; +//const char PROFILE[] = "profile"; + +const char CONNECT[] = "connect"; +const char INTERRUPT[] = "interrupt"; + +const char REQUEST[] = "request"; +const char IN[] = "in"; +const char NEXT[] = "next"; +const char OUT[] = "out"; + +const char FUNCTION[] = "function"; +const char SCRIPT[] = "script"; +const char EVENT[] = "event"; + +const char ALL[] = "all"; +const char UNCAUGHT[] = "uncaught"; + +//const char PAUSE[] = "pause"; +//const char RESUME[] = "resume"; + +const char HANDLE[] = "handle"; +const char REF[] = "ref"; +const char REFS[] = "refs"; +const char BODY[] = "body"; +const char NAME[] = "name"; +const char VALUE[] = "value"; + +const char OBJECT[] = "{}"; +const char ARRAY[] = "[]"; + +} //Internal +} //Debugger +#endif // QMLV8DEBUGGERCLIENTCONSTANTS_H diff --git a/src/plugins/debugger/qml/qscriptdebuggerclient.cpp b/src/plugins/debugger/qml/qscriptdebuggerclient.cpp index 0cf2a165d1..584b891a0d 100644 --- a/src/plugins/debugger/qml/qscriptdebuggerclient.cpp +++ b/src/plugins/debugger/qml/qscriptdebuggerclient.cpp @@ -249,7 +249,7 @@ void QScriptDebuggerClient::changeBreakpoint(const BreakpointModelId &id) handler->setResponse(id, br); } -void QScriptDebuggerClient::updateBreakpoints() +void QScriptDebuggerClient::synchronizeBreakpoints() { QByteArray reply; QDataStream rs(&reply, QIODevice::WriteOnly); @@ -270,13 +270,13 @@ void QScriptDebuggerClient::assignValueInDebugger(const QByteArray expr, const q sendMessage(reply); } -void QScriptDebuggerClient::updateWatchData(const WatchData *data) +void QScriptDebuggerClient::updateWatchData(const WatchData &data) { QByteArray reply; QDataStream rs(&reply, QIODevice::WriteOnly); QByteArray cmd = "EXEC"; rs << cmd; - rs << data->iname << data->name; + rs << data.iname << data.name; sendMessage(reply); } diff --git a/src/plugins/debugger/qml/qscriptdebuggerclient.h b/src/plugins/debugger/qml/qscriptdebuggerclient.h index 698e0ac2f6..fe8b539db2 100644 --- a/src/plugins/debugger/qml/qscriptdebuggerclient.h +++ b/src/plugins/debugger/qml/qscriptdebuggerclient.h @@ -67,12 +67,12 @@ public: void insertBreakpoint(const BreakpointModelId &id); void removeBreakpoint(const BreakpointModelId &id); void changeBreakpoint(const BreakpointModelId &id); - void updateBreakpoints(); + void synchronizeBreakpoints(); void assignValueInDebugger(const QByteArray expr, const quint64 &id, const QString &property, const QString &value); - void updateWatchData(const WatchData *data); + void updateWatchData(const WatchData &data); void executeDebuggerCommand(const QString &command); void synchronizeWatchers(const QStringList &watchers); @@ -81,9 +81,6 @@ public: void setEngine(QmlEngine *engine); -signals: - void notifyDebuggerStopped(); - protected: void messageReceived(const QByteArray &data); diff --git a/src/plugins/debugger/qml/scriptconsole.cpp b/src/plugins/debugger/qml/scriptconsole.cpp deleted file mode 100644 index 124d3aa2ae..0000000000 --- a/src/plugins/debugger/qml/scriptconsole.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this file. -** Please review the following information to ensure the GNU Lesser General -** Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -**************************************************************************/ - -#include "scriptconsole.h" - -#include <QtCore/QDebug> -#include <QtGui/QVBoxLayout> -#include <QtGui/QDockWidget> -#include <qmljseditor/qmljshighlighter.h> -#include <utils/styledbar.h> -#include <utils/filterlineedit.h> -#include <texteditor/fontsettings.h> -#include <texteditor/texteditorconstants.h> -#include <texteditor/texteditorsettings.h> - -namespace Debugger { -namespace Internal { - -ScriptConsole::ScriptConsole(QWidget *parent) - : QWidget(parent), - m_textEdit(new QPlainTextEdit), - m_lineEdit(0) -{ - -// m_prompt = QLatin1String(">"); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(m_textEdit); - m_textEdit->setFrameStyle(QFrame::NoFrame); - - //updateTitle(); - - /*m_highlighter = new QmlJSEditor::Highlighter(m_textEdit->document()); - m_highlighter->setParent(m_textEdit->document());*/ - - Utils::StyledBar *bar = new Utils::StyledBar; - m_lineEdit = new Utils::FilterLineEdit; - - m_lineEdit->setPlaceholderText(tr("<Type expression to evaluate>")); - m_lineEdit->setToolTip(tr("Write and evaluate QtScript expressions.")); - - /*m_clearButton = new QToolButton(); - m_clearButton->setToolTip(tr("Clear Output")); - m_clearButton->setIcon(QIcon(Core::Constants::ICON_CLEAN_PANE)); - connect(m_clearButton, SIGNAL(clicked()), this, SLOT(clearTextEditor()));*/ - //connect(m_lineEdit, SIGNAL(textChanged(QString)), SLOT(changeContextHelpId(QString))); - - connect(m_lineEdit, SIGNAL(returnPressed()), SLOT(executeExpression())); - QHBoxLayout *hbox = new QHBoxLayout(bar); - hbox->setMargin(1); - hbox->setSpacing(1); - hbox->addWidget(m_lineEdit); - //hbox->addWidget(m_clearButton); - layout->addWidget(bar); - - m_textEdit->setReadOnly(true); - m_lineEdit->installEventFilter(this); - - setFontSettings(); -} - - -void ScriptConsole::setFontSettings() -{ - const TextEditor::FontSettings &fs = TextEditor::TextEditorSettings::instance()->fontSettings(); - static QVector<QString> categories; - if (categories.isEmpty()) { - categories << QLatin1String(TextEditor::Constants::C_NUMBER) - << QLatin1String(TextEditor::Constants::C_STRING) - << QLatin1String(TextEditor::Constants::C_TYPE) - << QLatin1String(TextEditor::Constants::C_KEYWORD) - << QLatin1String(TextEditor::Constants::C_LABEL) - << QLatin1String(TextEditor::Constants::C_COMMENT) - << QLatin1String(TextEditor::Constants::C_VISUAL_WHITESPACE); - } - - const QVector<QTextCharFormat> formats = fs.toTextCharFormats(categories); -/* m_highlighter->setFormats(formats); - m_highlighter->rehighlight();*/ - m_textEdit->setFont(fs.font()); - m_lineEdit->setFont(fs.font()); -} - - -void ScriptConsole::clear() -{ - clearTextEditor(); - - if (m_lineEdit) - m_lineEdit->clear(); -// appendPrompt(); -} - -void ScriptConsole::clearTextEditor() -{ - m_textEdit->clear(); - m_textEdit->appendPlainText(tr("Script Console\n")); -} - - -/*void ExpressionQueryWidget::updateTitle() -{ - if (m_currObject.debugId() < 0) { - m_title = tr("Expression queries"); - } else { - QString desc = QLatin1String("<") - + m_currObject.className() + QLatin1String(": ") - + (m_currObject.name().isEmpty() ? QLatin1String("<unnamed>") : m_currObject.name()) - + QLatin1String(">"); - m_title = tr("Expression queries (using context for %1)" , "Selected object").arg(desc); - } -}*/ -/* -void ExpressionQueryWidget::appendPrompt() -{ - m_textEdit->moveCursor(QTextCursor::End); - - if (m_mode == SeparateEntryMode) { - m_textEdit->insertPlainText("\n"); - } else { - m_textEdit->appendPlainText(m_prompt); - } -} -*/ - - - -bool ScriptConsole::eventFilter(QObject* obj, QEvent* event) -{ - if (obj == m_textEdit) { - switch (event->type()) { - case QEvent::KeyPress: - { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); - int key = keyEvent->key(); - if (key == Qt::Key_Return || key == Qt::Key_Enter) { - executeExpression(); - return true; - } else if (key == Qt::Key_Backspace) { - // ensure m_expr doesn't contain backspace characters - QTextCursor cursor = m_textEdit->textCursor(); - bool atLastLine = !(cursor.block().next().isValid()); - if (!atLastLine) - return true; - if (cursor.positionInBlock() <= m_prompt.count()) - return true; - cursor.deletePreviousChar(); - m_expr = cursor.block().text().mid(m_prompt.count()); - return true; - } else { - m_textEdit->moveCursor(QTextCursor::End); - m_expr += keyEvent->text(); - } - break; - } - case QEvent::FocusIn: - //checkCurrentContext(); - m_textEdit->moveCursor(QTextCursor::End); - break; - default: - break; - } - } else if (obj == m_lineEdit) { - switch (event->type()) { - case QEvent::KeyPress: - { - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); - int key = keyEvent->key(); - if (key == Qt::Key_Up && m_lineEdit->text() != m_lastExpr) { - m_expr = m_lineEdit->text(); - if (!m_lastExpr.isEmpty()) - m_lineEdit->setText(m_lastExpr); - } else if (key == Qt::Key_Down) { - m_lineEdit->setText(m_expr); - } - break; - } - case QEvent::FocusIn: - // checkCurrentContext(); - break; - default: - break; - } - } - return QWidget::eventFilter(obj, event); -} - -void ScriptConsole::executeExpression() -{ - m_expr = m_lineEdit->text().trimmed(); - m_expr = m_expr.trimmed(); - if (!m_expr.isEmpty()) { - emit expressionEntered(m_expr); - m_lastExpr = m_expr; - if (m_lineEdit) - m_lineEdit->clear(); - } -} - -void ScriptConsole::appendResult(const QString& result) -{ - m_textEdit->moveCursor(QTextCursor::End); - m_textEdit->insertPlainText(m_expr + " : "); - m_textEdit->insertPlainText(result); - m_textEdit->insertPlainText("\n"); - m_expr.clear(); -} - -} -} |