Add formatted diagnostic output to NotebookCell
diff --git a/PPrint.cpp b/PPrint.cpp
index de43582..c5ad222 100644
--- a/PPrint.cpp
+++ b/PPrint.cpp
@@ -7,22 +7,35 @@
     QTextStream(stdout) << string << '\n';
 }
 
-QString pprint(ParseResult val, const Parser &parser)
+QString pprint(ParseResult val, const Parser &parser, PPrint::Style style)
 {
     if (val)
     {
         return "\033[32mOK\033[0m";
     }
 
-    QString highlighted = parser.line(val.pos().line - 1) + "\n";
+    QString newline = (style == PPrint::ANSI ? "\n" : "<br>");
+
+    QString highlighted = parser.line(val.pos().line - 1) + newline;
 
     for (int i = 1; i < val.pos().lineOffset; i++)
     {
-        highlighted += " ";
+        if (style == PPrint::ANSI)
+            highlighted += " ";
+        else
+            highlighted += "&nbsp;";
     }
-    highlighted += "\033[31m^~~\033[0m";
 
-    return val.message() + " at " + val.pos() + "\n" + highlighted;
+    if (style == PPrint::ANSI)
+    {
+        highlighted += "\033[31m^~~\033[0m";
+    }
+    else if (style == PPrint::HTML)
+    {
+        highlighted += "<font color=\"red\">^~~</font>";
+    }
+
+    return val.message() + " at " + val.pos() + newline + highlighted;
 }
 
 void eout(QString string)
diff --git a/PPrint.h b/PPrint.h
index 3ae9546..09327af 100644
--- a/PPrint.h
+++ b/PPrint.h
@@ -54,7 +54,17 @@
 	return static_cast<QString>(val);
 }
 
-QString pprint(ParseResult val, const Parser &parser);
+class PPrint
+{
+public:
+    enum Style
+    {
+        ANSI,
+        HTML
+    };
+};
+
+QString pprint(ParseResult val, const Parser &parser, PPrint::Style style = PPrint::ANSI);
 
 void sout(QString string);
 void eout(QString string);
diff --git a/ide/Cell.cpp b/ide/Cell.cpp
index c49466d..bf87abb 100644
--- a/ide/Cell.cpp
+++ b/ide/Cell.cpp
@@ -53,6 +53,11 @@
     return _status;
 }
 
+int Cell::resultType() const
+{
+    return _resultType;
+}
+
 void Cell::setCode(QString code)
 {
     _code = code;
@@ -71,6 +76,12 @@
     emit statusChanged(status);
 }
 
+void Cell::setResultType(int resultType)
+{
+    _resultType = resultType;
+    emit resultTypeChanged(resultType);
+}
+
 Cell *Cell::cellFromUuid(QUuid uuid)
 {
     if (_cellUuids.contains(uuid))
diff --git a/ide/Cell.h b/ide/Cell.h
index fb5bc42..6085b81 100644
--- a/ide/Cell.h
+++ b/ide/Cell.h
@@ -14,6 +14,7 @@
     Q_PROPERTY(QString result READ result WRITE setResult NOTIFY resultChanged)
     Q_PROPERTY(QUuid uuid READ uuid NOTIFY uuidChanged)
     Q_PROPERTY(int status READ status WRITE setStatus NOTIFY statusChanged)
+    Q_PROPERTY(int resultType READ resultType WRITE setResultType NOTIFY resultTypeChanged)
 
 public:
     ~Cell();
@@ -27,10 +28,12 @@
     QString result() const;
     QUuid uuid() const;
     int status() const;
+    int resultType() const;
 
     void setCode(QString code);
     void setResult(QString result);
     void setStatus(int status);
+    void setResultType(int resultType);
 
     Q_INVOKABLE static Cell *cellFromUuid(QUuid uuid);
 
@@ -43,14 +46,23 @@
 
     Q_ENUM(Status);
 
+    enum ResultType
+    {
+        EXPRESSION,
+        DIAGNOSTIC
+    };
+
+    Q_ENUM(ResultType)
+
 signals:
     void codeChanged(QString code);
     void resultChanged(QString result);
     void uuidChanged(QUuid uuid);
     void statusChanged(int status);
+    void resultTypeChanged(int resultType);
 
 private:
-    int _status = IDLE;
+    int _status = IDLE, _resultType = EXPRESSION;
     QString _code, _result;
     QUuid _uuid = QUuid::createUuid();
 
diff --git a/ide/CellModel.cpp b/ide/CellModel.cpp
index 8ed684f..167a2af 100644
--- a/ide/CellModel.cpp
+++ b/ide/CellModel.cpp
@@ -37,6 +37,8 @@
         return _notebook->_cells[index.row()]->uuid();
     case StatusRole:
         return _notebook->_cells[index.row()]->status();
+    case ResultTypeRole:
+        return _notebook->_cells[index.row()]->resultType();
     default:
         return QVariant();
     }
