Add evaluator
diff --git a/Evaluator.cpp b/Evaluator.cpp
index 5177fe5..56685ec 100644
--- a/Evaluator.cpp
+++ b/Evaluator.cpp
@@ -5,6 +5,7 @@
 #include "PPrint.h"
 
 #include <QDebug>
+#include <QCoreApplication>
 
 RuntimeResult::RuntimeResult(QList<Token> result)
 {
@@ -14,7 +15,41 @@
 RuntimeResult::RuntimeResult(QString message)
 {
 	_errorMessage = message;
-	_success = false;
+    _success = false;
+}
+
+RuntimeResult &RuntimeResult::operator =(const RuntimeResult &other)
+{
+    _errorMessage = other._errorMessage;
+    _success = other._success;
+    _result = other._result;
+
+    return *this;
+}
+
+RuntimeResult RuntimeResult::operator +(const RuntimeResult &other)
+{
+    RuntimeResult res;
+
+    if (_success)
+    {
+        res._success = other._success;
+        res._result = _result;
+        res._result.append(other._result);
+        res._errorMessage = other._errorMessage;
+    }
+    else
+    {
+        res = *this;
+    }
+
+    return res;
+}
+
+RuntimeResult &RuntimeResult::operator +=(const RuntimeResult &other)
+{
+    *this = *this + other;
+    return *this;
 }
 
 bool RuntimeResult::success() const
@@ -34,7 +69,7 @@
 
 RuntimeResult::operator QString() const
 {
-	return QString(_success) + " " + _errorMessage;
+    return _errorMessage + pprint(_result);
 }
 
 Evaluator::Evaluator()
@@ -73,6 +108,19 @@
         return copy(name.name());
     });
     addFunction(copyFn);
+
+    Function undefFn("Undef");
+    undefFn.addNativeSentence("s.Name", [this](VarContext args)
+    {
+        Token name = args.singleVar("Name");
+        if (name.type() != Token::IDENT)
+            rtError("Invalid argument", "First argument to <Undef> must be an identifier, received " + pprint(name));
+
+        clearFunction(name.name());
+
+        return QList<Token>();
+    });
+    addFunction(undefFn);
 }
 
 void Evaluator::addFunction(Function func)
@@ -85,8 +133,13 @@
 	_functions.remove(name);
 }
 
-RuntimeResult Evaluator::evaluate(AstNode node, VarContext ctx)
+RuntimeResult Evaluator::evaluate(AstNode node, VarContext ctx, int recursionDepth)
 {
+    if (recursionDepth > _recursionLimit)
+    {
+        throw StackOverflowException(node);
+    }
+
 	if (node.isSym())
 	{
 		return RuntimeResult(QList<Token>{Token(node.symbol())});
@@ -138,20 +191,20 @@
 
 		for (const AstNode &arg : node.funcArgs())
 		{
-			RuntimeResult internalResult = evaluate(arg, ctx);
+            RuntimeResult internalResult = evaluate(arg, ctx, recursionDepth + 1);
 			if (!internalResult.success())
 				return internalResult;
 
 			args.append(internalResult.result());
 		}
 
-		return callFunction(node.name(), args);
+        return callFunction(node.name(), args, recursionDepth + 1);
 	}
 
 	return RuntimeResult("#TYPE_ERROR");
 }
 
