First import of qtscript editor from svn://labs.trolltech.com/svn/qtscript/qtscriptdebug
authorAndrey Paskal <apaskal@gmail.com>
Wed, 23 Dec 2009 06:54:00 +0000 (09:54 +0300)
committerAndrey Paskal <apaskal@gmail.com>
Sun, 27 Dec 2009 10:00:36 +0000 (13:00 +0300)
src/editor/editor.pri [new file with mode: 0644]
src/editor/scriptedit.cpp [new file with mode: 0644]
src/editor/scriptedit.h [new file with mode: 0644]
src/editor/scripthighlighter.cpp [new file with mode: 0644]
src/editor/scripthighlighter.h [new file with mode: 0644]
src/editor/tabsettings.cpp [new file with mode: 0644]
src/editor/tabsettings.h [new file with mode: 0644]
src/editor/textedit.cpp [new file with mode: 0644]
src/editor/textedit.h [new file with mode: 0644]

diff --git a/src/editor/editor.pri b/src/editor/editor.pri
new file mode 100644 (file)
index 0000000..d49b206
--- /dev/null
@@ -0,0 +1,11 @@
+SOURCES += \
+    $$PWD/scripthighlighter.cpp \
+    $$PWD/textedit.cpp \
+    $$PWD/tabsettings.cpp \
+    $$PWD/scriptedit.cpp
+
+HEADERS += \
+    $$PWD/scripthighlighter.h \
+    $$PWD/textedit.h \
+    $$PWD/tabsettings.h \
+    $$PWD/scriptedit.h
diff --git a/src/editor/scriptedit.cpp b/src/editor/scriptedit.cpp
new file mode 100644 (file)
index 0000000..62003ed
--- /dev/null
@@ -0,0 +1,43 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "scriptedit.h"
+
+#include "scripthighlighter.h"
+
+#include <QtGui/QStyle>
+#include <QtGui/QPainter>
+#include <QtCore/QTimer>
+#include <QtCore/QDebug>
+
+
+ScriptEdit::ScriptEdit(QWidget *parent)
+  : TextEdit(parent)
+{
+    highlighter =  new ScriptHighlighter(this);
+}
+
+ScriptEdit::~ScriptEdit()
+{
+}
+
diff --git a/src/editor/scriptedit.h b/src/editor/scriptedit.h
new file mode 100644 (file)
index 0000000..ae9fd24
--- /dev/null
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef SCRIPTRUNNER_SCRIPTEDIT_H
+#define SCRIPTRUNNER_SCRIPTEDIT_H
+
+#include "textedit.h"
+
+class ScriptHighlighter;
+
+
+class ScriptEdit : public TextEdit
+{
+    Q_OBJECT
+
+public:
+    explicit ScriptEdit(QWidget *parent = 0);
+    ~ScriptEdit();
+
+private:
+    ScriptHighlighter *highlighter;
+    int m_executingLineNumber;
+};
+
+
+#endif // SCRIPTRUNNER_SCRIPTEDIT_H
diff --git a/src/editor/scripthighlighter.cpp b/src/editor/scripthighlighter.cpp
new file mode 100644 (file)
index 0000000..12b849e
--- /dev/null
@@ -0,0 +1,704 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+
+#include "scripthighlighter.h"
+
+enum ScriptIds {
+    Comment = 1,
+    Number,
+    String,
+    Type,
+    Keyword,
+    PreProcessor,
+    Label
+};
+
+#define MAX_KEYWORD 63
+static const char *const keywords[MAX_KEYWORD] = {
+    "Infinity",
+    "NaN",
+    "abstract",
+    "boolean",
+    "break",
+    "byte",
+    "case",
+    "catch",
+    "char",
+    "class",
+    "const",
+    "constructor",
+    "continue",
+    "debugger",
+    "default",
+    "delete",
+    "do",
+    "double",
+    "else",
+    "enum",
+    "export",
+    "extends",
+    "false",
+    "final",
+    "finally",
+    "float",
+    "for",
+    "function",
+    "goto",
+    "if",
+    "implements",
+    "import",
+    "in",
+    "instanceof",
+    "int",
+    "interface",
+    "long",
+    "native",
+    "new",
+    "package",
+    "private",
+    "protected",
+    "public",
+    "return",
+    "short",
+    "static",
+    "super",
+    "switch",
+    "synchronized",
+    "this",
+    "throw",
+    "throws",
+    "transient",
+    "true",
+    "try",
+    "typeof",
+    "undefined",
+    "var",
+    "void",
+    "volatile",
+    "while",
+    "with",    // end of array
+    0
+};
+
+struct KeywordHelper
+{
+    inline KeywordHelper(const QString &word) : needle(word) {}
+    const QString needle;
+};
+
+static bool operator<(const KeywordHelper &helper, const char *kw)
+{
+    return helper.needle < QLatin1String(kw);
+}
+
+static bool operator<(const char *kw, const KeywordHelper &helper)
+{
+    return QLatin1String(kw) < helper.needle;
+}
+
+static bool isKeyword(const QString &word)
+{
+    const char * const *start = &keywords[0];
+    const char * const *end = &keywords[MAX_KEYWORD - 1];
+    const char * const *kw = qBinaryFind(start, end, KeywordHelper(word));
+
+    return kw != end;
+}
+
+ScriptHighlighter::ScriptHighlighter(TextEdit *editor) :
+    QSyntaxHighlighter(editor)
+{
+    setDocument(editor->document());
+    textEdit = editor;
+
+    m_formats[ScriptNumberFormat].setForeground(Qt::darkBlue);
+    m_formats[ScriptStringFormat].setForeground(Qt::darkGreen);
+    m_formats[ScriptTypeFormat].setForeground(Qt::darkMagenta);
+    m_formats[ScriptKeywordFormat].setForeground(Qt::darkYellow);
+    m_formats[ScriptPreprocessorFormat].setForeground(Qt::darkBlue);
+    m_formats[ScriptLabelFormat].setForeground(Qt::darkRed);
+    m_formats[ScriptCommentFormat].setForeground(Qt::darkGreen);
+    m_formats[ScriptCommentFormat].setFontItalic(true);
+
+    // parentheses matcher
+    m_formatRange = true;
+    m_matchFormat.setForeground(Qt::red);
+    m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
+    m_mismatchFormat.setBackground(Qt::magenta);
+
+}
+
+void ScriptHighlighter::highlightBlock(const QString &text)
+{
+
+    // states
+    enum States { StateStandard, StateCommentStart1, StateCCommentStart2,
+                  StateScriptCommentStart2, StateCComment, StateScriptComment, StateCCommentEnd1,
+                  StateCCommentEnd2, StateStringStart, StateString, StateStringEnd,
+                  StateString2Start, StateString2, StateString2End,
+                  StateNumber, StatePreProcessor, NumStates  };
+
+    // tokens
+    enum Tokens { InputAlpha, InputNumber, InputAsterix, InputSlash, InputParen,
+                  InputSpace, InputHash, InputQuotation, InputApostrophe, InputSep, NumTokens };
+
+    static uchar table[NumStates][NumTokens] = {
+        { StateStandard,      StateNumber,     StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStandard
+        { StateStandard,      StateNumber,   StateCCommentStart2, StateScriptCommentStart2, StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCommentStart1
+        { StateCComment,      StateCComment,   StateCCommentEnd1,   StateCComment,         StateCComment,   StateCComment,   StateCComment,     StateCComment,    StateCComment,     StateCComment }, // StateCCommentStart2
+        { StateScriptComment,    StateScriptComment, StateScriptComment,     StateScriptComment,       StateScriptComment, StateScriptComment, StateScriptComment,   StateScriptComment,  StateScriptComment,   StateScriptComment }, // ScriptCommentStart2
+        { StateCComment,      StateCComment,   StateCCommentEnd1,   StateCComment,         StateCComment,   StateCComment,   StateCComment,     StateCComment,    StateCComment,     StateCComment }, // StateCComment
+        { StateScriptComment,    StateScriptComment, StateScriptComment,     StateScriptComment,       StateScriptComment, StateScriptComment, StateScriptComment,   StateScriptComment,  StateScriptComment,   StateScriptComment }, // StateScriptComment
+        { StateCComment,      StateCComment,   StateCCommentEnd1,   StateCCommentEnd2,     StateCComment,   StateCComment,   StateCComment,     StateCComment,    StateCComment,     StateCComment }, // StateCCommentEnd1
+        { StateStandard,      StateNumber,     StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCCommentEnd2
+        { StateString,        StateString,     StateString,         StateString,           StateString,     StateString,     StateString,       StateStringEnd,   StateString,       StateString }, // StateStringStart
+        { StateString,        StateString,     StateString,         StateString,           StateString,     StateString,     StateString,       StateStringEnd,   StateString,       StateString }, // StateString
+        { StateStandard,      StateStandard,   StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStringEnd
+        { StateString2,       StateString2,    StateString2,        StateString2,          StateString2,    StateString2,    StateString2,      StateString2,     StateString2End,   StateString2 }, // StateString2Start
+        { StateString2,       StateString2,    StateString2,        StateString2,          StateString2,    StateString2,    StateString2,      StateString2,     StateString2End,   StateString2 }, // StateString2
+        { StateStandard,      StateStandard,   StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateString2End
+        { StateNumber,        StateNumber,     StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateNumber
+        { StatePreProcessor,  StateStandard,   StateStandard,       StateCommentStart1,    StateStandard,   StateStandard,   StatePreProcessor, StateStringStart, StateString2Start, StateStandard } // StatePreProcessor
+    };
+
+    QString buffer;
+    buffer.reserve(text.length());
+
+    QTextCharFormat emptyFormat;
+
+    int state = StateStandard;
+    int braceDepth = 0;
+    const int previousState = previousBlockState();
+    if (previousState != -1) {
+        state = previousState & 0xff;
+        braceDepth = previousState >> 8;
+    }
+
+    if (text.isEmpty()) {
+        setCurrentBlockState(previousState);
+        TextEditDocumentLayout::clearParentheses(currentBlock());
+        return;
+    }
+    Parentheses parentheses;
+    parentheses.reserve(20); // assume wizard level ;-)
+    int input = -1;
+    int i = 0;
+    bool lastWasBackSlash = false;
+    bool makeLastStandard = false;
+
+    static const QString alphabeth = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+    static const QString mathChars = QLatin1String("xXeE");
+    static const QString numbers = QLatin1String("0123456789");
+    bool questionMark = false;
+    QChar lastChar;
+
+    int firstNonSpace = -1;
+    int lastNonSpace = -1;
+
+    for (;;) {
+        const QChar c = text.at(i);
+
+        if (lastWasBackSlash) {
+            input = InputSep;
+        } else {
+            switch (c.toAscii()) {
+                case '*':
+                    input = InputAsterix;
+                    break;
+                case '/':
+                    input = InputSlash;
+                    break;
+                case '{':
+                    braceDepth++;
+                    // fall through
+                case '(': case '[':
+                    input = InputParen;
+                    switch (state) {
+                    case StateStandard:
+                    case StateNumber:
+                    case StatePreProcessor:
+                    case StateCCommentEnd2:
+                    case StateCCommentEnd1:
+                    case StateString2End:
+                    case StateStringEnd:
+                        parentheses.push_back(Parenthesis(Parenthesis::Opened, c, i));
+                        break;
+                    default:
+                        break;
+                    }
+                    break;
+                case '}':
+                    if (--braceDepth < 0)
+                        braceDepth = 0;
+                    // fall through
+                case ')': case ']':
+                    input = InputParen;
+                    switch (state) {
+                    case StateStandard:
+                    case StateNumber:
+                    case StatePreProcessor:
+                    case StateCCommentEnd2:
+                    case StateCCommentEnd1:
+                    case StateString2End:
+                    case StateStringEnd:
+                        parentheses.push_back(Parenthesis(Parenthesis::Closed, c, i));
+                        break;
+                    default:
+                        break;
+                    }
+                    break;
+                case '#':
+                    input = InputHash;
+                    break;
+                case '"':
+                    input = InputQuotation;
+                    break;
+                case '\'':
+                    input = InputApostrophe;
+                    break;
+                case ' ':
+                    input = InputSpace;
+                    break;
+                case '1': case '2': case '3': case '4': case '5':
+                case '6': case '7': case '8': case '9': case '0':
+                    if (alphabeth.contains(lastChar)
+                        && (!mathChars.contains(lastChar) || !numbers.contains(text.at(i - 1)))
+                       ) {
+                        input = InputAlpha;
+                    } else {
+                        if (input == InputAlpha && numbers.contains(lastChar))
+                            input = InputAlpha;
+                        else
+                            input = InputNumber;
+                    }
+                    break;
+                case ':': {
+                              input = InputAlpha;
+                              const QChar colon = QLatin1Char(':');
+                              if (state == StateStandard && !questionMark && lastChar != colon) {
+                                  const QChar nextChar = i < text.length() - 1 ?  text.at(i + 1) : QLatin1Char(' ');
+                                  if (nextChar != colon)
+                                      for (int j = 0; j < i; ++j) {
+                                          if (format(j) == emptyFormat )
+                                              setFormat(j, 1, m_formats[ScriptLabelFormat]);
+                                      }
+                              }
+                          } break;
+                default:
+                    if (!questionMark && c == QLatin1Char('?'))
+                        questionMark = true;
+                    if (c.isLetter() || c == QLatin1Char('_'))
+                        input = InputAlpha;
+                    else
+                      input = InputSep;
+                break;
+            }
+        }
+
+        if (input != InputSpace) {
+            if (firstNonSpace < 0)
+                firstNonSpace = i;
+            lastNonSpace = i;
+        }
+
+        lastWasBackSlash = !lastWasBackSlash && c == QLatin1Char('\\');
+
+        if (input == InputAlpha)
+            buffer += c;
+
+        state = table[state][input];
+
+        switch (state) {
+            case StateStandard: {
+                                    setFormat(i, 1, emptyFormat);
+                                    if (makeLastStandard)
+                                        setFormat(i - 1, 1, emptyFormat);
+                                    makeLastStandard = false;
+                                    if (input != InputAlpha) {
+                                        highlightWord(i, buffer);
+                                        buffer = QString::null;
+                                    }
+                                } break;
+            case StateCommentStart1:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = true;
+                                buffer = QString::null;
+                                break;
+            case StateCCommentStart2:
+                                setFormat(i - 1, 2, m_formats[ScriptCommentFormat]);
+                                makeLastStandard = false;
+                                parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1));
+                                buffer = QString::null;
+                                break;
+            case StateScriptCommentStart2:
+                                setFormat(i - 1, 2, m_formats[ScriptCommentFormat]);
+                                makeLastStandard = false;
+                                buffer = QString::null;
+                                break;
+            case StateCComment:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptCommentFormat]);
+                                buffer = QString::null;
+                                break;
+            case StateScriptComment:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptCommentFormat]);
+                                buffer = QString::null;
+                                break;
+            case StateCCommentEnd1:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptCommentFormat]);
+                                buffer = QString::null;
+                                break;
+            case StateCCommentEnd2:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptCommentFormat]);
+                                parentheses.push_back(Parenthesis(Parenthesis::Closed, QLatin1Char('/'), i));
+                                buffer = QString::null;
+                                break;
+            case StateStringStart:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, emptyFormat);
+                                buffer = QString::null;
+                                break;
+            case StateString:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptStringFormat]);
+                                buffer = QString::null;
+                                break;
+            case StateStringEnd:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, emptyFormat);
+                                buffer = QString::null;
+                                break;
+            case StateString2Start:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, emptyFormat);
+                                buffer = QString::null;
+                                break;
+            case StateString2:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptStringFormat]);
+                                buffer = QString::null;
+                                break;
+            case StateString2End:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, emptyFormat);
+                                buffer = QString::null;
+                                break;
+            case StateNumber:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptNumberFormat]);
+                                buffer = QString::null;
+                                break;
+            case StatePreProcessor:
+                                if (makeLastStandard)
+                                    setFormat(i - 1, 1, emptyFormat);
+                                makeLastStandard = false;
+                                setFormat(i, 1, m_formats[ScriptPreprocessorFormat]);
+                                buffer = QString::null;
+                                break;
+        }
+
+        lastChar = c;
+        i++;
+        if (i >= text.length()) {
+            int collapse = Parenthesis::collapseAtPos(parentheses);
+            if (collapse >= 0) {
+                if (collapse == firstNonSpace)
+                    TextEditDocumentLayout::userData(currentBlock())->setCollapse(TextBlockUserData::CollapseThis);
+                else
+                    TextEditDocumentLayout::userData(currentBlock())->setCollapse(TextBlockUserData::CollapseAfter);
+            } else if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) {
+                userData->setCollapse(TextBlockUserData::NoCollapse);
+            }
+            break;
+        }
+    }
+
+    highlightWord(text.length(), buffer);
+
+    switch (state) {
+    case  StateCComment:
+    case  StateCCommentEnd1:
+    case  StateCCommentStart2:
+        state = StateCComment;
+        break;
+    case StateString:
+        // quotes cannot span multiple lines, so if somebody starts
+        // typing a quoted string we don't need to look for the ending
+        // quote in another line (or highlight until the end of the
+        // document) and therefore slow down editing.
+        state = StateStandard;
+        break;
+    case StateString2:
+        state =  StateStandard;
+        break;
+    default:
+        state = StateStandard;
+        break;
+    }
+
+    TextEditDocumentLayout::setParentheses(currentBlock(), parentheses);
+
+    setCurrentBlockState((braceDepth << 8) | state);
+}
+
+void ScriptHighlighter::highlightWord(int currentPos, const QString &buffer)
+{
+    if (buffer.isEmpty())
+        return;
+
+    // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
+    // but don't highlight words like 'Query'
+    if (buffer.length() > 1
+        && buffer.at(0) == QLatin1Char('Q')
+        && (buffer.at(1).isUpper()
+            || buffer.at(1) == QLatin1Char('_')
+            || buffer.at(1) == QLatin1Char('t'))) {
+        setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptTypeFormat]);
+    } else {
+        if (isKeyword(buffer))
+            setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptKeywordFormat]);
+    }
+}
+
+
+ScriptHighlighter::MatchType ScriptHighlighter::checkOpenParenthesis(QTextCursor *cursor, QChar c)
+{
+    if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+        return NoMatch;
+
+    Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+    Parenthesis openParen, closedParen;
+    QTextBlock closedParenParag = cursor->block();
+
+    const int cursorPos = cursor->position() - closedParenParag.position();
+    int i = 0;
+    int ignore = 0;
+    bool foundOpen = false;
+    for (;;) {
+        if (!foundOpen) {
+            if (i >= parenList.count())
+                return NoMatch;
+            openParen = parenList.at(i);
+            if (openParen.pos != cursorPos) {
+                ++i;
+                continue;
+            } else {
+                foundOpen = true;
+                ++i;
+            }
+        }
+
+        if (i >= parenList.count()) {
+            for (;;) {
+                closedParenParag = closedParenParag.next();
+                if (!closedParenParag.isValid())
+                    return NoMatch;
+                if (TextEditDocumentLayout::hasParentheses(closedParenParag)) {
+                    parenList = TextEditDocumentLayout::parentheses(closedParenParag);
+                    break;
+                }
+            }
+            i = 0;
+        }
+
+        closedParen = parenList.at(i);
+        if (closedParen.type == Parenthesis::Opened) {
+            ignore++;
+            ++i;
+            continue;
+        } else {
+            if (ignore > 0) {
+                ignore--;
+                ++i;
+                continue;
+            }
+
+            cursor->clearSelection();
+            cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
+
+            if (c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}')
+                || c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')')
+                || c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']')
+               )
+                return Mismatch;
+
+            return Match;
+        }
+    }
+}
+
+ScriptHighlighter::MatchType ScriptHighlighter::checkClosedParenthesis(QTextCursor *cursor, QChar c)
+{
+
+    if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+        return NoMatch;
+
+    Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+    Parenthesis openParen, closedParen;
+    QTextBlock openParenParag = cursor->block();
+
+    const int cursorPos = cursor->position() - openParenParag.position();
+    int i = parenList.count() - 1;
+    int ignore = 0;
+    bool foundClosed = false;
+    for (;;) {
+        if (!foundClosed) {
+            if (i < 0)
+                return NoMatch;
+            closedParen = parenList.at(i);
+            if (closedParen.pos != cursorPos - 1) {
+                --i;
+                continue;
+            } else {
+                foundClosed = true;
+                --i;
+            }
+        }
+
+        if (i < 0) {
+            for (;;) {
+                openParenParag = openParenParag.previous();
+                if (!openParenParag.isValid())
+                    return NoMatch;
+
+                if (TextEditDocumentLayout::hasParentheses(openParenParag)) {
+                    parenList = TextEditDocumentLayout::parentheses(openParenParag);
+                    break;
+                }
+            }
+            i = parenList.count() - 1;
+        }
+
+        openParen = parenList.at(i);
+        if (openParen.type == Parenthesis::Closed) {
+            ignore++;
+            --i;
+            continue;
+        } else {
+            if (ignore > 0) {
+                ignore--;
+                --i;
+                continue;
+            }
+
+            cursor->clearSelection();
+            cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
+
+            if ( c == '}' && openParen.chr != '{' ||
+                    c == ')' && openParen.chr != '(' ||
+                    c == ']' && openParen.chr != '[' )
+                return Mismatch;
+
+            return Match;
+        }
+    }
+}
+ScriptHighlighter::MatchType ScriptHighlighter::matchCursor(QTextCursor *cursor)
+{
+    cursor->clearSelection();
+
+    const QTextBlock block = cursor->block();
+
+
+    if (!TextEditDocumentLayout::hasParentheses(block))
+        return NoMatch;
+
+    const int relPos = cursor->position() - block.position();
+
+    Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+    const Parentheses::const_iterator cend = parentheses.constEnd();
+    for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+        const Parenthesis &paren = *it;
+        if (paren.pos == relPos
+            && paren.type == Parenthesis::Opened) {
+            return checkOpenParenthesis(cursor, paren.chr);
+        } else if (paren.pos == relPos - 1
+                   && paren.type == Parenthesis::Closed) {
+            return checkClosedParenthesis(cursor, paren.chr);
+        }
+    }
+    return NoMatch;
+}
+
+void ScriptHighlighter::matchParentheses()
+{
+    if (textEdit->isReadOnly())
+        return;
+
+    QTextCursor currentMatch = textEdit->textCursor();
+    const MatchType matchType = matchCursor(&currentMatch);
+    if (currentMatch.hasSelection()) {
+        QList<QTextEdit::ExtraSelection> extraSelections = textEdit->extraSelections();
+        QTextEdit::ExtraSelection sel;
+        if (matchType == Mismatch) {
+            sel.cursor = currentMatch;
+            sel.format = m_mismatchFormat;
+        } else {
+
+            if (m_formatRange) {
+                sel.cursor = currentMatch;
+                sel.format = m_rangeFormat;
+                extraSelections.append(sel);
+            }
+
+            sel.cursor = currentMatch;
+            sel.format = m_matchFormat;
+
+            sel.cursor.setPosition(currentMatch.selectionStart());
+            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+            extraSelections.append(sel);
+
+            sel.cursor.setPosition(currentMatch.selectionEnd());
+            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+        }
+        extraSelections.append(sel);
+        textEdit->setExtraSelections(extraSelections);
+    }
+}
diff --git a/src/editor/scripthighlighter.h b/src/editor/scripthighlighter.h
new file mode 100644 (file)
index 0000000..f6dfec4
--- /dev/null
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef SCRIPTRUNNER_SCRIPTHIGHLIGHTER_H
+#define SCRIPTRUNNER_SCRIPTHIGHLIGHTER_H
+
+#include "textedit.h"
+
+#include <QtGui/QSyntaxHighlighter>
+#include <QtGui/QTextCharFormat>
+#include <QtCore/QtAlgorithms>
+
+
+class ScriptHighlighter : public QSyntaxHighlighter
+{
+    Q_OBJECT
+
+public:
+    ScriptHighlighter(TextEdit *editor);
+
+    virtual void highlightBlock(const QString &text);
+
+//     // Set formats from a sequence of type QTextCharFormat
+//     template <class InputIterator>
+//         void setFormats(InputIterator begin, InputIterator end) {
+//             qCopy(begin, end, m_formats);
+//         }
+
+    enum ScriptFormats {
+        ScriptTextFormat, ScriptNumberFormat,
+        ScriptStringFormat, ScriptTypeFormat,
+        ScriptKeywordFormat, ScriptPreprocessorFormat,
+        ScriptLabelFormat, ScriptCommentFormat,
+        NumScriptFormats
+    };
+
+public Q_SLOTS:
+    void matchParentheses();
+
+private:
+    enum MatchType { NoMatch, Match, Mismatch  };
+    static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
+    static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
+    static MatchType matchCursor(QTextCursor *cursor);
+
+    void highlightWord(int currentPos, const QString &buffer);
+
+    QTextCharFormat m_formats[NumScriptFormats];
+
+    // parentheses matcher
+    bool m_formatRange;
+    QTextCharFormat m_matchFormat;
+    QTextCharFormat m_mismatchFormat;
+    QTextCharFormat m_rangeFormat;
+
+    TextEdit *textEdit;
+};
+
+
+#endif // SCRIPTRUNNER_SCRIPTEDIT_H
diff --git a/src/editor/tabsettings.cpp b/src/editor/tabsettings.cpp
new file mode 100644 (file)
index 0000000..a7093fa
--- /dev/null
@@ -0,0 +1,187 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "tabsettings.h"
+#include <QtCore/QSettings>
+#include <QtCore/QString>
+#include <QtGui/QTextCursor>
+#include <QtGui/QTextDocument>
+
+static const char* spacesForTabsKey="SpacesForTabs";
+static const char* autoIndentKey="AutoIndent";
+static const char* indentSelectionKey="IndentSelection";
+static const char* indentSizeKey="IndentSize";
+static const char* groupPostfix ="TabSettings";
+
+
+TabSettings::TabSettings() :
+    m_spacesForTabs(true),
+    m_autoIndent(true),
+    m_indentSelection(true),
+    m_indentSize(4)
+{
+}
+
+void TabSettings::toSettings(const QString &category, QSettings *s) const
+{
+    QString group = QLatin1String(groupPostfix);
+    if (!category.isEmpty())
+        group.insert(0, category);
+    s->beginGroup(group);
+    s->setValue(QLatin1String(spacesForTabsKey),  m_spacesForTabs);
+    s->setValue(QLatin1String(autoIndentKey), m_autoIndent);
+    s->setValue(QLatin1String(indentSelectionKey), m_indentSelection);
+    s->setValue(QLatin1String(indentSizeKey), m_indentSize);
+    s->endGroup();
+}
+
+void TabSettings::fromSettings(const QString &category, const QSettings *s)
+{
+    QString group = QLatin1String(groupPostfix);
+    if (!category.isEmpty())
+        group.insert(0, category);
+    group += QLatin1Char('/');
+
+    *this = TabSettings();
+
+    m_spacesForTabs   = s->value(group + QLatin1String(spacesForTabsKey), m_spacesForTabs).toBool();
+    m_autoIndent      = s->value(group + QLatin1String(autoIndentKey), m_autoIndent).toBool();
+    m_indentSelection = s->value(group + QLatin1String(indentSelectionKey), m_indentSelection).toBool();
+    m_indentSize      = s->value(group + QLatin1String(indentSizeKey), m_indentSize).toInt();
+}
+
+void TabSettings::tabify(QString &s) const
+{
+    const QChar tab = QLatin1Char('\t');
+    const QChar blank = QLatin1Char(' ');
+    const QChar newLine = QLatin1Char('\n');
+
+    if (m_spacesForTabs) {
+        s.replace(tab, QString(m_indentSize, blank));
+    } else {
+        int i = 0;
+        forever {
+            for (int j = i; j < s.length(); ++j) {
+                if (s.at(j) != blank && s.at(j) != tab) {
+                    if (j > i) {
+                        const QString t  = s.mid(i, j - i);
+                        int spaces = 0;
+                        for (int k = 0; k < t.length(); ++k)
+                            spaces += (t.at(k) == blank ? 1 : m_indentSize);
+                        s.remove(i, t.length());
+                        const int tabs = spaces / m_indentSize;
+                        spaces = spaces - (m_indentSize * tabs);
+                        if (spaces > 0)
+                            s.insert(i, QString(spaces, blank));
+                        if (tabs > 0)
+                            s.insert(i, QString(tabs, tab));
+                    }
+                    break;
+                }
+            }
+            i = s.indexOf(newLine, i);
+            if (i == -1)
+                break;
+            ++i;
+        }
+    }
+}
+
+QString TabSettings::indentationString(int indent) const
+{
+    if (indent == 0)
+        return QString();
+    QString indentString(indent, QLatin1Char(' '));
+    indentString.append(QLatin1Char('a'));
+    tabify(indentString);
+    indentString.truncate(indentString.length() - 1);
+    return indentString;
+}
+
+TabSettings::IndentationNumCharsPair TabSettings::indentation(const QString &s, IndentationCountMode icm) const
+{
+    IndentationNumCharsPair rc = IndentationNumCharsPair(0, 0);
+    const int size = s.size();
+    if (size == 0)
+        return rc;
+
+    const QChar tab = QLatin1Char('\t');
+    const QChar blank = QLatin1Char(' ');
+
+    for ( ; rc.second < size; rc.second++) {
+        const QChar c = s.at(rc.second);
+        if (c == tab)
+            rc.first += m_indentSize;
+        else {
+            if (c == blank || (icm == CountNonAlphaNumerical && !c.isLetterOrNumber()))
+                rc.first++;
+            else
+                break;
+        }
+    }
+    return rc;
+}
+
+void TabSettings::indentLine(QTextBlock block, int newIndent) const
+{
+    const QString blockText = block.text();
+    const int oldBlockLength = blockText.size();
+
+    // Quickly check whether indenting is required.
+    if (oldBlockLength == 0 && newIndent == 0)
+        return;
+
+    const QString indentString = indentationString(newIndent);
+    newIndent = indentString.length();
+
+    if (oldBlockLength == indentString.length() && blockText == indentString)
+        return;
+
+    if (oldBlockLength > indentString.length() &&
+        blockText.startsWith(indentString) &&
+        !blockText.at(indentString.length()).isSpace()) {
+        return;
+    }
+
+    QTextCursor cursor(block);
+    cursor.movePosition(QTextCursor::StartOfBlock);
+
+    const IndentationNumCharsPair oldIndentation = indentation(blockText);
+    if (oldIndentation.second > 0) {
+        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, oldIndentation.second);
+        cursor.removeSelectedText();
+    }
+
+    if (!indentString.isEmpty()) {
+        cursor.insertText(indentString);
+        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, indentString.length());
+    }
+}
+
+bool TabSettings::equals(const TabSettings &ts) const
+{
+    return m_spacesForTabs == ts.m_spacesForTabs &&
+           m_autoIndent ==  ts.m_autoIndent &&
+           m_indentSelection == ts.m_indentSelection &&
+           m_indentSize == ts.m_indentSize;
+}
diff --git a/src/editor/tabsettings.h b/src/editor/tabsettings.h
new file mode 100644 (file)
index 0000000..b706247
--- /dev/null
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef TABSETTING_H
+#define TABSETTING_H
+#include <QtCore/QPair>
+#include <QtGui/QTextBlock>
+
+class QSettings;
+class QString;
+class QTextDocument;
+class QTextCursor;
+
+// Tab settings: Data type the TabSettingsPage acts on
+// with some convenience functions for formatting.
+struct TabSettings {
+    TabSettings();
+
+    void toSettings(const QString &category, QSettings *s) const;
+    void fromSettings(const QString &category, const QSettings *s);
+
+    void tabify(QString &s) const;
+    void indentLine(QTextBlock block, int newIndent) const;
+
+    // Determine indentation string
+    QString indentationString(int indent) const;
+
+    // A pair of <indentation>/<number of leading space characters<, returned by indentation.
+    typedef QPair<int, int> IndentationNumCharsPair;
+
+    // How to determine the  indentation
+    enum  IndentationCountMode {
+        // Count space characters only.
+        CountSpaceCharacters,
+        // Consider non-alphanumerical characters to be blanks.
+        // This can be used for plain text editors for writing bulleted lists
+        // with items that span several lines.
+        CountNonAlphaNumerical };
+
+    // Determine indentation
+    IndentationNumCharsPair indentation(const QString &, IndentationCountMode icm = CountSpaceCharacters) const;
+
+    bool m_spacesForTabs;
+    bool m_autoIndent;
+    bool m_indentSelection;
+    int m_indentSize;
+
+    bool equals(const TabSettings &ts) const;
+};
+
+inline bool operator==(const TabSettings &t1, const TabSettings &t2) { return t1.equals(t2); }
+inline bool operator!=(const TabSettings &t1, const TabSettings &t2) { return !t1.equals(t2); }
+
+
+#endif
diff --git a/src/editor/textedit.cpp b/src/editor/textedit.cpp
new file mode 100644 (file)
index 0000000..827207d
--- /dev/null
@@ -0,0 +1,1476 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#include "textedit.h"
+
+#include <QtGui/QTextDocument>
+#include <QtGui/QTextBlock>
+#include <QtGui/QStyle>
+#include <QtGui/QPainter>
+#include <QtGui/QShortcut>
+#include <QtGui/QScrollBar>
+#include <QtGui/QToolTip>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTimer>
+#include <QtCore/QDebug>
+
+#include <limits.h>
+
+
+int Parenthesis::collapseAtPos(const Parentheses &parentheses)
+{
+    int result = -1;
+
+    int depth = 0;
+    for (int i = 0; i < parentheses.size(); ++i) {
+        const Parenthesis &p = parentheses.at(i);
+        if (p.chr == QLatin1Char('{')) {
+            if (depth == 0)
+                result = p.pos;
+            ++depth;
+        } else if (p.chr == QLatin1Char('}')) {
+            if (--depth < 0)
+                depth = 0;
+            result = -1;
+        }
+    }
+    return result;
+}
+
+
+bool TextBlockUserData::hasClosingCollapse() const
+{
+    int depth = 0;
+    for (int i = 0; i < m_parentheses.size(); ++i) {
+        const Parenthesis &p = m_parentheses.at(i);
+        if (p.chr == QLatin1Char('{')) {
+            ++depth;
+        } else if (p.chr == QLatin1Char('}')) {
+            if (--depth < 0)
+                return true;
+        }
+    }
+    return false;
+}
+
+int TextBlockUserData::collapseAtPos() const
+{
+    return Parenthesis::collapseAtPos(m_parentheses);
+}
+
+
+void TextEditDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses)
+{
+    if (parentheses.isEmpty()) {
+        if (TextBlockUserData *userData = testUserData(block))
+            userData->clearParentheses();
+    } else {
+        userData(block)->setParentheses(parentheses);
+    }
+}
+
+Parentheses TextEditDocumentLayout::parentheses(const QTextBlock &block)
+{
+    if (TextBlockUserData *userData = testUserData(block))
+        return userData->parentheses();
+    return Parentheses();
+}
+
+bool TextEditDocumentLayout::hasParentheses(const QTextBlock &block)
+{
+    if (TextBlockUserData *userData = testUserData(block))
+        return userData->hasParentheses();
+    return false;
+}
+
+
+
+
+
+QRectF TextEditDocumentLayout::blockBoundingRect(const QTextBlock &block) const
+{
+    QRectF r = QPlainTextDocumentLayout::blockBoundingRect(block);
+    return r;
+}
+
+class TextEditExtraArea : public QWidget {
+    TextEdit *textEdit;
+public:
+    TextEditExtraArea(TextEdit *edit):QWidget(edit) {
+        textEdit = edit;
+        setAutoFillBackground(true);
+    }
+public:
+
+    QSize sizeHint() const {
+        return QSize(textEdit->extraAreaWidth(), 0);
+    }
+protected:
+    void paintEvent(QPaintEvent *event){
+        textEdit->extraAreaPaintEvent(event);
+    }
+    void mousePressEvent(QMouseEvent *event){
+        textEdit->extraAreaMouseEvent(event);
+    }
+    void mouseMoveEvent(QMouseEvent *event){
+        textEdit->extraAreaMouseEvent(event);
+    }
+    void mouseReleaseEvent(QMouseEvent *event){
+        textEdit->extraAreaMouseEvent(event);
+    }
+
+    void wheelEvent(QWheelEvent *event) {
+        QCoreApplication::sendEvent(textEdit->viewport(), event);
+    }
+};
+
+
+
+TextEdit::TextEdit(QWidget *parent) : QPlainTextEdit(parent)
+{
+    viewport()->setMouseTracking(true);
+    extraAreaSelectionAnchorBlockNumber = extraAreaToggleBreakpointBlockNumber = -1;
+    QTextDocument *doc = document();
+    doc->setDocumentLayout(new TextEditDocumentLayout(doc));
+
+    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
+    connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
+    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
+    m_extraArea = new TextEditExtraArea(this);
+    m_extraArea->setMouseTracking(true);
+    connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int)));
+
+    baseLine = 1;
+
+    executionIcon = QIcon(":/images/location.svg");
+    bookmarkIcon = QIcon(":/images/bookmark.svg");
+    breakpointIcon = QIcon(":/images/breakpoint.svg");
+    breakpointPendingIcon = QIcon(":/images/pending.svg");
+
+    // parentheses matcher
+    m_formatRange = true;
+    m_matchFormat.setForeground(Qt::red);
+    m_rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
+    m_mismatchFormat.setBackground(Qt::magenta);
+    m_parenthesesMatchingTimer = new QTimer(this);
+    m_parenthesesMatchingTimer->setSingleShot(true);
+    connect(m_parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(matchParentheses()));
+
+    slotUpdateExtraAreaWidth();
+    slotCursorPositionChanged();
+}
+
+TextEdit::~TextEdit()
+{
+}
+
+
+bool TextEdit::viewportEvent(QEvent *event)
+{
+    if (event->type() == QEvent::ToolTip) {
+        QString text;
+        QRect box;
+        if (collapsedBlockAt(static_cast<QHelpEvent*>(event)->pos(), &text, &box).isValid()) {
+            QToolTip::showText(static_cast<QHelpEvent*>(event)->globalPos(), text, viewport(), box);
+        }
+        return true;
+    }
+    return QPlainTextEdit::viewportEvent(event);
+}
+
+
+void TextEdit::resizeEvent(QResizeEvent *e)
+{
+    QPlainTextEdit::resizeEvent(e);
+    QRect cr = contentsRect();
+    m_extraArea->setGeometry(
+        QStyle::visualRect(layoutDirection(), cr,
+                           QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
+}
+
+QRect TextEdit::collapseBox(const QTextBlock &block)
+{
+    QRectF br = blockBoundingGeometry(block).translated(contentOffset());
+    int collapseBoxWidth = fontMetrics().lineSpacing();
+    return QRect(m_extraArea->width() - collapseBoxWidth + collapseBoxWidth/4,
+                 int(br.top()) + collapseBoxWidth/4,
+                  2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1);
+
+}
+
+QTextBlock TextEdit::collapsedBlockAt(const QPoint &pos, QString *text, QRect *box) const {
+    QPointF offset(contentOffset());
+    QTextBlock block = firstVisibleBlock();
+    int top = (int)blockBoundingGeometry(block).translated(offset).top();
+    int bottom = top + (int)blockBoundingRect(block).height();
+
+    int viewportHeight = viewport()->height();
+
+    while (block.isValid() && top <= viewportHeight) {
+        QTextBlock nextBlock = block.next();
+        if (block.isVisible() && bottom >= 0) {
+            if (nextBlock.isValid() && !nextBlock.isVisible()) {
+                QTextLayout *layout = block.layout();
+                QTextLine line = layout->lineAt(layout->lineCount()-1);
+                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
+                lineRect.adjust(0, 0, -1, -1);
+
+                QRectF collapseRect(lineRect.right() + 8, lineRect.top(),
+                                    fontMetrics().width(QLatin1String(" {...}; ")),
+                                    lineRect.height());
+                if (collapseRect.contains(pos)) {
+                    QTextBlock result = block;
+                    if (box)
+                        *box = collapseRect.toAlignedRect();
+                    if (text) {
+                        *text = QString();
+                        block = nextBlock;
+                        int lineCount = 15;
+                        while (nextBlock.isValid() && !nextBlock.isVisible()) {
+                            block = nextBlock;
+                            if (lineCount) {
+                                --lineCount;
+                                if (!text->isEmpty())
+                                    *text += QLatin1Char('\n');
+                                *text += block.text();
+                            }
+                            nextBlock = block.next();
+                        }
+                        if (lineCount == 0)
+                            *text += QLatin1String("\n...");
+                    }
+                    return result;
+                } else {
+                    block = nextBlock;
+                    while (nextBlock.isValid() && !nextBlock.isVisible()) {
+                        block = nextBlock;
+                        nextBlock = block.next();
+                    }
+                }
+            }
+        }
+
+        block = nextBlock;
+        top = bottom;
+        bottom = top + (int)blockBoundingRect(block).height();
+    }
+    return QTextBlock();
+}
+
+void TextEdit::paintEvent(QPaintEvent *e)
+{
+    QPlainTextEdit::paintEvent(e);
+
+    QPainter painter(viewport());
+    QPointF offset(contentOffset());
+    QTextBlock block = firstVisibleBlock();
+    int top = (int)blockBoundingGeometry(block).translated(offset).top();
+    int bottom = top + (int)blockBoundingRect(block).height();
+
+    while (block.isValid() && top <= e->rect().bottom()) {
+        QTextBlock nextBlock = block.next();
+        if (block.isVisible() && bottom >= e->rect().top()) {
+            if (nextBlock.isValid() && !nextBlock.isVisible()) {
+                QTextLayout *layout = block.layout();
+                QTextLine line = layout->lineAt(layout->lineCount()-1);
+                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
+                lineRect.adjust(0, 0, -1, -1);
+
+                QRectF collapseRect(lineRect.right() + 8, lineRect.top(),
+                                    fontMetrics().width(QLatin1String(" {...}; ")),
+                                    lineRect.height());
+                painter.drawRect(collapseRect.adjusted(0, 0, 0, -1));
+
+
+                QString replacement = QLatin1String("...");
+
+                QTextBlock info = block;
+                if (block.userData()
+                    && static_cast<TextBlockUserData*>(block.userData())->collapse() == TextBlockUserData::CollapseAfter)
+                    ;
+                else if (block.next().userData()
+                         && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+                         == TextBlockUserData::CollapseThis) {
+                    replacement.prepend(nextBlock.text().trimmed().left(1));
+                    info = nextBlock;
+                }
+
+                block = nextBlock;
+                while (nextBlock.isValid() && !nextBlock.isVisible()) {
+                    block = nextBlock;
+                    nextBlock = block.next();
+                }
+                if (static_cast<TextBlockUserData*>(info.userData())->collapseIncludesClosure()) {
+                    QString right = block.text().trimmed();
+                    if (right.endsWith(QLatin1Char(';'))) {
+                        right.chop(1);
+                        right = right.trimmed();
+                        replacement.append(right.right(1));
+                        replacement.append(QLatin1Char(';'));
+                    } else {
+                        replacement.append(right.right(1));
+                    }
+                }
+                painter.drawText(collapseRect, Qt::AlignCenter, replacement);
+            }
+        }
+
+        block = nextBlock;
+        top = bottom;
+        bottom = top + (int)blockBoundingRect(block).height();
+    }
+
+
+}
+
+void TextEdit::slotUpdateExtraAreaWidth()
+{
+    if (isLeftToRight())
+        setViewportMargins(extraAreaWidth(), 0, 0, 0);
+    else
+        setViewportMargins(0, 0, extraAreaWidth(), 0);
+}
+
+
+QWidget *TextEdit::extraArea() const {
+    return m_extraArea;
+}
+
+int TextEdit::extraAreaWidth(int *markWidthPtr) const {
+    int digits = 1;
+    int max = qMax(1, blockCount());
+    while (max >= 10) {
+        max /= 10;
+        ++digits;
+    }
+    QFontMetrics fm(fontMetrics());
+    int space = fm.width(QLatin1Char('9')) * digits;
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+    Q_ASSERT(documentLayout);
+    int markWidth = 0;
+    markWidth += fm.lineSpacing();
+    if (documentLayout->doubleMarkCount)
+        markWidth += fm.lineSpacing() / 3;
+
+    if (markWidthPtr)
+        *markWidthPtr = markWidth;
+
+    space += markWidth;
+
+
+    space += 4;
+
+    space += fm.lineSpacing();
+
+    return space;
+}
+
+void TextEdit::slotModificationChanged(bool m)
+{
+    if (!m) {
+        QTextDocument *doc = document();
+        TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+        Q_ASSERT(documentLayout);
+        int oldLastSaveRevision = documentLayout->lastSaveRevision;
+        documentLayout->lastSaveRevision = doc->revision();
+        QTextBlock block = doc->begin();
+        while (block.isValid()) {
+            if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
+                block.setRevision(-documentLayout->lastSaveRevision - 1);
+            } else {
+                block.setRevision(documentLayout->lastSaveRevision);
+            }
+            block = block.next();
+        }
+        m_extraArea->update();
+    }
+}
+
+void TextEdit::slotUpdateRequest(const QRect &r, int dy)
+{
+    if (dy)
+        m_extraArea->scroll(0, dy);
+    else
+        m_extraArea->update(0, r.y(), m_extraArea->width(), r.height());
+
+    if (r.contains(viewport()->rect()))
+        slotUpdateExtraAreaWidth();
+}
+
+
+int TextEdit::executionBlockNumber() const
+{
+    QTextDocument *doc = document();
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+    Q_ASSERT(documentLayout);
+    return documentLayout->executionBlockNumber();
+}
+
+void TextEdit::setExecutionBlockNumber(int blockNumber)
+{
+    QTextDocument *doc = document();
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+    Q_ASSERT(documentLayout);
+    documentLayout->setExecutionBlockNumber(blockNumber);
+
+    if (true) {
+        QTextBlock block = document()->findBlockByNumber(blockNumber);
+        if (block.isValid()) {
+            QTextCursor cursor = textCursor();
+            cursor.setPosition(block.position());
+            setTextCursor(cursor);
+        }
+    }
+}
+
+void TextEdit::extraAreaPaintEvent(QPaintEvent *e)
+{
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+    Q_ASSERT(documentLayout);
+
+    QPalette pal = palette();
+    QPainter painter(m_extraArea);
+    QFontMetrics fm(painter.fontMetrics());
+    int fmLineSpacing = fm.lineSpacing();
+
+    int markWidth = 0;
+    markWidth += fm.lineSpacing();
+    if (documentLayout->doubleMarkCount)
+        markWidth += fm.lineSpacing() / 3;
+
+
+    int collapseBoxWidth = fmLineSpacing;
+    int extraAreaWidth = m_extraArea->width() - collapseBoxWidth;
+
+    QLinearGradient gradient(QPointF(extraAreaWidth - 10, 0), QPointF(extraAreaWidth, 0));
+    gradient.setColorAt(0, pal.color(QPalette::Background));
+    gradient.setColorAt(1, pal.color(QPalette::Base));
+    painter.fillRect(e->rect(), gradient);
+
+    QLinearGradient gradient2(QPointF(0, 0), QPointF(markWidth, 0));
+    gradient2.setColorAt(0, pal.color(QPalette::Dark));
+    gradient2.setColorAt(1, pal.color(QPalette::Background));
+    painter.fillRect(e->rect().intersected(QRect(0, 0, markWidth, INT_MAX)), gradient2);
+
+
+    painter.setPen(QPen(pal.color(QPalette::Background), 2));
+    painter.drawLine(extraAreaWidth-1, e->rect().top(), extraAreaWidth-1, e->rect().bottom());
+    painter.setRenderHint(QPainter::Antialiasing);
+
+
+    QTextBlock block = firstVisibleBlock();
+    int blockNumber = block.blockNumber();
+    int top = (int)blockBoundingGeometry(block).translated(contentOffset()).top();
+    int bottom = top + (int)blockBoundingRect(block).height();
+
+    int executionBlock = executionBlockNumber();
+
+    while (block.isValid() && top <= e->rect().bottom()) {
+        bool collapseThis = false;
+        bool collapseAfter = false;
+        bool hasClosingCollapse = false;
+        QTextBlock nextBlock = block.next();
+        if (block.isVisible() && bottom >= e->rect().top()) {
+            QString number = QString::number(blockNumber + baseLine);
+            painter.setPen(pal.color(QPalette::Highlight));
+
+            if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
+                int x = 0;
+                int radius = fmLineSpacing - 1;
+                if (userData->bookmark()) {
+                    QRect r(x, top, radius, radius);
+                    bookmarkIcon.paint(&painter, r);
+                    x += fmLineSpacing / 3;
+                }
+                if (userData->breakpoint() || userData->pendingBreakpoint()) {
+                    QRect r(x, top, radius, radius);
+                    breakpointIcon.paint(&painter, r);
+                    if (!userData->breakpoint()) { // pending only
+                        breakpointPendingIcon.paint(&painter, r);
+                    }
+                }
+
+                collapseAfter = (userData->collapse() == TextBlockUserData::CollapseAfter);
+                collapseThis = (userData->collapse() == TextBlockUserData::CollapseThis);
+                hasClosingCollapse = userData->hasClosingCollapse();
+            }
+
+
+            if (executionBlock == blockNumber) {
+                QRect br(0, top, markWidth, fmLineSpacing);
+                executionIcon.paint(&painter, br);
+            }
+
+
+            int previousBraceDepth = block.previous().userState();
+            if (previousBraceDepth >= 0)
+                previousBraceDepth >>= 8;
+            else
+                previousBraceDepth = 0;
+            int braceDepth = block.userState();
+            if (!nextBlock.isVisible()) {
+                QTextBlock b = nextBlock;
+                while (b.isValid() && !b.isVisible())
+                    b = b.next();
+                braceDepth = b.userState();
+            }
+            if (braceDepth >= 0)
+                braceDepth >>= 8;
+            else
+                braceDepth = 0;
+
+            QRect box(extraAreaWidth + collapseBoxWidth/4, top + collapseBoxWidth/4,
+                      2 * (collapseBoxWidth/4) + 1, 2 * (collapseBoxWidth/4) + 1);
+            QPoint boxCenter = box.center();
+            painter.save();
+            painter.setPen(pal.text().color());
+            painter.setRenderHint(QPainter::Antialiasing, false);
+
+            bool collapseNext = (nextBlock.userData() && static_cast<TextBlockUserData*>(nextBlock.userData())->collapse() == TextBlockUserData::CollapseThis);
+
+            if (previousBraceDepth || collapseThis)
+                painter.drawLine(boxCenter.x(), top, boxCenter.x(), boxCenter.y());
+
+            if (braceDepth || (collapseNext && nextBlock.isVisible()))
+                painter.drawLine(boxCenter.x(), boxCenter.y(), boxCenter.x(), bottom-1);
+
+            if (collapseAfter || collapseNext) {
+                    painter.setBrush(pal.background());
+                    painter.drawRect(box.adjusted(0, 0, -1, -1));
+                    if (!nextBlock.isVisible())
+                        painter.drawLine(boxCenter.x(), box.top() + 2, boxCenter.x(), box.bottom() - 2);
+                    painter.drawLine(box.left() + 2, boxCenter.y(), box.right() - 2, boxCenter.y());
+            } else if (hasClosingCollapse) {
+                painter.drawLine(boxCenter.x(), boxCenter.y(), box.right() - 1, boxCenter.y());
+            }
+
+            painter.restore();
+
+
+//             qDebug() << "block" << block.blockNumber() << "revision:" << block.revision() << "lastSaved:" << documentLayout->lastSaveRevision;
+            if (block.revision() != documentLayout->lastSaveRevision) {
+                painter.save();
+                painter.setRenderHint(QPainter::Antialiasing, false);
+                if (block.revision() < 0)
+                    painter.setPen(QPen(Qt::darkGreen, 2));
+                else
+                    painter.setPen(QPen(Qt::red, 2));
+                painter.drawLine(extraAreaWidth-1, top, extraAreaWidth-1, bottom-1);
+                painter.restore();
+            }
+
+            painter.drawText(markWidth, top, extraAreaWidth - markWidth - 4, fm.height(), Qt::AlignRight, number);
+//            painter.drawText(markWidth + 4, top + fm.ascent() + fm.leading(), number);
+
+
+        }
+        block = nextBlock;
+        top = bottom;
+        bottom = top + (int)blockBoundingRect(block).height();
+        ++blockNumber;
+    }
+
+}
+
+void TextEdit::timerEvent(QTimerEvent *e)
+{
+    if (e->timerId() == autoScrollTimer.timerId()) {
+        const QPoint globalPos = QCursor::pos();
+        const QPoint pos = m_extraArea->mapFromGlobal(globalPos);
+        QRect visible = m_extraArea->rect();
+        verticalScrollBar()->triggerAction( pos.y() < visible.center().y() ?
+                                            QAbstractSlider::SliderSingleStepSub
+                                            : QAbstractSlider::SliderSingleStepAdd);
+        QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+        extraAreaMouseEvent(&ev);
+        int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
+        if (delta < 7)
+            delta = 7;
+        int timeout = 4900 / (delta * delta);
+        autoScrollTimer.start(timeout, this);
+
+    }
+    QPlainTextEdit::timerEvent(e);
+}
+
+
+void TextEdit::mouseMoveEvent(QMouseEvent *e)
+{
+    if (e->buttons() == 0) {
+        QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
+        viewport()->setCursor(collapsedBlock.isValid() ? Qt::PointingHandCursor : Qt::IBeamCursor);
+    } else {
+        QPlainTextEdit::mouseMoveEvent(e);
+    }
+}
+
+void TextEdit::mousePressEvent(QMouseEvent *e)
+{
+    if (e->button() == Qt::LeftButton) {
+        QTextBlock collapsedBlock = collapsedBlockAt(e->pos());
+        if (collapsedBlock.isValid()) {
+            toggleBlockVisible(collapsedBlock);
+            viewport()->setCursor(Qt::IBeamCursor);
+        }
+    }
+    QPlainTextEdit::mousePressEvent(e);
+}
+
+void TextEdit::extraAreaMouseEvent(QMouseEvent *e)
+{
+    QTextCursor cursor = cursorForPosition(QPoint(0, e->pos().y()));
+    cursor.setPosition(cursor.block().position());
+
+    int markWidth;
+    extraAreaWidth(&markWidth);
+
+    if (e->type() == QEvent::MouseMove && e->buttons() == 0) { // mouse tracking
+        bool hand = (e->pos().x() <= markWidth
+                     || (TextBlockUserData::canCollapse(cursor.block())
+                         && collapseBox(cursor.block()).contains(e->pos())));
+        if (hand != (m_extraArea->cursor().shape() == Qt::PointingHandCursor))
+            m_extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
+        return;
+    }
+
+
+    if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) {
+        if (e->button() == Qt::LeftButton) {
+            if (TextBlockUserData::canCollapse(cursor.block())
+                && collapseBox(cursor.block()).contains(e->pos())) {
+                setTextCursor(cursor);
+                toggleBlockVisible(cursor.block());
+            } else if (e->pos().x() > markWidth) {
+                QTextCursor selection = cursor;
+                extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
+                selection.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
+                setTextCursor(selection);
+            } else {
+                extraAreaToggleBreakpointBlockNumber = cursor.blockNumber();
+            }
+        }
+    } else if (extraAreaSelectionAnchorBlockNumber >= 0) {
+        QTextCursor selection = cursor;
+        if (e->type() == QEvent::MouseMove) {
+            QTextBlock anchorBlock = document()->findBlockByNumber(extraAreaSelectionAnchorBlockNumber);
+            if (extraAreaSelectionAnchorBlockNumber == cursor.blockNumber()) {
+                selection.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
+            } else {
+                selection.setPosition(anchorBlock.position() +
+                                      (cursor.blockNumber() <= extraAreaSelectionAnchorBlockNumber ?
+                                       anchorBlock.length() - (anchorBlock.next().isValid()?0:1) : 0));
+                selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
+            }
+
+            if (e->pos().y() >= 0 && e->pos().y() <= m_extraArea->height())
+                autoScrollTimer.stop();
+            else if (!autoScrollTimer.isActive())
+                autoScrollTimer.start(100, this);
+
+        } else {
+            autoScrollTimer.stop();
+            extraAreaSelectionAnchorBlockNumber = -1;
+            return;
+        }
+        setTextCursor(selection);
+    } else if (extraAreaToggleBreakpointBlockNumber >= 0) {
+        if (e->type() == QEvent::MouseButtonRelease && e->button() == Qt::LeftButton) {
+            if (cursor.blockNumber() == extraAreaToggleBreakpointBlockNumber) {
+                bool b = false;
+                if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(cursor.block()))
+                    b = userData->breakpoint();
+                setBreakpoint_helper(extraAreaToggleBreakpointBlockNumber, !b);
+            }
+            extraAreaToggleBreakpointBlockNumber = -1;
+        }
+    }
+}
+
+void TextEdit::slotCursorPositionChanged()
+{
+
+    QList<QTextEdit::ExtraSelection> extraSelections;
+
+    if (true || !isReadOnly()){ // the line selection
+
+        QTextEdit::ExtraSelection sel;
+
+        QTextCursor cursor = textCursor();
+        const QColor fg = palette().color(QPalette::Highlight);
+        const QColor bg = palette().color(QPalette::Base);
+        QColor col;
+        const qreal ratio = 0.15;
+        col.setRedF(fg.redF() * ratio + bg.redF() * (1 - ratio));
+        col.setGreenF(fg.greenF() * ratio + bg.greenF() * (1 - ratio));
+        col.setBlueF(fg.blueF() * ratio + bg.blueF() * (1 - ratio));
+        sel.format.setBackground(col);
+        sel.format.setProperty(QTextFormat::FullWidthSelection, true);
+        sel.cursor = cursor;
+        sel.cursor.clearSelection();
+        extraSelections.append(sel);
+
+        m_parenthesesMatchingTimer->start(50);
+   }
+
+    setExtraSelections(extraSelections);
+}
+
+void TextEdit::slotToggleBookmark()
+{
+    QTextBlock block = textCursor().block();
+    bool b = false;
+    if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+        b = userData->bookmark();
+    setBookmark_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotToggleBreakpoint()
+{
+    QTextBlock block = textCursor().block();
+    bool b = false;
+    if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+        b = userData->breakpoint();
+    setBreakpoint_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotTogglePendingBreakpoint()
+{
+    QTextBlock block = textCursor().block();
+    bool b = false;
+    if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+        b = userData->pendingBreakpoint();
+    setPendingBreakpoint_helper(block.blockNumber(), !b);
+}
+
+void TextEdit::slotSetExecutionBlock()
+{
+    setExecutionBlockNumber(textCursor().block().blockNumber());
+}
+
+
+void TextBlockUserData::setCollapse(const QTextBlock& block, bool visible)
+{
+    QTextBlock info = block;
+    if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->collapse() == CollapseAfter)
+        ;
+    else if (block.next().userData()
+             && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+             == TextBlockUserData::CollapseThis)
+        info = block.next();
+    else
+        return;
+    int pos = static_cast<TextBlockUserData*>(info.userData())->collapseAtPos();
+    if (pos < 0)
+        return;
+    QTextCursor cursor(info);
+    cursor.setPosition(cursor.position() + pos);
+    if (matchCursorForward(&cursor) != Match) {
+        if (visible) {
+            // no match, at least unfold!
+            QTextBlock b = block.next();
+            while (b.isValid() && !b.isVisible()) {
+                b.setVisible(true);
+                b = b.next();
+            }
+        }
+        return;
+    }
+
+    QTextBlock b = block.next();
+    while (b < cursor.block()) {
+        b.setVisible(visible);
+        b = b.next();
+    }
+
+    Q_ASSERT(cursor.block() == b);
+    int relPos = cursor.position() - b.position();
+    QString s = b.text().mid(relPos).trimmed();
+    bool collapseIncludesClosure = (s.isEmpty() || s == QLatin1String(";"));
+    if (collapseIncludesClosure)
+        b.setVisible(visible);
+    static_cast<TextBlockUserData*>(info.userData())->setCollapseIncludesClosure(collapseIncludesClosure);
+}
+
+void TextEdit::toggleBlockVisible(const QTextBlock &block)
+{
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+    Q_ASSERT(documentLayout);
+
+    if (!TextBlockUserData::canCollapse(block))
+        return;
+
+    bool visible = block.next().isVisible();
+    TextBlockUserData::setCollapse(block, !visible);
+    documentLayout->requestUpdate();
+}
+
+void TextEdit::slotToggleBlockVisible()
+{
+    toggleBlockVisible(textCursor().block());
+}
+
+
+QChar TextEdit::charAt(int pos) const
+{
+    QTextCursor c = textCursor();
+
+    if (pos < 0)
+        return QChar();
+
+    c.movePosition(QTextCursor::End);
+    if (pos >= c.position())
+        return QChar();
+
+    c.setPosition(pos);
+    c.setPosition(pos + 1, QTextCursor::KeepAnchor);
+
+    return c.selectedText().at(0);
+}
+
+
+void TextEdit::keyPressEvent(QKeyEvent *e)
+{
+    switch (e->key()) {
+    case Qt::Key_Enter:
+    case Qt::Key_Return:
+        if (m_tabsettings.m_autoIndent) {
+            QTextCursor cursor = textCursor();
+            cursor.insertBlock();
+            const QString leftText = cursor.block().text().left(cursor.position() - cursor.block().position());
+            if (cursor.hasSelection() || leftText.simplified().isEmpty()) {
+                indent(document(), cursor, QChar::Null);
+            }
+            e->accept();
+            setTextCursor(cursor);
+            return;
+        }
+        break;
+    case Qt::Key_Tab:
+        if (m_tabsettings.m_spacesForTabs) {
+            indentSelection();
+            e->accept();
+            return;
+        }
+        break;
+    case Qt::Key_Backtab:
+        if (m_tabsettings.m_indentSelection) {
+            unIndentSelection();
+            e->accept();
+            return;
+        }
+        break;
+    case Qt::Key_Home:
+        if (!(e == QKeySequence::MoveToStartOfDocument)) {
+            handleHomeKey(e->modifiers() == Qt::ShiftModifier);
+            e->accept();
+            return;
+        }
+        break;
+    case Qt::Key_Left:
+    case Qt::Key_Right:
+        if ((e->modifiers() == Qt::NoModifier) && textCursor().hasSelection()) {
+            handleArrowKeys(e->key());
+            e->accept();
+            return;
+        }
+        break;
+    case Qt::Key_BraceLeft:
+    case Qt::Key_BraceRight: {
+        const QString text = e->text();
+        if (!text.isEmpty()) {
+            const QChar c = text.at(0);
+            if (c == QLatin1Char('}') || c == QLatin1Char('{')) {
+                QTextCursor cursor = textCursor();
+                cursor.insertText(QString(c));
+                const QString leftText = cursor.block().text().left(cursor.position() - 1 - cursor.block().position());
+                if (cursor.hasSelection() || leftText.simplified().isEmpty()) {
+                    indent(document(), cursor, c);
+                }
+                TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(document()->documentLayout());
+                Q_ASSERT(documentLayout);
+                documentLayout->requestUpdate(); // a bit drastic
+                e->accept();
+                setTextCursor(cursor);
+                return;
+            }
+        }
+    } break;
+
+    default:
+        break;
+    }
+    QPlainTextEdit::keyPressEvent(e);
+    if (e->key() == Qt::Key_Delete)
+        slotCursorPositionChanged(); // parentheses matching
+}
+
+void TextEdit::unIndentSelection()
+{
+    QTextCursor cursor = textCursor();
+    cursor.beginEditBlock();
+    const QString indent(m_tabsettings.m_indentSize, QLatin1Char(' '));
+
+    const int anchor = cursor.anchor();
+    const int pos = cursor.position();
+    int indents = 0;
+    const bool reversed = (anchor > pos);
+    int startPos = (reversed ? pos : anchor);
+    int endPos = (reversed ? anchor : pos);
+    const QTextBlock startBlock = document()->findBlock(reversed ? pos : anchor);
+    QTextBlock lastBlock = document()->findBlock(reversed ? anchor : pos);
+    if ((lastBlock.position() == pos || lastBlock.position() == anchor) && startBlock != lastBlock)
+        lastBlock = lastBlock.previous();
+
+    QTextBlock curBlock = startBlock;
+    while(curBlock.previous() != lastBlock) {
+        if (curBlock.text().startsWith(indent)) {
+            cursor.setPosition(curBlock.position());
+            cursor.setPosition(curBlock.position() + m_tabsettings.m_indentSize, QTextCursor::KeepAnchor);
+            cursor.removeSelectedText();
+            ++indents;
+        }
+        curBlock = curBlock.next();
+    }
+
+    startPos = startBlock.position();
+    endPos -= m_tabsettings.m_indentSize * (indents);
+    if (endPos < startPos)
+        endPos = startPos;
+    cursor.setPosition(reversed ? endPos : startPos);
+    cursor.setPosition(reversed ? startPos : endPos, QTextCursor::KeepAnchor);
+
+    cursor.endEditBlock();
+    setTextCursor(cursor);
+}
+
+void TextEdit::indentSelection()
+{
+    QTextCursor cursor = textCursor();
+    cursor.beginEditBlock();
+
+    const QString indent(m_tabsettings.m_indentSize, QLatin1Char(' '));
+    if (cursor.hasSelection() && m_tabsettings.m_indentSelection) {
+        int anchor = cursor.anchor();
+        int pos = cursor.position();
+        int indents = 0;
+        const bool reversed = (anchor > pos);
+        const QTextBlock startBlock = document()->findBlock(reversed ? pos : anchor);
+        QTextBlock lastBlock = document()->findBlock(reversed ? anchor : pos);
+        if ((lastBlock.position() == pos || lastBlock.position() == anchor) && startBlock != lastBlock)
+            lastBlock = lastBlock.previous();
+
+        QTextBlock curBlock = startBlock;
+        while(curBlock.previous() != lastBlock) {
+            cursor.setPosition(curBlock.position());
+            cursor.insertText(indent);
+            curBlock = curBlock.next();
+            ++indents;
+        }
+
+        if (reversed) {
+            pos = startBlock.position();
+            anchor += m_tabsettings.m_indentSize * (indents);
+        } else {
+            anchor = startBlock.position();
+            pos += m_tabsettings.m_indentSize * (indents);
+        }
+        cursor.setPosition(anchor);
+        cursor.setPosition(pos, QTextCursor::KeepAnchor);
+    } else {
+        cursor.insertText(indent);
+    }
+    cursor.endEditBlock();
+    setTextCursor(cursor);
+}
+
+void TextEdit::handleHomeKey(bool anchor)
+{
+    QTextCursor cursor = textCursor();
+    QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
+
+    if (anchor)
+        mode = QTextCursor::KeepAnchor;
+
+    const int initpos = cursor.position();
+    int pos = cursor.block().position();
+
+    while (charAt(pos).category() == QChar::Separator_Space) {
+        ++pos;
+    }
+
+    // Go to the start of the block when we're already at the start of the text
+    if (pos == initpos)
+        pos = cursor.block().position();
+
+    cursor.setPosition(pos, mode);
+    setTextCursor(cursor);
+}
+
+void TextEdit::handleArrowKeys(int key)
+{
+    QTextCursor cursor = textCursor();
+    if (key == Qt::Key_Left)
+        cursor.setPosition(cursor.selectionStart());
+    else if (key == Qt::Key_Right)
+        cursor.setPosition(cursor.selectionEnd());
+    setTextCursor(cursor);
+}
+
+
+void TextEdit::format()
+{
+    QTextCursor cursor = textCursor();
+    if (!cursor.hasSelection())
+        return;
+
+    const int start = cursor.selectionStart();
+    int end = cursor.selectionEnd();
+
+    const QTextBlock endblock = document()->findBlock(end - 1);
+    indent(document(), cursor, QChar::Null);
+
+    cursor.setPosition(start);
+    end = endblock.position() + endblock.length();
+    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, end-start);
+    setTextCursor(cursor);
+}
+
+void TextEdit::setTabSettings(const TabSettings &ts)
+{
+     m_tabsettings = ts;
+}
+
+void TextEdit::indentBlock(QTextDocument *, QTextBlock, QChar)
+{
+}
+
+void TextEdit::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
+{
+    if (cursor.hasSelection()) {
+        QTextBlock block = doc->findBlock(cursor.selectionStart());
+        const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();
+        do {
+            indentBlock(doc, block, typedChar);
+            block = block.next();
+        } while (block.isValid() && block != end);
+    } else {
+        indentBlock(doc, cursor.block(), typedChar);
+    }
+}
+
+
+QList<int> TextEdit::bookmarks() const
+{
+    QList<int> list;
+    QTextBlock block = document()->begin();
+    int blockNumber = 0;
+    while (block.isValid()) {
+        if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+            if (userData->bookmark())
+                list += blockNumber;
+        block = block.next();
+        ++blockNumber;
+    }
+    return list;
+}
+
+QList<int> TextEdit::breakpoints() const
+{
+    QList<int> list;
+    QTextBlock block = document()->begin();
+    int blockNumber = 0;
+    while (block.isValid()) {
+        if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+            if (userData->breakpoint())
+                list += blockNumber;
+        block = block.next();
+        ++blockNumber;
+    }
+    return list;
+}
+
+void TextEdit::setBookmark_helper(int blockNumber, bool b, bool update)
+{
+    QTextDocument *doc = document();
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+    Q_ASSERT(documentLayout);
+    QTextBlock block = doc->findBlockByNumber(blockNumber);
+    if (block.isValid()) {
+        TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+        bool wasSet = userData && userData->bookmark();
+        if (b != wasSet) {
+            if (!userData)
+                userData = TextEditDocumentLayout::userData(block);
+            userData->setBookmark(b);
+            int diff = b ? 1 : -1;
+            documentLayout->markCount += diff;
+            if (userData->breakpoint())
+                documentLayout->doubleMarkCount += diff;
+        }
+    }
+    if (update)
+        documentLayout->requestUpdate();
+}
+
+void TextEdit::setBreakpoint_helper(int blockNumber, bool b, bool update)
+{
+    QTextDocument *doc = document();
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+    Q_ASSERT(documentLayout);
+    QTextBlock block = doc->findBlockByNumber(blockNumber);
+
+    if (block.isValid()) {
+        TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+        bool wasSet = userData && userData->breakpoint();
+        if (b != wasSet) {
+            if (!userData)
+                userData = TextEditDocumentLayout::userData(block);
+            setPendingBreakpoint_helper(blockNumber, false, false);
+            userData->setBreakpoint(b);
+            int diff = b ? 1 : -1;
+            documentLayout->markCount += diff;
+            if (userData->bookmark())
+                documentLayout->doubleMarkCount += diff;
+            emit breakpointToggled(blockNumber + baseLine, b);
+        }
+    }
+    if (update)
+        documentLayout->requestUpdate();
+}
+
+void TextEdit::setPendingBreakpoint_helper(int blockNumber, bool b, bool update)
+{
+    QTextDocument *doc = document();
+    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
+    Q_ASSERT(documentLayout);
+    QTextBlock block = doc->findBlockByNumber(blockNumber);
+
+    if (block.isValid()) {
+        TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block);
+        bool wasSet = userData && userData->pendingBreakpoint();
+        if (b != wasSet) {
+            if (!userData)
+                userData = TextEditDocumentLayout::userData(block);
+            setBreakpoint_helper(blockNumber, false, false);
+            userData->setPendingBreakpoint(b);
+            int diff = b ? 1 : -1;
+            documentLayout->markCount += diff;
+            if (userData->bookmark())
+                documentLayout->doubleMarkCount += diff;
+        }
+    }
+    if (update)
+        documentLayout->requestUpdate();
+}
+
+void TextEdit::setBookmarks(const QList<int> &blockNumbers)
+{
+    Q_ASSERT(qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()));
+    QTextBlock block = document()->begin();
+    while (block.isValid()) {
+        if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+            userData->setBookmark(false);
+        block = block.next();
+    }
+    foreach (int blockNumber, blockNumbers) {
+        setBookmark_helper(blockNumber, true, blockNumber == blockNumbers.last());
+    }
+}
+
+
+void TextEdit::setBreakpoints(const QList<int> &blockNumbers)
+{
+    Q_ASSERT(qobject_cast<TextEditDocumentLayout*>(document()->documentLayout()));
+    QTextBlock block = document()->begin();
+    while (block.isValid()) {
+        if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(block))
+            userData->setBreakpoint(false);
+        block = block.next();
+    }
+    foreach (int blockNumber, blockNumbers) {
+        setBreakpoint_helper(blockNumber, true, blockNumber == blockNumbers.last());
+    }
+}
+
+
+void TextEdit::markBlocksAsChanged(QList<int> blockNumbers) {
+    QTextBlock block = document()->begin();
+    while (block.isValid()) {
+        if (block.revision() < 0)
+            block.setRevision(-block.revision() - 1);
+        block = block.next();
+    }
+    foreach(int blockNumber, blockNumbers) {
+        QTextBlock block = document()->findBlockByNumber(blockNumber);
+        if (block.isValid())
+            block.setRevision(-block.revision() - 1);
+    }
+}
+
+
+
+TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c)
+{
+    if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+        return NoMatch;
+
+    Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+    Parenthesis openParen, closedParen;
+    QTextBlock closedParenParag = cursor->block();
+
+    const int cursorPos = cursor->position() - closedParenParag.position();
+    int i = 0;
+    int ignore = 0;
+    bool foundOpen = false;
+    for (;;) {
+        if (!foundOpen) {
+            if (i >= parenList.count())
+                return NoMatch;
+            openParen = parenList.at(i);
+            if (openParen.pos != cursorPos) {
+                ++i;
+                continue;
+            } else {
+                foundOpen = true;
+                ++i;
+            }
+        }
+
+        if (i >= parenList.count()) {
+            for (;;) {
+                closedParenParag = closedParenParag.next();
+                if (!closedParenParag.isValid())
+                    return NoMatch;
+                if (TextEditDocumentLayout::hasParentheses(closedParenParag)) {
+                    parenList = TextEditDocumentLayout::parentheses(closedParenParag);
+                    break;
+                }
+            }
+            i = 0;
+        }
+
+        closedParen = parenList.at(i);
+        if (closedParen.type == Parenthesis::Opened) {
+            ignore++;
+            ++i;
+            continue;
+        } else {
+            if (ignore > 0) {
+                ignore--;
+                ++i;
+                continue;
+            }
+
+            cursor->clearSelection();
+            cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
+
+            if (c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}')
+                || c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')')
+                || c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']')
+               )
+                return Mismatch;
+
+            return Match;
+        }
+    }
+}
+
+TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c)
+{
+
+    if (!TextEditDocumentLayout::hasParentheses(cursor->block()))
+        return NoMatch;
+
+    Parentheses parenList = TextEditDocumentLayout::parentheses(cursor->block());
+    Parenthesis openParen, closedParen;
+    QTextBlock openParenParag = cursor->block();
+
+    const int cursorPos = cursor->position() - openParenParag.position();
+    int i = parenList.count() - 1;
+    int ignore = 0;
+    bool foundClosed = false;
+    for (;;) {
+        if (!foundClosed) {
+            if (i < 0)
+                return NoMatch;
+            closedParen = parenList.at(i);
+            if (closedParen.pos != cursorPos - 1) {
+                --i;
+                continue;
+            } else {
+                foundClosed = true;
+                --i;
+            }
+        }
+
+        if (i < 0) {
+            for (;;) {
+                openParenParag = openParenParag.previous();
+                if (!openParenParag.isValid())
+                    return NoMatch;
+
+                if (TextEditDocumentLayout::hasParentheses(openParenParag)) {
+                    parenList = TextEditDocumentLayout::parentheses(openParenParag);
+                    break;
+                }
+            }
+            i = parenList.count() - 1;
+        }
+
+        openParen = parenList.at(i);
+        if (openParen.type == Parenthesis::Closed) {
+            ignore++;
+            --i;
+            continue;
+        } else {
+            if (ignore > 0) {
+                ignore--;
+                --i;
+                continue;
+            }
+
+            cursor->clearSelection();
+            cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
+
+            if ( c == '}' && openParen.chr != '{' ||
+                    c == ')' && openParen.chr != '(' ||
+                    c == ']' && openParen.chr != '[' )
+                return Mismatch;
+
+            return Match;
+        }
+    }
+}
+TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor)
+{
+    cursor->clearSelection();
+    const QTextBlock block = cursor->block();
+
+    if (!TextEditDocumentLayout::hasParentheses(block))
+        return NoMatch;
+
+    const int relPos = cursor->position() - block.position();
+
+    Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+    const Parentheses::const_iterator cend = parentheses.constEnd();
+    for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+        const Parenthesis &paren = *it;
+        if (paren.pos == relPos - 1
+            && paren.type == Parenthesis::Closed) {
+            return checkClosedParenthesis(cursor, paren.chr);
+        }
+    }
+    return NoMatch;
+}
+
+TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor)
+{
+    cursor->clearSelection();
+    const QTextBlock block = cursor->block();
+
+    if (!TextEditDocumentLayout::hasParentheses(block))
+        return NoMatch;
+
+    const int relPos = cursor->position() - block.position();
+
+    Parentheses parentheses = TextEditDocumentLayout::parentheses(block);
+    const Parentheses::const_iterator cend = parentheses.constEnd();
+    for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
+        const Parenthesis &paren = *it;
+        if (paren.pos == relPos
+            && paren.type == Parenthesis::Opened) {
+            return checkOpenParenthesis(cursor, paren.chr);
+        }
+    }
+    return NoMatch;
+}
+
+void TextEdit::matchParentheses()
+{
+    if (isReadOnly())
+        return;
+
+    QTextCursor backwardMatch = textCursor();
+    QTextCursor forwardMatch = textCursor();
+    const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
+    const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);
+
+    if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch)
+        return;
+
+    QList<QTextEdit::ExtraSelection> extraSelections = this->extraSelections();
+
+    if (backwardMatch.hasSelection()) {
+        QTextEdit::ExtraSelection sel;
+        if (backwardMatchType == TextBlockUserData::Mismatch) {
+            sel.cursor = backwardMatch;
+            sel.format = m_mismatchFormat;
+        } else {
+
+            if (m_formatRange) {
+                sel.cursor = backwardMatch;
+                sel.format = m_rangeFormat;
+                extraSelections.append(sel);
+            }
+
+            sel.cursor = backwardMatch;
+            sel.format = m_matchFormat;
+
+            sel.cursor.setPosition(backwardMatch.selectionStart());
+            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+            extraSelections.append(sel);
+
+            sel.cursor.setPosition(backwardMatch.selectionEnd());
+            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+        }
+        extraSelections.append(sel);
+    }
+
+    if (forwardMatch.hasSelection()) {
+        QTextEdit::ExtraSelection sel;
+        if (forwardMatchType == TextBlockUserData::Mismatch) {
+            sel.cursor = forwardMatch;
+            sel.format = m_mismatchFormat;
+        } else {
+
+            if (m_formatRange) {
+                sel.cursor = forwardMatch;
+                sel.format = m_rangeFormat;
+                extraSelections.append(sel);
+            }
+
+            sel.cursor = forwardMatch;
+            sel.format = m_matchFormat;
+
+            sel.cursor.setPosition(forwardMatch.selectionStart());
+            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
+            extraSelections.append(sel);
+
+            sel.cursor.setPosition(forwardMatch.selectionEnd());
+            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
+        }
+        extraSelections.append(sel);
+    }
+    setExtraSelections(extraSelections);
+}
+
diff --git a/src/editor/textedit.h b/src/editor/textedit.h
new file mode 100644 (file)
index 0000000..52fe8ef
--- /dev/null
@@ -0,0 +1,264 @@
+/****************************************************************************
+**
+** Copyright (C) 2007-2008 Trolltech ASA. All rights reserved.
+**
+** This file is part of the Qt Script Debug project on Trolltech Labs.
+**
+** This file may be used under the terms of the GNU General Public
+** License version 2.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of
+** this file.  Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+** http://www.trolltech.com/products/qt/opensource.html
+**
+** If you are unsure which license is appropriate for your use, please
+** review the following information:
+** http://www.trolltech.com/products/qt/licensing.html or contact the
+** sales department at sales@trolltech.com.
+**
+** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+**
+****************************************************************************/
+
+#ifndef TEXTEDIT_H
+#define TEXTEDIT_H
+
+#include <QtGui/QPlainTextEdit>
+#include "tabsettings.h"
+#include <QtGui/QTextCharFormat>
+#include <QtGui/QTextBlockUserData>
+#include <QtGui/QIcon>
+#include <QtCore/QBasicTimer>
+#include <QtCore/QDebug>
+
+
+struct Parenthesis;
+typedef QVector<Parenthesis> Parentheses;
+struct Parenthesis
+{
+    enum Type { Opened, Closed };
+
+    inline Parenthesis() : type(Opened), pos(-1) {}
+    inline Parenthesis(Type t, QChar c, int position)
+        : type(t), chr(c), pos(position) {}
+    Type type;
+    QChar chr;
+    int pos;
+    static int collapseAtPos(const Parentheses &parentheses);
+};
+
+class TextBlockUserData : public QTextBlockUserData {
+public:
+
+    enum Collapse { CollapseThis, CollapseAfter, NoCollapse };
+
+    inline TextBlockUserData()
+        :m_breakpoint(false), m_pendingBreakpoint(false), m_bookmark(false),
+         m_collapseIncludesClosure(false),
+         m_collapse(NoCollapse) {}
+    inline bool breakpoint() const { return m_breakpoint; }
+    inline bool pendingBreakpoint() const { return m_pendingBreakpoint; }
+    inline bool bookmark() const { return m_bookmark; }
+    inline Collapse collapse() const { return m_collapse; }
+    inline bool collapseIncludesClosure() const { return m_collapseIncludesClosure; }
+    inline void setBookmark(bool b) { m_bookmark = b; }
+    inline void setBreakpoint(bool b){ m_breakpoint = b; }
+    inline void setPendingBreakpoint(bool b){ m_pendingBreakpoint = b; }
+    inline void setCollapse(Collapse c) { m_collapse = c; }
+    inline void setCollapseIncludesClosure(bool b) { m_collapseIncludesClosure = b; }
+
+    inline void setParentheses(const Parentheses &parentheses) { m_parentheses = parentheses; }
+    inline void clearParentheses() { m_parentheses.clear(); }
+    inline const Parentheses &parentheses() const { return m_parentheses; }
+
+    inline bool hasParentheses() const { return !m_parentheses.isEmpty(); }
+
+    inline static bool canCollapse(const QTextBlock& block) {
+        return (block.userData()
+                && static_cast<TextBlockUserData*>(block.userData())->collapse()
+                == CollapseAfter)
+            || (block.next().userData()
+                && static_cast<TextBlockUserData*>(block.next().userData())->collapse()
+                == TextBlockUserData::CollapseThis);
+    }
+
+    static void setCollapse(const QTextBlock& block, bool visible);
+
+    bool hasClosingCollapse() const;
+    int collapseAtPos() const;
+
+    enum MatchType { NoMatch, Match, Mismatch  };
+    static MatchType checkOpenParenthesis(QTextCursor *cursor, QChar c);
+    static MatchType checkClosedParenthesis(QTextCursor *cursor, QChar c);
+    static MatchType matchCursorBackward(QTextCursor *cursor);
+    static MatchType matchCursorForward(QTextCursor *cursor);
+
+
+
+private:
+    bool m_breakpoint;
+    bool m_pendingBreakpoint;
+    bool m_bookmark;
+    bool m_collapseIncludesClosure;
+    Collapse m_collapse;
+    Parentheses m_parentheses;
+};
+
+class TextEditDocumentLayout : public QPlainTextDocumentLayout
+{
+    Q_OBJECT
+
+public:
+
+    TextEditDocumentLayout(QTextDocument *doc):QPlainTextDocumentLayout(doc) {
+        lastSaveRevision = markCount = doubleMarkCount = 0;
+        m_executionBlockNumber = -1;
+    }
+    ~TextEditDocumentLayout(){}
+
+    QRectF blockBoundingRect(const QTextBlock &block) const;
+
+    static void setParentheses(const QTextBlock &block, const Parentheses &parentheses);
+    static void clearParentheses(const QTextBlock &block) { setParentheses(block, Parentheses());}
+    static Parentheses parentheses(const QTextBlock &block);
+    static bool hasParentheses(const QTextBlock &block);
+
+    static TextBlockUserData *testUserData(const QTextBlock &block) {
+        return static_cast<TextBlockUserData*>(block.userData());
+    }
+    static TextBlockUserData *userData(const QTextBlock &block) {
+        TextBlockUserData *data = static_cast<TextBlockUserData*>(block.userData());
+        if (!data && block.isValid())
+            const_cast<QTextBlock &>(block).setUserData((data = new TextBlockUserData));
+        return data;
+    }
+
+    inline void setExecutionBlockNumber(int blockNumber) {
+        m_executionBlockNumber = blockNumber;
+        requestUpdate();
+    }
+
+    inline int executionBlockNumber() const { return m_executionBlockNumber; }
+
+    int lastSaveRevision;
+    int markCount;
+    int doubleMarkCount;
+
+    int m_executionBlockNumber;
+};
+
+class TextEdit : public QPlainTextEdit
+{
+    Q_OBJECT
+public:
+    explicit TextEdit(QWidget *parent = 0);
+    ~TextEdit();
+
+    QWidget *extraArea() const;
+    virtual int extraAreaWidth(int *markWidthPtr = 0) const;
+    virtual void extraAreaPaintEvent(QPaintEvent *);
+    virtual void extraAreaMouseEvent(QMouseEvent *);
+
+
+    inline const TabSettings &tabSettings() const { return m_tabsettings; }
+
+    QList<int> bookmarks() const;
+    void setBookmarks(const QList<int> &blockNumbers);
+    QList<int> breakpoints() const;
+    void setBreakpoints(const QList<int> &blockNumbers);
+    QList<int> pendingBreakpoints() const;
+    void setPendingBreakpoints(const QList<int> &blockNumbers);
+
+
+    void markBlocksAsChanged(QList<int> blockNumbers);
+
+    int executionBlockNumber() const;
+    void setExecutionBlockNumber(int blockNumber);
+
+    int baseLineNumber() const { return baseLine; }
+    void setBaseLineNumber(int lineNumber) { baseLine = lineNumber; }
+    int cursorLineNumber() const { return textCursor().blockNumber() + baseLine; }
+
+public Q_SLOTS:
+    void setTabSettings(const TabSettings &);
+    virtual void format();
+
+Q_SIGNALS:
+    void breakpointToggled(int lineNumber, bool set);
+
+protected:
+
+    bool viewportEvent(QEvent *event);
+    QChar charAt(int pos) const;
+
+    void resizeEvent(QResizeEvent *);
+    void paintEvent(QPaintEvent *);
+    void keyPressEvent(QKeyEvent *);
+    void timerEvent(QTimerEvent *);
+    void mouseMoveEvent(QMouseEvent *);
+    void mousePressEvent(QMouseEvent *);
+
+    // Indent a text block based on previous line. Default does nothing
+    virtual void indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar);
+    // Indent at cursor. Calls indentBlock for selection or current line.
+    virtual void indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar);
+
+
+protected Q_SLOTS:
+    virtual void slotUpdateExtraAreaWidth();
+    virtual void slotModificationChanged(bool);
+    virtual void slotUpdateRequest(const QRect &r, int dy);
+    virtual void slotCursorPositionChanged();
+
+public slots:
+    void slotToggleBookmark();
+    void slotToggleBreakpoint();
+    void slotTogglePendingBreakpoint();
+    void slotToggleBlockVisible();
+    void slotSetExecutionBlock();
+private:
+
+    void setBookmark_helper(int blockNumber, bool b = true, bool update = true);
+    void setBreakpoint_helper(int blockNumber, bool b = true, bool update = true);
+    void setPendingBreakpoint_helper(int blockNumber, bool b = true, bool update = true);
+
+    void indentSelection();
+    void handleHomeKey(bool anchor);
+    void handleArrowKeys(int key);
+    void unIndentSelection();
+
+    void toggleBlockVisible(const QTextBlock &block);
+    QRect collapseBox(const QTextBlock &block);
+
+    QTextBlock collapsedBlockAt(const QPoint &pos, QString *text = 0, QRect *box = 0) const;
+
+    // parentheses matcher
+private slots:
+    void matchParentheses();
+private:
+    bool m_formatRange;
+    QTextCharFormat m_matchFormat;
+    QTextCharFormat m_mismatchFormat;
+    QTextCharFormat m_rangeFormat;
+    QTimer *m_parenthesesMatchingTimer;
+    // end parentheses matcher
+
+
+    QWidget *m_extraArea;
+    TabSettings m_tabsettings;
+    QIcon executionIcon;
+    QIcon bookmarkIcon;
+    QIcon breakpointIcon;
+    QIcon breakpointPendingIcon;
+
+    int extraAreaSelectionAnchorBlockNumber;
+    int extraAreaToggleBreakpointBlockNumber;
+
+    QBasicTimer autoScrollTimer;
+
+    int baseLine;
+};
+
+
+#endif // CPPEDIT_H