From 06723f8df97cd975509ad5c0f768513b7f24b56c Mon Sep 17 00:00:00 2001
From: Aurindam Jana <aurindam.jana@nokia.com>
Date: Tue, 6 Sep 2011 17:03:16 +0200
Subject: [PATCH] JSDebugger: Break on Exception

The debugger breaks on Javascript exception. The error message is
printed on the ScriptConsole and the relevant code is marked with
a wavy underline.

Change-Id: I5e6f603430c3b8a0db450d1e8c821714ec0140ab
Reviewed-on: http://codereview.qt-project.org/4276
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Leandro T. C. Melo <leandro.melo@nokia.com>
Reviewed-by: Kai Koehne <kai.koehne@nokia.com>
---
 .../debugger/qml/qmlv8debuggerclient.cpp      | 185 ++++++++++++++----
 .../debugger/qml/qmlv8debuggerclient.h        |  34 +++-
 src/plugins/texteditor/basetexteditor.h       |   1 +
 3 files changed, 171 insertions(+), 49 deletions(-)

diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp
index 692c080d049..5f7b11f45ef 100644
--- a/src/plugins/debugger/qml/qmlv8debuggerclient.cpp
+++ b/src/plugins/debugger/qml/qmlv8debuggerclient.cpp
@@ -44,6 +44,10 @@
 #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/QFileInfo>
 #include <QtGui/QTextDocument>
@@ -51,20 +55,31 @@
 
 #define INITIALPARAMS "seq" << ':' << ++d->sequence << ',' << "type" << ':' << "request"
 
+using namespace Core;
 using namespace Json;
 
 namespace Debugger {
 namespace Internal {
 
+struct ExceptionInfo
+{
+    int sourceLine;
+    QString filePath;
+    QString errorMessage;
+};
+
 class QmlV8DebuggerClientPrivate
 {
 public:
     explicit QmlV8DebuggerClientPrivate(QmlV8DebuggerClient *) :
-        sequence(0), ping(0), engine(0)
+        handleException(false),
+        sequence(0),
+        ping(0),
+        engine(0)
     {
-
     }
 
+    bool handleException;
     int sequence;
     int ping;
     QmlEngine *engine;
@@ -73,6 +88,7 @@ public:
     QHash<int,QByteArray> locals;
     QHash<int,QByteArray> watches;
     QByteArray frames;
+    QScopedPointer<ExceptionInfo> exceptionInfo;
 };
 
 QmlV8DebuggerClient::QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection* client)
@@ -86,7 +102,7 @@ QmlV8DebuggerClient::~QmlV8DebuggerClient()
     delete d;
 }
 
-QByteArray QmlV8DebuggerClient::packMessage(QByteArray& message)
+QByteArray QmlV8DebuggerClient::packMessage(const QByteArray &message)
 {
     QByteArray reply;
     QDataStream rs(&reply, QIODevice::WriteOnly);
@@ -95,15 +111,21 @@ QByteArray QmlV8DebuggerClient::packMessage(QByteArray& message)
     return reply;
 }
 
-void QmlV8DebuggerClient::executeStep()
+void QmlV8DebuggerClient::breakOnException(Exceptions exceptionsType, bool enabled)
 {
+    //TODO: Have to deal with NoExceptions
     QByteArray request;
 
     JsonInputStream(request) << '{' << INITIALPARAMS ;
-    JsonInputStream(request) << ',' << "command" << ':' << "continue";
+    JsonInputStream(request) << ',' << "command" << ':' << "setexceptionbreak";
 
     JsonInputStream(request) << ',' << "arguments" << ':';
-    JsonInputStream(request) << '{' << "stepaction" << ':' << "in";
+    if (exceptionsType == AllExceptions)
+        JsonInputStream(request) << '{' << "type" << ':' << "all";
+    else if (exceptionsType == UncaughtExceptions)
+        JsonInputStream(request) << '{' << "type" << ':' << "uncaught";
+
+    JsonInputStream(request) << ',' << "enabled" << ':' << enabled;
     JsonInputStream(request) << '}';
 
     JsonInputStream(request) << '}';
@@ -112,71 +134,134 @@ void QmlV8DebuggerClient::executeStep()
     sendMessage(packMessage(request));
 }
 
-void QmlV8DebuggerClient::executeStepOut()
+void QmlV8DebuggerClient::storeExceptionInformation(const QByteArray &message)
 {
-    QByteArray request;
+    JsonValue response(message);
 
-    JsonInputStream(request) << '{' << INITIALPARAMS ;
-    JsonInputStream(request) << ',' << "command" << ':' << "continue";
+    JsonValue body = response.findChild("body");
 
-    JsonInputStream(request) << ',' << "arguments" << ':';
-    JsonInputStream(request) << '{' << "stepaction" << ':' << "out";
-    JsonInputStream(request) << '}';
+    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();
+}
 