-RuntimeResult Evaluator::callFunction(QString name, QList<Token> args)
+RuntimeResult Evaluator::callFunction(QString name, QList<Token> args, int recursionDepth)
 {
 	if (!_functions.contains(name))
 		return RuntimeResult("Function " + name + " is not defined.");
@@ -173,7 +226,7 @@
 		QList<Token> final;
 		for (const AstNode &node : sentence.result())
 		{
-			RuntimeResult argRes = evaluate(node, res.context);
+            RuntimeResult argRes = evaluate(node, res.context, recursionDepth);
 			if (!argRes.success())
 				return argRes;
 
@@ -186,6 +239,11 @@
     return RuntimeResult("Function " + name + " had no matching sentences for input");
 }
 
+void Evaluator::quit()
+{
+    throw EvalQuitException();
+}
+
 QList<Token> Evaluator::dig(QString name)
 {
     if (_vars.contains(name))
@@ -232,3 +290,39 @@
     eout("Runtime Error: " + brief);
     eout(details);
 }
+
+void EvalQuitException::raise() const
+{
+    throw *this;
+}
+
+EvalQuitException *EvalQuitException::clone() const
+{
+    return new EvalQuitException(*this);
+}
+
+StackOverflowException::StackOverflowException(AstNode failedAt)
+    : QException()
+{
+    _failedAt = failedAt;
+}
+
+AstNode StackOverflowException::failedAt() const
+{
+    return _failedAt;
+}
+
+void StackOverflowException::raise() const
+{
+    throw *this;
+}
+
+StackOverflowException *StackOverflowException::clone() const
+{
+    return new StackOverflowException(*this);
+}
+
+StackOverflowException::operator QString() const
+{
+    return "StackOverflowException: at " + pprint(_failedAt);
+}
diff --git a/Evaluator.h b/Evaluator.h
index afda18f..47ea941 100644
--- a/Evaluator.h
+++ b/Evaluator.h
@@ -2,6 +2,7 @@
 
 #include <QMap>
 #include <QStack>
+#include <QException>
 
 #include "Token.h"
 #include "AstNode.h"
@@ -11,9 +12,14 @@
 class RuntimeResult
 {
 public:
-	RuntimeResult(QList<Token> result);
+    RuntimeResult(const RuntimeResult &other) = default;
+    RuntimeResult(QList<Token> result = {});
 	RuntimeResult(QString message);
 	
+    RuntimeResult &operator =(const RuntimeResult &other);
+    RuntimeResult operator +(const RuntimeResult &other);
+    RuntimeResult &operator +=(const RuntimeResult &other);
+
 	bool success() const;
 	QString message() const;
 	QList<Token> result() const;
@@ -26,18 +32,50 @@
 	QList<Token> _result;
 };
 
+class EvalQuitException : public QException
+{
+public:
+    EvalQuitException() = default;
+    EvalQuitException(const EvalQuitException &other) = default;
+
+    void raise() const override;
+    EvalQuitException *clone() const override;
+};
+
+class StackOverflowException : public QException
+{
+public:
+    StackOverflowException(AstNode failedAt = AstNode());
+    StackOverflowException(const StackOverflowException &other) = default;
+
+    AstNode failedAt() const;
+
+    void raise() const override;
+    StackOverflowException *clone() const override;
+
+    operator QString() const;
+
+private:
+    AstNode _failedAt;
+};
+
 class Evaluator {
 public:
 	Evaluator();
 
 	void addFunction(Function func);
 	void clearFunction(QString name);
-	RuntimeResult evaluate(AstNode node, VarContext ctx);
-	RuntimeResult callFunction(QString name, QList<Token> args);
+    RuntimeResult evaluate(AstNode node, VarContext ctx, int recursionDepth = 0);
+    RuntimeResult callFunction(QString name, QList<Token> args, int recursionDepth);
+
+    // Throws an EvalQuitException
+    void quit();
 
 private:
 	QMap<QString, Function> _functions;
     QMap<QString, QStack<QList<Token>>> _vars;
+    bool _shouldContinue = true;
+    int _recursionLimit = 1024;
 
 protected:
     QList<Token> dig(QString name);
diff --git a/Parser.cpp b/Parser.cpp
index 6ce62fa..9679b4c 100644
--- a/Parser.cpp
+++ b/Parser.cpp
@@ -1,4 +1,5 @@
 #include "Parser.h"
+#include "PPrint.h"
 
 #include <QDebug>
 
@@ -34,7 +35,12 @@
 
 int ParseResult::status() const
 {
-	return _status;
+    return _status;
+}
+
+ParseResult::operator QString() const
+{
+    return pprint(pos()) + "\n" + message();
 }
 
 Parser::Parser(QString input)
diff --git a/Parser.h b/Parser.h
index 102a3c3..d0a348d 100644
--- a/Parser.h
+++ b/Parser.h
@@ -36,6 +36,8 @@
 	QString message() const;
 	int status() const;
 
+    operator QString() const;
+
 private:
 	int _status = COMPLETE;
 	QString _message = "";
diff --git a/ide/Cell.cpp b/ide/Cell.cpp
index 2fd964b..c49466d 100644
--- a/ide/Cell.cpp
+++ b/ide/Cell.cpp
@@ -1,8 +1,15 @@
 #include "Cell.h"
 
+QHash<QUuid, Cell *> Cell::_cellUuids = QHash<QUuid, Cell*>();
+
+Cell::~Cell()
+{
+    _cellUuids.remove(_uuid);
+}
+
 Cell::Cell(QObject *parent) : QObject(parent)
 {
-
+    _cellUuids[_uuid] = this;
 }
 
 Cell::Cell(const Cell &copy, QObject *parent)
@@ -36,6 +43,16 @@
     return _result;
 }
 
+QUuid Cell::uuid() const
+{
+    return _uuid;
+}
+
+int Cell::status() const
+{
+    return _status;
+}
+
 void Cell::setCode(QString code)
 {
     _code = code;
@@ -47,3 +64,17 @@
     _result = result;
     emit resultChanged(result);
 }
+
+void Cell::setStatus(int status)
+{
+    _status = status;
+    emit statusChanged(status);
+}
+
+Cell *Cell::cellFromUuid(QUuid uuid)
+{
+    if (_cellUuids.contains(uuid))
+        return _cellUuids[uuid];
+    else
+        return nullptr;
+}
diff --git a/ide/Cell.h b/ide/Cell.h
index e3d1f9d..fb5bc42 100644
--- a/ide/Cell.h
+++ b/ide/Cell.h
@@ -2,6 +2,8 @@
 
 #include <QObject>
 #include <qqml.h>
+#include <QUuid>
+#include <QHash>
 
 class Cell : public QObject
 {
@@ -10,8 +12,11 @@
 
     Q_PROPERTY(QString code READ code WRITE setCode NOTIFY codeChanged)
     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)
 
 public:
+    ~Cell();
     explicit Cell(QObject *parent = nullptr);
     Cell(const Cell &copy, QObject *parent = nullptr);
     Cell(QString code, QString result, QObject *parent = nullptr);
@@ -20,16 +25,36 @@
 
     QString code() const;
     QString result() const;
+    QUuid uuid() const;
+    int status() const;
 
     void setCode(QString code);
     void setResult(QString result);
+    void setStatus(int status);
+
+    Q_INVOKABLE static Cell *cellFromUuid(QUuid uuid);
+
+    enum Status
+    {
+        RUNNING,
+        IDLE,
+        WAITING,
+    };
+
+    Q_ENUM(Status);
 
 signals:
     void codeChanged(QString code);
     void resultChanged(QString result);
+    void uuidChanged(QUuid uuid);
+    void statusChanged(int status);
 
 private:
+    int _status = IDLE;
     QString _code, _result;
+    QUuid _uuid = QUuid::createUuid();
+
+    static QHash<QUuid, Cell *> _cellUuids;
 };
 
 Q_DECLARE_METATYPE(Cell)