@@ -57,6 +59,9 @@
         case StatusRole:
             _notebook->_cells[index.row()]->setStatus(value.toInt());
             break;
+        case ResultTypeRole:
+            _notebook->_cells[index.row()]->setResultType(value.toInt());
+            break;
         default:
             return false;
         }
@@ -100,6 +105,11 @@
             announceCellChange(cell, StatusRole);
         });
 
+        connect(cell, &Cell::resultTypeChanged, this, [this, cell](int)
+        {
+            announceCellChange(cell, ResultTypeRole);
+        });
+
         _notebook->_cells.insert(row, cell);
     }
 
@@ -130,6 +140,7 @@
         {ResultRole, "result"},
         {UuidRole, "uuid"},
         {StatusRole, "status"},
+        {ResultTypeRole, "resultType"},
     };
 }
 
diff --git a/ide/CellModel.h b/ide/CellModel.h
index 46f3628..345454a 100644
--- a/ide/CellModel.h
+++ b/ide/CellModel.h
@@ -21,6 +21,7 @@
         ResultRole,
         UuidRole,
         StatusRole,
+        ResultTypeRole,
     };
 
     // Basic functionality:
diff --git a/ide/NbRuntime.cpp b/ide/NbRuntime.cpp
index eeb9d0d..a2111a7 100644
--- a/ide/NbRuntime.cpp
+++ b/ide/NbRuntime.cpp
@@ -73,7 +73,7 @@
                 }
                 else if (ret.status() == ParseResult::INCOMPLETE)
                 {
-                    emit cellFailedToParse(cell, ret);
+                    emit cellFailedToParse(cell, ret, parser);
                     goto endOfCell; // JANK!
                 }
                 else if ((ret = parser.parseMany(&ast)) && !ast.empty())
@@ -91,7 +91,7 @@
                 }
                 else if (ret.status() == ParseResult::INCOMPLETE)
                 {
-                    emit cellFailedToParse(cell, ret);
+                    emit cellFailedToParse(cell, ret, parser);
                     break;
                 }
                 else
@@ -100,7 +100,7 @@
 
                     if (!parser.atEnd())
                     {
-                        emit cellFailedToParse(cell, ParseResult(ParseResult::NO_MATCH, "Garbage at end of input", parser.save()));
+                        emit cellFailedToParse(cell, ParseResult(ParseResult::NO_MATCH, "Garbage at end of input", parser.save()), parser);
                         goto endOfCell;
                     }
 
diff --git a/ide/NbRuntime.h b/ide/NbRuntime.h
index 9d4c714..e7a4b3d 100644
--- a/ide/NbRuntime.h
+++ b/ide/NbRuntime.h
@@ -22,7 +22,7 @@
 
 signals:
     void cellFinishedRunning(Cell *cell, RuntimeResult result);
-    void cellFailedToParse(Cell *cell, ParseResult result);
+    void cellFailedToParse(Cell *cell, ParseResult result, Parser parser);
     void cellWaiting(Cell *cell);
     void cellRunning(Cell *cell);
     void cellQuit(Cell *cell);
diff --git a/ide/Notebook.cpp b/ide/Notebook.cpp
index 3396779..97df181 100644
--- a/ide/Notebook.cpp
+++ b/ide/Notebook.cpp
@@ -8,13 +8,15 @@
 {
     _rtThread->quit();
     _rtThread->wait();
+
+    delete _rt;
 }
 
 Notebook::Notebook(QObject *parent)
     : QObject(parent)
     , _cellModel(new CellModel(this))
-    , _rt(new NbRuntime(this))
     , _rtThread(new QThread(this))
+    , _rt(new NbRuntime)
 {
     connect(_rt, &NbRuntime::cellFailedToParse, this, &Notebook::cellFailedToParse);
     connect(_rt, &NbRuntime::cellFinishedRunning, this, &Notebook::cellFinishedRunning);
@@ -56,13 +58,15 @@
     qInfo() << "cellFinishedRunning" << cell->uuid() << pprint(result);
     cell->setResult(pprint(result));
     cell->setStatus(Cell::IDLE);
+    cell->setResultType(Cell::EXPRESSION);
 }
 
-void Notebook::cellFailedToParse(Cell *cell, ParseResult result)
+void Notebook::cellFailedToParse(Cell *cell, ParseResult result, Parser parser)
 {
-    qInfo() << "cellFailedToParse" << cell->uuid() << pprint(result);
-    cell->setResult(pprint(result));
+    qInfo() << "cellFailedToParse" << cell->uuid() << pprint(result, parser);
+    cell->setResult(pprint(result, parser, PPrint::HTML));
     cell->setStatus(Cell::IDLE);
+    cell->setResultType(Cell::DIAGNOSTIC);
 }
 
 void Notebook::cellWaiting(Cell *cell)