-    JsonInputStream(request) << '}';
+void QmlV8DebuggerClient::handleException()
+{
+    EditorManager *editorManager = EditorManager::instance();
+    QList<IEditor *> openedEditors = editorManager->openedEditors();
 
+    // set up the format for the errors
+    QTextCharFormat errorFormat;
+    errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline);
+    errorFormat.setUnderlineColor(Qt::red);
 
-    sendMessage(packMessage(request));
+    foreach (IEditor *editor, openedEditors) {
+        if (editor->file()->fileName() == d->exceptionInfo->filePath) {
+            TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget());
+            if (!ed)
+                continue;
 
-}
+            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;
 
-void QmlV8DebuggerClient::executeNext()
-{
-    QByteArray request;
+            sel.format.setToolTip(d->exceptionInfo->errorMessage);
 
-    JsonInputStream(request) << '{' << INITIALPARAMS ;
-    JsonInputStream(request) << ',' << "command" << ':' << "continue";
+            selections.append(sel);
+            ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections);
 
-    JsonInputStream(request) << ',' << "arguments" << ':';
-    JsonInputStream(request) << '{' << "stepaction" << ':' << "next";
-    JsonInputStream(request) << '}';
+            d->engine->showMessage(d->exceptionInfo->errorMessage, ScriptConsoleOutput);
+        }
+    }
 
-    JsonInputStream(request) << '}';
+    //Delete the info even if the code hasnt been highlighted
+    d->exceptionInfo.reset();
+}
 
+void QmlV8DebuggerClient::clearExceptionSelection()
+{
+    //Check if break was due to exception
+    if (d->handleException) {
+        EditorManager *editorManager = EditorManager::instance();
+        QList<IEditor *> openedEditors = editorManager->openedEditors();
+        QList<QTextEdit::ExtraSelection> selections;
 
-    sendMessage(packMessage(request));
+        foreach (IEditor *editor, openedEditors) {
+            TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget());
+            if (!ed)
+                continue;
 
+            ed->setExtraSelections(TextEditor::BaseTextEditorWidget::DebuggerExceptionSelection, selections);
+        }
+        d->handleException = false;
+    }
 }
 
-void QmlV8DebuggerClient::executeStepI()
+void QmlV8DebuggerClient::continueDebugging(StepAction type)
 {
+    clearExceptionSelection();
+
     QByteArray request;
 
     JsonInputStream(request) << '{' << INITIALPARAMS ;
     JsonInputStream(request) << ',' << "command" << ':' << "continue";
 
-    JsonInputStream(request) << ',' << "arguments" << ':';
-    JsonInputStream(request) << '{' << "stepaction" << ':' << "in";
-    JsonInputStream(request) << '}';
+    if (type != Continue) {
+        JsonInputStream(request) << ',' << "arguments" << ':';
+
+        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;
+        }
 
-    JsonInputStream(request) << '}';
+        JsonInputStream(request) << '}';
+    }
 
+    JsonInputStream(request) << '}';
 
     sendMessage(packMessage(request));
-
 }
 
-void QmlV8DebuggerClient::continueInferior()
+void QmlV8DebuggerClient::executeStep()
 {
-    QByteArray request;
+    continueDebugging(In);
+}
 
-    JsonInputStream(request) << '{' << INITIALPARAMS ;
-    JsonInputStream(request) << ',' << "command" << ':' << "continue";
-    JsonInputStream(request) << '}';
+void QmlV8DebuggerClient::executeStepOut()
+{
+    continueDebugging(Out);
+}
 
+void QmlV8DebuggerClient::executeNext()
+{
+    continueDebugging(Next);
+}
 
-    sendMessage(packMessage(request));
+void QmlV8DebuggerClient::executeStepI()
+{
+    continueDebugging(In);
+}
 
+void QmlV8DebuggerClient::continueInferior()
+{
+    continueDebugging(Continue);
 }
 
 void QmlV8DebuggerClient::interruptInferior()
@@ -194,6 +279,10 @@ void QmlV8DebuggerClient::interruptInferior()
 
 void QmlV8DebuggerClient::startSession()
 {
+    //Set up Exception Handling first
+    //TODO: For now we enable breaks for all exceptions
+    breakOnException(AllExceptions, true);
+
     QByteArray request;
 
     JsonInputStream(request) << '{' << INITIALPARAMS ;
@@ -206,6 +295,8 @@ void QmlV8DebuggerClient::startSession()
 
 void QmlV8DebuggerClient::endSession()
 {
+    clearExceptionSelection();
+
     QByteArray request;
 
     JsonInputStream(request) << '{' << INITIALPARAMS ;
@@ -418,12 +509,17 @@ void QmlV8DebuggerClient::messageReceived(const QByteArray &data)
             if (event == "break") {
                 d->engine->inferiorSpontaneousStop();
                 listBreakpoints();
+            } else if (event == "exception") {
+                d->handleException = true;
+                d->engine->inferiorSpontaneousStop();
+                storeExceptionInformation(response);
+                backtrace();
             }
         }
     }
 }
 