diff --git a/ide/CellModel.cpp b/ide/CellModel.cpp
index 41acbe7..8271c87 100644
--- a/ide/CellModel.cpp
+++ b/ide/CellModel.cpp
@@ -29,9 +29,13 @@
     switch (role)
     {
     case CodeRole:
-        return _notebook->_cells[index.row()].code();
+        return _notebook->_cells[index.row()]->code();
     case ResultRole:
-        return _notebook->_cells[index.row()].result();
+        return _notebook->_cells[index.row()]->result();
+    case UuidRole:
+        return _notebook->_cells[index.row()]->uuid();
+    case StatusRole:
+        return _notebook->_cells[index.row()]->status();
     default:
         return QVariant();
     }
@@ -44,10 +48,13 @@
         switch (role)
         {
         case CodeRole:
-            _notebook->_cells[index.row()].setCode(value.toString());
+            _notebook->_cells[index.row()]->setCode(value.toString());
             break;
         case ResultRole:
-            _notebook->_cells[index.row()].setResult(value.toString());
+            _notebook->_cells[index.row()]->setResult(value.toString());
+            break;
+        case StatusRole:
+            _notebook->_cells[index.row()]->setStatus(value.toInt());
             break;
         default:
             return false;
@@ -66,7 +73,7 @@
     if (!index.isValid())
         return Qt::NoItemFlags;
 
-    return Qt::ItemIsEditable; // FIXME: Implement me!
+    return Qt::ItemIsEditable;
 }
 
 bool CellModel::insertRows(int row, int count, const QModelIndex &parent)