diff --git a/ide/Notebook.h b/ide/Notebook.h
index 0dbe21e..1a0206e 100644
--- a/ide/Notebook.h
+++ b/ide/Notebook.h
@@ -28,7 +28,7 @@
 
 protected slots:
     void cellFinishedRunning(Cell *cell, RuntimeResult result);
-    void cellFailedToParse(Cell *cell, ParseResult result);
+    void cellFailedToParse(Cell *cell, ParseResult result, Parser parser);
     void cellWaiting(Cell *cell);
     void cellRunning(Cell *cell);
     void cellQuit(Cell *cell);
@@ -38,8 +38,8 @@
 
     QList<Cell *> _cells;
     CellModel *_cellModel;
-    NbRuntime *_rt;
     QThread *_rtThread;
+    NbRuntime *_rt;
 };
 
 Q_DECLARE_METATYPE(Notebook)
diff --git a/ide/qml/Constants.qml b/ide/qml/Constants.qml
index a6d02fb..5153b39 100644
--- a/ide/qml/Constants.qml
+++ b/ide/qml/Constants.qml
@@ -1,6 +1,6 @@
 pragma Singleton
 
-import QtQuick 2.0
+import QtQuick 2.5
 import QtQuick.Controls.Material 2.0
 
 QtObject {
diff --git a/ide/qml/InsertRow.qml b/ide/qml/InsertRow.qml
index ee45430..5f69503 100644
--- a/ide/qml/InsertRow.qml
+++ b/ide/qml/InsertRow.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick 2.5
 import QtQuick.Controls 2.15
 import QtQuick.Controls.Material 2.0
 import QtQuick.Layouts 1.0
diff --git a/ide/qml/NotebookCell.qml b/ide/qml/NotebookCell.qml
index 3c59393..24eaf17 100644
--- a/ide/qml/NotebookCell.qml
+++ b/ide/qml/NotebookCell.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick 2.5
 import QtQuick.Controls 2.15
 import QtQuick.Controls.Material 2.0
 import QtQuick.Layouts 1.0
@@ -11,6 +11,7 @@
     required property string code
     required property string result
     property int status: Cell.IDLE
+    property int resultType: Cell.EXPRESSION
     property bool cellActive: false
 
     signal insertBelowClicked()
@@ -97,14 +98,12 @@
                             cursorPosition = pos
                         }
 
-//                        Keys.onEscapePressed: {
-//                            focus = false
-//                        }
-
-                        onEditingFinished: {
+                        onTextChanged: {
                             root.codeEditingFinished(text)
                         }
 
+                        onPressed: root.cellFocused()
+
                         onFocusChanged: if (focus) root.cellFocused()
                         onActiveFocusChanged: if (activeFocus) root.cellFocused()
                     }
@@ -114,6 +113,7 @@
                         Layout.fillWidth: true
                         font.family: "monospace"
                         text: root.result
+                        textFormat: root.resultType === Cell.EXPRESSION ? Text.PlainText : Text.RichText
 
                         Layout.bottomMargin: 5
                     }
diff --git a/ide/qml/main.qml b/ide/qml/main.qml
index 63eb004..2439e6c 100644
--- a/ide/qml/main.qml
+++ b/ide/qml/main.qml
@@ -15,6 +15,27 @@
     Material.theme: Material.Light
     Material.accent: Material.Orange
 
+    menuBar: MenuBar {
+        Menu {
+            title: qsTr("&File")
+        }
+
+        Menu {
+            title: qsTr("&Runtime")
+
+            Action {
+                text: qsTr("Run &Selected Cell")
+                shortcut: "Ctrl+Return"
+
+                onTriggered: {
+                    if (codeEditor.currentItem !== null) {
+                        notebook.runCell(codeEditor.currentItem.uuid)
+                    }
+                }
+            }
+        }
+    }
+
     Notebook {
         id: notebook
     }
@@ -100,6 +121,7 @@
                     code: model.code
                     result: model.result.trim()
                     status: model.status
+                    resultType: model.resultType
                     cellActive: codeEditor.currentIndex === index
 
                     onCodeEditingFinished: code => model.code = code
diff --git a/ide/qml/qtquickcontrols2.conf b/ide/qml/qtquickcontrols2.conf
deleted file mode 100644
index 433a54e..0000000
--- a/ide/qml/qtquickcontrols2.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-[Controls]
-Style=Material
-
-[Material]
-Variant=Dense
diff --git a/ide/resources.qrc b/ide/resources.qrc
index 07d0bc0..c4b120e 100644
--- a/ide/resources.qrc
+++ b/ide/resources.qrc
@@ -10,6 +10,5 @@
         <file>icons/menu.svg</file>
         <file>qml/Constants.qml</file>
         <file>qml/qmldir</file>
-        <file>qml/qtquickcontrols2.conf</file>
     </qresource>
 </RCC>