-void QmlV8DebuggerClient::setStackFrames(QByteArray &message)
+void QmlV8DebuggerClient::setStackFrames(const QByteArray &message)
 {
     d->frames = message;
     JsonValue response(message);
@@ -468,6 +564,9 @@ void QmlV8DebuggerClient::setStackFrames(QByteArray &message)
         d->engine->gotoLocation(ideStackFrames.value(0));
     }
 
+    if (d->handleException) {
+        handleException();
+    }
 }
 
 void QmlV8DebuggerClient::setLocals(int frameIndex)
@@ -533,7 +632,7 @@ void QmlV8DebuggerClient::setLocals(int frameIndex)
     }
 }
 
-void QmlV8DebuggerClient::expandLocal(QByteArray &message)
+void QmlV8DebuggerClient::expandLocal(const QByteArray &message)
 {
     JsonValue response(message);
 
@@ -553,7 +652,7 @@ void QmlV8DebuggerClient::expandLocal(QByteArray &message)
     }
 }
 
-void QmlV8DebuggerClient::setExpression(QByteArray &message)
+void QmlV8DebuggerClient::setExpression(const QByteArray &message)
 {
     JsonValue response(message);
     JsonValue body = response.findChild("body");
@@ -569,7 +668,7 @@ void QmlV8DebuggerClient::setExpression(QByteArray &message)
     //TODO: For watch point
 }
 
-void QmlV8DebuggerClient::updateBreakpoints(QByteArray &message)
+void QmlV8DebuggerClient::updateBreakpoints(const QByteArray &message)
 {
     JsonValue response(message);
 
@@ -600,7 +699,7 @@ void QmlV8DebuggerClient::updateBreakpoints(QByteArray &message)
     }
 }
 
-void QmlV8DebuggerClient::setPropertyValue(JsonValue &refs, JsonValue &property, QByteArray &prepend)
+void QmlV8DebuggerClient::setPropertyValue(const JsonValue &refs, const JsonValue &property, const QByteArray &prepend)
 {
     WatchData data;
     data.exp = property.findChild("name").toVariant().toByteArray();
diff --git a/src/plugins/debugger/qml/qmlv8debuggerclient.h b/src/plugins/debugger/qml/qmlv8debuggerclient.h
index 04aeec17627..17165d879a1 100644
--- a/src/plugins/debugger/qml/qmlv8debuggerclient.h
+++ b/src/plugins/debugger/qml/qmlv8debuggerclient.h
@@ -48,6 +48,21 @@ class QmlV8DebuggerClient : public QmlDebuggerClient
 {
     Q_OBJECT
 
+    enum Exceptions
+    {
+        NoExceptions,
+        UncaughtExceptions,
+        AllExceptions
+    };
+
+    enum StepAction
+    {
+        Continue,
+        In,
+        Out,
+        Next
+    };
+
 public:
     explicit QmlV8DebuggerClient(QmlJsDebugClient::QDeclarativeDebugConnection *client);
     ~QmlV8DebuggerClient();
@@ -91,14 +106,21 @@ protected:
 private:
     void listBreakpoints();
     void backtrace();
-    void setStackFrames(QByteArray &);
+    void setStackFrames(const QByteArray &message);
     void setLocals(int frameIndex);
-    void setExpression(QByteArray &message);
-    void updateBreakpoints(QByteArray &message);
-    void expandLocal(QByteArray &message);
-    void setPropertyValue(Json::JsonValue &refs, Json::JsonValue &property, QByteArray &prepend);
+    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(QByteArray& message);
+    QByteArray packMessage(const QByteArray &message);
+
+    void breakOnException(Exceptions exceptionsType, bool enabled);
+    void storeExceptionInformation(const QByteArray &message);
+    void handleException();
+    void clearExceptionSelection();
+
+    void continueDebugging(StepAction type);
 
 private:
     QmlV8DebuggerClientPrivate *d;
diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h
index e93f99cc84e..4820729abc0 100644
--- a/src/plugins/texteditor/basetexteditor.h
+++ b/src/plugins/texteditor/basetexteditor.h
@@ -398,6 +398,7 @@ public:
         OtherSelection,
         SnippetPlaceholderSelection,
         ObjCSelection,
+        DebuggerExceptionSelection,
         NExtraSelectionKinds
     };
     void setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections);
-- 
GitLab