@@ -74,7 +81,26 @@
     beginInsertRows(parent, row, row + count - 1);
 
     for (int i = 0; i < count; i++)
-        _notebook->_cells.insert(row, Cell());
+    {
+        Cell *cell = new Cell(this);
+
+        connect(cell, &Cell::codeChanged, this, [this, cell](QString)
+        {
+            announceCellChange(cell, CodeRole);
+        });
+
+        connect(cell, &Cell::resultChanged, this, [this, cell](QString)
+        {
+            announceCellChange(cell, ResultRole);
+        });
+
+        connect(cell, &Cell::statusChanged, this, [this, cell](int)
+        {
+            announceCellChange(cell, StatusRole);
+        });
+
+        _notebook->_cells.insert(row, cell);
+    }
 
     endInsertRows();
 
@@ -86,7 +112,10 @@
     beginRemoveRows(parent, row, row + count - 1);
 
     for (int i = 0; i < count; i++)
+    {
+        delete _notebook->_cells[row];
         _notebook->_cells.removeAt(row);
+    }
 
     endRemoveRows();
 
@@ -98,6 +127,8 @@
     return {
         {CodeRole, "code"},
         {ResultRole, "result"},
+        {UuidRole, "uuid"},
+        {StatusRole, "status"},
     };
 }
 
@@ -107,7 +138,7 @@
 
     insertRows(i, 1, QModelIndex());
 
-    _notebook->_cells[i] = cell;
+    *_notebook->_cells[i] = cell;
     emit dataChanged(index(i), index(i), {CodeRole, ResultRole});
 }
 
@@ -115,3 +146,17 @@
 {
     addCell(Cell(code, result));
 }
+
+void CellModel::announceCellChange(Cell *cell, int role)
+{
+    // TODO: Optimize
+
+    for (int i = 0; i < rowCount(); i++)
+    {
+        if (_notebook->_cells[i] == cell)
+        {
+            emit dataChanged(index(i), index(i), QVector<int>() << role);
+            break;
+        }
+    }
+}
diff --git a/ide/CellModel.h b/ide/CellModel.h
index 16bfee5..1f56056 100644
--- a/ide/CellModel.h
+++ b/ide/CellModel.h
@@ -17,7 +17,9 @@
     enum CellRoles
     {
         CodeRole = Qt::UserRole + 1,
-        ResultRole
+        ResultRole,
+        UuidRole,
+        StatusRole,
     };
 
     // Basic functionality:
@@ -44,6 +46,7 @@
 
 private:
     Notebook *_notebook;
+    void announceCellChange(Cell *cell, int role);
 };
 
 Q_DECLARE_METATYPE(CellModel)
diff --git a/ide/IDE.pri b/ide/IDE.pri
index f656fd1..0341a0b 100644
--- a/ide/IDE.pri
+++ b/ide/IDE.pri
@@ -17,12 +17,14 @@
     $$PWD/Cell.cpp \
     $$PWD/CellModel.cpp \
     $$PWD/IdeMain.cpp \
+    $$PWD/NbRuntime.cpp \
     $$PWD/Notebook.cpp
 
 HEADERS += \
     $$PWD/Cell.h \
     $$PWD/CellModel.h \
     $$PWD/IdeMain.h \
+    $$PWD/NbRuntime.h \
     $$PWD/Notebook.h
 
 RESOURCES += \
diff --git a/ide/NbRuntime.cpp b/ide/NbRuntime.cpp
new file mode 100644
index 0000000..8454597
--- /dev/null
+++ b/ide/NbRuntime.cpp
@@ -0,0 +1,122 @@
+#include <QCoreApplication>
+
+#include "NbRuntime.h"
+#include "../Parser.h"
+
+NbRuntime::NbRuntime(QObject *parent)
+    : QThread(parent)
+{
+}
+
+void NbRuntime::queueCell(Cell *cell)
+{
+    if (!_cells.contains(cell))
+    {
+        qInfo() << "Queueing cell";
+
+        _cells.append(cell);
+
+        emit cellWaiting(cell);
+
+        if (!_running)
+            evalRemaining();
+    }
+}
+
+void NbRuntime::unqueueCell(Cell *cell)
+{
+    if (cell == _running)
+    {
+        // Exception should propagate back up to evalRemaining()
+        _eval.quit();
+    }
+    else
+    {
+        _cells.removeOne(cell);
+    }
+}
+
+void NbRuntime::evalRemaining()
+{
+    qInfo() << "evalRemaining";
+
+    while (!_cells.empty())
+    {
+        QCoreApplication::processEvents();
+
+        Cell *cell = _cells.first();
+        _cells.removeFirst();
+
+        _running = cell;
+
+        Parser parser(cell->code());
+        RuntimeResult result;
+
+        emit cellRunning(cell);
+
+        try
+        {
+            // Allow this cell to be quit
+            QCoreApplication::processEvents();
+
+            while (true)
+            {
+                ParseResult ret;
+                Function func;
+                AstNode ast;
+
+                if ((ret = parser.parseFunctionDefinition(&func)))
+                {
+                    _eval.addFunction(func);
+                }
+                else if (ret.status() == ParseResult::INCOMPLETE)
+                {
+                    emit cellFailedToParse(cell, ret);
+                    goto endOfCell; // JANK!
+                }
+                else if ((ret = parser.parseOne(&ast)))
+                {
+                    RuntimeResult nodeRes = _eval.evaluate(ast, _ctx);
+                    result += nodeRes;
+
+                    if (!nodeRes.success())
+                    {
+                        break;
+                    }
+                }
+                else if (ret.status() == ParseResult::INCOMPLETE)
+                {
+                    emit cellFailedToParse(cell, ret);
+                    break;
+                }
+                else
+                {
+                    parser.skip();
+
+                    if (!parser.atEnd())
+                    {
+                        emit cellFailedToParse(cell, ParseResult(ParseResult::NO_MATCH, "Garbage at end of input", parser.save()));
+                        goto endOfCell;
+                    }
+
+                    break;
+                }
+            }
+
+            emit cellFinishedRunning(cell, result);
+
+        endOfCell:
+            _running = nullptr;
+        }
+        catch (EvalQuitException &ex)
+        {
+            _running = nullptr;
+            emit cellQuit(cell);
+        }
+        catch (StackOverflowException &ex)
+        {
+            _running = nullptr;
+            emit cellFinishedRunning(cell, RuntimeResult(ex));
+        }
+    }
+}
diff --git a/ide/NbRuntime.h b/ide/NbRuntime.h
new file mode 100644
index 0000000..2f6a845
--- /dev/null
+++ b/ide/NbRuntime.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <QObject>
+#include <QThread>
+#include <QQueue>
+
+#include "Cell.h"
+#include "../Token.h"
+#include "../Evaluator.h"
+#include "../Parser.h"
+
+class NbRuntime : public QThread
+{
+    Q_OBJECT
+
+public:
+    explicit NbRuntime(QObject *parent = nullptr);
+
+public slots:
+    void queueCell(Cell *cell);
+    void unqueueCell(Cell *cell);
+
+signals:
+    void cellFinishedRunning(Cell *cell, RuntimeResult result);
+    void cellFailedToParse(Cell *cell, ParseResult result);
+    void cellWaiting(Cell *cell);
+    void cellRunning(Cell *cell);
+    void cellQuit(Cell *cell);
+
+protected:
+    void evalRemaining();
+
+    Evaluator _eval;
+    QQueue<Cell *> _cells;
+    Cell *_running = nullptr;
+    VarContext _ctx;
+};
diff --git a/ide/Notebook.cpp b/ide/Notebook.cpp
index 9beb332..82bee7e 100644
--- a/ide/Notebook.cpp
+++ b/ide/Notebook.cpp
@@ -1,21 +1,74 @@
 #include "Notebook.h"
 #include "CellModel.h"
+#include "../PPrint.h"
 
 Notebook::Notebook(QObject *parent)
     : QObject(parent)
     , _cellModel(new CellModel(this))
 {
+    connect(&_rt, &NbRuntime::cellFailedToParse, this, &Notebook::cellFailedToParse);
+    connect(&_rt, &NbRuntime::cellFinishedRunning, this, &Notebook::cellFinishedRunning);
+    connect(&_rt, &NbRuntime::cellQuit, this, &Notebook::cellQuit);
+    connect(&_rt, &NbRuntime::cellRunning, this, &Notebook::cellRunning);
+    connect(&_rt, &NbRuntime::cellWaiting, this, &Notebook::cellWaiting);
 
+    _rt.start();
 }
 
 Notebook::Notebook(const Notebook &other, QObject *parent)
-    : QObject(parent)
-    , _cells(other._cells)
-    , _cellModel(new CellModel(this))
+    : Notebook(parent)
 {
+    for (const Cell *cell : other._cells)
+    {
+        _cells.append(new Cell(*cell, this));
+    }
 }
 
 CellModel *Notebook::cellModel()
 {
     return _cellModel;
 }
+
+void Notebook::runCell(QUuid uuid)
+{
+    qInfo() << "Running cell" << uuid;
+    _rt.queueCell(Cell::cellFromUuid(uuid));
+}
+
+void Notebook::quitCell(QUuid uuid)
+{
+    _rt.unqueueCell(Cell::cellFromUuid(uuid));
+}
+
+void Notebook::cellFinishedRunning(Cell *cell, RuntimeResult result)
+{
+    qInfo() << "cellFinishedRunning" << cell->uuid() << pprint(result);
+    cell->setResult(pprint(result));
+    cell->setStatus(Cell::IDLE);
+}
+
+void Notebook::cellFailedToParse(Cell *cell, ParseResult result)
+{
+    qInfo() << "cellFailedToParse" << cell->uuid() << pprint(result);
+    cell->setResult(pprint(result));
+    cell->setStatus(Cell::IDLE);
+}
+
+void Notebook::cellWaiting(Cell *cell)
+{
+    qInfo() << "cellWaiting" << cell->uuid();
+    cell->setStatus(Cell::WAITING);
+}
+
+void Notebook::cellRunning(Cell *cell)
+{
+    qInfo() << "cellRunning" << cell->uuid();
+    cell->setStatus(Cell::RUNNING);
+}
+
+void Notebook::cellQuit(Cell *cell)
+{
+    qInfo() << "cellQuit" << cell->uuid();
+    cell->setResult("");
+    cell->setStatus(Cell::IDLE);
+}
diff --git a/ide/Notebook.h b/ide/Notebook.h
index 0bd84ec..867c2e9 100644
--- a/ide/Notebook.h
+++ b/ide/Notebook.h
@@ -3,6 +3,7 @@
 #include <QObject>
 
 #include "Cell.h"
+#include "NbRuntime.h"
 
 class CellModel;
 
@@ -19,14 +20,25 @@
 
     CellModel *cellModel();
 
+    Q_INVOKABLE void runCell(QUuid uuid);
+    Q_INVOKABLE void quitCell(QUuid uuid);
+
 signals:
     void cellModelChanged();
 
+protected slots:
+    void cellFinishedRunning(Cell *cell, RuntimeResult result);
+    void cellFailedToParse(Cell *cell, ParseResult result);
+    void cellWaiting(Cell *cell);
+    void cellRunning(Cell *cell);
+    void cellQuit(Cell *cell);
+
 protected:
     friend class CellModel;
 
-    QList<Cell> _cells;
+    QList<Cell *> _cells;
     CellModel *_cellModel;
+    NbRuntime _rt;
 };
 
 Q_DECLARE_METATYPE(Notebook)
diff --git a/ide/icons/square.svg b/ide/icons/square.svg
new file mode 100644
index 0000000..3a4587b
--- /dev/null
+++ b/ide/icons/square.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M405.333 64H106.667C83.198 64 64 83.198 64 106.667v298.666C64 428.802 83.198 448 106.667 448h298.666C428.802 448 448 428.802 448 405.333V106.667C448 83.198 428.802 64 405.333 64z"/></svg>
\ No newline at end of file
diff --git a/ide/qml/DocumentPadding.qml b/ide/qml/DocumentPadding.qml
new file mode 100644
index 0000000..3c47413
--- /dev/null
+++ b/ide/qml/DocumentPadding.qml
@@ -0,0 +1,9 @@
+import QtQuick 2.0
+import QtQuick.Layouts 1.3
+
+ColumnLayout {
+    Layout.topMargin: 10
+    Layout.bottomMargin: 10
+    Layout.leftMargin: 12
+    Layout.rightMargin: 12
+}
diff --git a/ide/qml/NotebookCell.qml b/ide/qml/NotebookCell.qml
index 3466d05..f51ffaa 100644
--- a/ide/qml/NotebookCell.qml
+++ b/ide/qml/NotebookCell.qml
@@ -3,15 +3,19 @@
 import QtQuick.Controls.Material 2.0
 import QtQuick.Layouts 1.0
 
+import sh.swisschili.REFAL 1.0
+
 Item {
     id: root
 
     required property string code
     required property string result
+    property int status: Cell.IDLE
 
     signal insertBelowClicked()
     signal codeEditingFinished(string code)
     signal cellFocused()
+    signal runClicked()
 
     height: column.height
 
@@ -34,9 +38,18 @@
 
             RoundButton {
                 Layout.alignment: Qt.AlignTop
-                icon.source: "qrc:///icons/play-circle.svg"
+                icon.source: iconForState(root.state)
                 icon.color: Material.color(Material.Grey, Material.Shade600)
                 flat: true
+
+                onClicked: root.runClicked()
+
+                function iconForState(state) {
+                    if (state === Cell.RUNNING)
+                        return "qrc:///icons/square.svg"
+
+                    return "qrc:///icons/play-circle.svg"
+                }
             }
 
             ColumnLayout {
diff --git a/ide/qml/main.qml b/ide/qml/main.qml
index a28d47d..bf99a8d 100644
--- a/ide/qml/main.qml
+++ b/ide/qml/main.qml
@@ -63,22 +63,47 @@
                 model: notebook.cellModel
                 clip: true
 
+                header: ColumnLayout {
+                    width: codeEditor.width
+
+                    DocumentPadding {
+                        Layout.bottomMargin: 0
+
+                        Label {
+                            font.pointSize: 18
+                            text: "Notebook"
+                        }
+                    }
+
+                    InsertRow {
+                        onInsertClicked: notebook.cellModel.insertRows(notebook.cellModel.index(0, 0), 1);
+                    }
+                }
+
                 delegate: NotebookCell {
                     id: notebookCell
 
                     required property var model
                     required property var index
+                    required property var uuid
+                    required property int status
 
                     width: codeEditor.width - 5
 
                     code: model.code
-                    result: model.result
+                    result: model.result.trim()
+                    status: model.status
 
                     onCodeEditingFinished: model.code = code
 
                     onInsertBelowClicked: {
-                        console.info(index)
-                        cellModel.insertRows(cellModel.index(0, index), 1)
+                        console.info(index);
+                        notebook.cellModel.insertRows(notebook.cellModel.index(index + 1, 0), 1);
+                    }
+
+                    onRunClicked: {
+                        console.info("Cell run clicked")
+                        notebook.runCell(uuid)
                     }
                 }
             }
diff --git a/ide/resources.qrc b/ide/resources.qrc
index a77a977..23ecdd0 100644
--- a/ide/resources.qrc
+++ b/ide/resources.qrc
@@ -6,5 +6,7 @@
         <file>icons/add.svg</file>
         <file>qml/InsertRow.qml</file>
         <file>icons/trash.svg</file>
+        <file>qml/DocumentPadding.qml</file>
+        <file>icons/square.svg</file>
     </qresource>
 </RCC>