Skip to content
Snippets Groups Projects
basetexteditor.cpp 189 KiB
Newer Older
            cursor.clearSelection();
            setTextCursor(cursor);
            return;
        }
con's avatar
con committed
    }

    bool ro = isReadOnly();

    if (d->m_inBlockSelectionMode) {
        if (e == QKeySequence::Cut) {
            if (!ro) {
                cut();
                e->accept();
                return;
            }
        } else if (e == QKeySequence::Delete || e->key() == Qt::Key_Backspace) {
con's avatar
con committed
            if (!ro) {
                d->removeBlockSelection();
                e->accept();
                return;
            }
        } else if (e == QKeySequence::Paste) {
            if (!ro) {
                d->removeBlockSelection();
                // continue
            }
con's avatar
con committed
        }
    }


    if (!ro
        && (e == QKeySequence::InsertParagraphSeparator
            || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))
        ) {

        if (d->m_snippetOverlay->isVisible()) {
            e->accept();
            d->m_snippetOverlay->hide();
            d->m_snippetOverlay->clear();
            QTextCursor cursor = textCursor();
            cursor.movePosition(QTextCursor::EndOfBlock);
            setTextCursor(cursor);
            return;
        }


con's avatar
con committed
        QTextCursor cursor = textCursor();
        if (d->m_inBlockSelectionMode)
            cursor.clearSelection();
        const TabSettings &ts = d->m_document->tabSettings();
        cursor.beginEditBlock();

        int extraBlocks = paragraphSeparatorAboutToBeInserted(cursor); // virtual
        if (ts.m_autoIndent) {
con's avatar
con committed
            indent(document(), cursor, QChar::Null);
        } else {
            cursor.insertBlock();

            // After inserting the block, to avoid duplicating whitespace on the same line
            const QString previousBlockText = cursor.block().previous().text();
            cursor.insertText(ts.indentationString(previousBlockText));
con's avatar
con committed
        }
        cursor.endEditBlock();
con's avatar
con committed
        e->accept();

        if (extraBlocks > 0) {
            QTextCursor ensureVisible = cursor;
            while (extraBlocks > 0) {
                --extraBlocks;
                ensureVisible.movePosition(QTextCursor::NextBlock);
            }
            setTextCursor(ensureVisible);
        }

con's avatar
con committed
        setTextCursor(cursor);
        return;
    } else if (!ro
               && (e == QKeySequence::MoveToStartOfBlock
                   || e == QKeySequence::SelectStartOfBlock)){
        if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier))
            d->m_lastEventWasBlockSelectionEvent = true;
        handleHomeKey(e == QKeySequence::SelectStartOfBlock);
        e->accept();
        return;
    } else if (!ro
               && (e == QKeySequence::MoveToStartOfLine
                   || e == QKeySequence::SelectStartOfLine)){
        if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier))
            d->m_lastEventWasBlockSelectionEvent = true;
        QTextCursor cursor = textCursor();
        if (QTextLayout *layout = cursor.block().layout()) {
            if (layout->lineForTextPosition(cursor.position() - cursor.block().position()).lineNumber() == 0) {
                handleHomeKey(e == QKeySequence::SelectStartOfLine);
                e->accept();
                return;
            }
        }
    } else if (!ro
               && e == QKeySequence::DeleteStartOfWord
               && d->m_document->tabSettings().m_autoIndent
               && !textCursor().hasSelection()){
        e->accept();
        QTextCursor c = textCursor();
        int pos = c.position();
        c.movePosition(QTextCursor::PreviousWord);
        int targetpos = c.position();
        forever {
            handleBackspaceKey();
            int cpos = textCursor().position();
            if (cpos == pos || cpos <= targetpos)
                break;
            pos = cpos;
        }
        return;
con's avatar
con committed
    } else switch (e->key()) {


#if 0
    case Qt::Key_Dollar: {
            d->m_overlay->setVisible(!d->m_overlay->isVisible());
            d->m_overlay->setCursor(textCursor());
            e->accept();
con's avatar
con committed
        return;

    } break;
#endif
    case Qt::Key_Tab:
Joel Nordell's avatar
Joel Nordell committed
    case Qt::Key_Backtab: {
con's avatar
con committed
        if (ro) break;
        if (d->m_snippetOverlay->isVisible() && !d->m_snippetOverlay->isEmpty()) {
            d->snippetTabOrBacktab(e->key() == Qt::Key_Tab);
            e->accept();
            return;
        }
Joel Nordell's avatar
Joel Nordell committed
        QTextCursor cursor = textCursor();
        int newPosition;
        if (d->m_document->tabSettings().tabShouldIndent(document(), cursor, &newPosition)) {
            if (newPosition != cursor.position() && !cursor.hasSelection()) {
                cursor.setPosition(newPosition);
                setTextCursor(cursor);
            }
            indent(document(), cursor, QChar::Null);
        } else {
            indentOrUnindent(e->key() == Qt::Key_Tab);
        }
con's avatar
con committed
        e->accept();
        return;
Joel Nordell's avatar
Joel Nordell committed
    } break;
con's avatar
con committed
    case Qt::Key_Backspace:
        if (ro) break;
        if ((e->modifiers() & (Qt::ControlModifier
con's avatar
con committed
                               | Qt::ShiftModifier
                               | Qt::AltModifier
                               | Qt::MetaModifier)) == Qt::NoModifier
            && !textCursor().hasSelection()) {
            handleBackspaceKey();
            e->accept();
            return;
        }
        break;
    case Qt::Key_Up:
    case Qt::Key_Down:
        if (e->modifiers() & Qt::ControlModifier) {
            verticalScrollBar()->triggerAction(
                    e->key() == Qt::Key_Up ? QAbstractSlider::SliderSingleStepSub :
                                             QAbstractSlider::SliderSingleStepAdd);
            e->accept();
            return;
        }
        // fall through
    case Qt::Key_End:
con's avatar
con committed
    case Qt::Key_Right:
    case Qt::Key_Left:
Oswald Buddenhagen's avatar
Oswald Buddenhagen committed
#ifndef Q_WS_MAC
con's avatar
con committed
        if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) {

            d->m_lastEventWasBlockSelectionEvent = true;

            if (d->m_inBlockSelectionMode) {
                if (e->key() == Qt::Key_Right && textCursor().atBlockEnd()) {
                    d->m_blockSelectionExtraX++;
                    viewport()->update();
                    e->accept();
                    return;
                } else if (e->key() == Qt::Key_Left && d->m_blockSelectionExtraX > 0) {
                    d->m_blockSelectionExtraX--;
                    e->accept();
                    viewport()->update();
                    return;
                }
            }

            e = new QKeyEvent(
                e->type(),
                e->key(),
                e->modifiers() & ~Qt::AltModifier,
                e->text(),
                e->isAutoRepeat(),
                e->count()
                );
        }
#endif
        break;
    case Qt::Key_PageUp:
    case Qt::Key_PageDown:
        if (e->modifiers() == Qt::ControlModifier) {
            verticalScrollBar()->triggerAction(
                    e->key() == Qt::Key_PageUp ? QAbstractSlider::SliderPageStepSub :
                                                 QAbstractSlider::SliderPageStepAdd);
            e->accept();
            return;
        }
        break;

    default:
        break;
    }

    if (d->m_inBlockSelectionMode) {
        QString text = e->text();
        if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
            d->removeBlockSelection(text);
            goto skip_event;
        }
    }

mae's avatar
mae committed
    if (e->key() == Qt::Key_H && e->modifiers() ==
#ifdef Q_OS_DARWIN
        Qt::MetaModifier
#else
        Qt::ControlModifier
#endif
        ) {
        universalHelper();
        e->accept();
        return;
    }

    if (d->m_snippetOverlay->isVisible()
        && (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace)) {
        d->snippetCheckCursor(textCursor());
    if (ro || e->text().isEmpty() || !e->text().at(0).isPrint()) {
        QPlainTextEdit::keyPressEvent(e);
    } else if ((e->modifiers() & (Qt::ControlModifier|Qt::AltModifier)) != Qt::ControlModifier){
        QTextCursor cursor = textCursor();
        QString text = e->text();
        QString autoText = autoComplete(cursor, text);
        QChar electricChar;
        if (d->m_document->tabSettings().m_autoIndent) {
                if (isElectricCharacter(c)) {
                    electricChar = c;
                    break;
                }
            }
        }
        if (d->m_snippetOverlay->isVisible())
            d->snippetCheckCursor(cursor);

        bool doEditBlock = !(electricChar.isNull() && autoText.isEmpty());
        if (doEditBlock)
            cursor.beginEditBlock();
        cursor.insertText(text);
        if (!autoText.isEmpty()) {
            int pos = cursor.position();
            cursor.insertText(autoText);
            cursor.setPosition(pos);
        }
            indent(document(), cursor, electricChar);
            cursor.endEditBlock();
        setTextCursor(cursor);
    }
con's avatar
con committed

skip_event:
    if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled)
        d->m_parenthesesMatchingTimer->start(50);

con's avatar
con committed

    if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint())
        emit requestAutoCompletion(editableInterface(), false);

    if (e != original_e)
        delete e;
}

void BaseTextEditor::insertCodeSnippet(const QString &snippet)
{
    QList<QTextEdit::ExtraSelection> selections;
    QTextCursor cursor = textCursor();
    const int startCursorPosition = cursor.position();
    cursor.beginEditBlock();

    if ((snippet.count('$') % 2) != 0) {
        qWarning() << "invalid snippet";
        return;
    }

    int pos = 0;
mae's avatar
mae committed
    QMap<int, int> positions;

    while (pos < snippet.size()) {
        if (snippet.at(pos) != QChar::ObjectReplacementCharacter) {
            const int start = pos;
            do { ++pos; }
            while (pos < snippet.size() && snippet.at(pos) != QChar::ObjectReplacementCharacter);
            cursor.insertText(snippet.mid(start, pos - start));
        } else {
            // the start of a place holder.
            const int start = ++pos;
            for (; pos < snippet.size(); ++pos) {
                if (snippet.at(pos) == QChar::ObjectReplacementCharacter)
                    break;
            }

            Q_ASSERT(pos < snippet.size());
            Q_ASSERT(snippet.at(pos) == QChar::ObjectReplacementCharacter);

            const QString textToInsert = snippet.mid(start, pos - start);

            int cursorPosition = cursor.position();
            cursor.insertText(textToInsert);

mae's avatar
mae committed
            if (textToInsert.isEmpty()) {
                positions.insert(cursorPosition, 0);
            } else {
                positions.insert(cursorPosition-1, textToInsert.length()+1);
            }
mae's avatar
mae committed
    QMapIterator<int,int> it(positions);
    while (it.hasNext()) {
        it.next();
        int length = it.value();
        int position = it.key();

        QTextCursor tc(document());
        tc.setPosition(position);
        tc.setPosition(position + length, QTextCursor::KeepAnchor);
        QTextEdit::ExtraSelection selection;
        selection.cursor = tc;
        selection.format.setBackground(length ? Qt::darkCyan : Qt::darkMagenta);
        selections.append(selection);
    }

    cursor.setPosition(startCursorPosition, QTextCursor::KeepAnchor);
    indent(cursor.document(), cursor, QChar());
mae's avatar
mae committed
    cursor.endEditBlock();
    setExtraSelections(BaseTextEditor::SnippetPlaceholderSelection, selections);

    if (! selections.isEmpty()) {
        const QTextEdit::ExtraSelection &selection = selections.first();

        cursor = textCursor();
        if (selection.cursor.hasSelection()) {
            cursor.setPosition(selection.cursor.selectionStart()+1);
            cursor.setPosition(selection.cursor.selectionEnd(), QTextCursor::KeepAnchor);
        } else {
            cursor.setPosition(selection.cursor.position());
        }
void BaseTextEditor::universalHelper()
{
    // Test function for development. Place your new fangled experiment here to
    // give it proper scrutiny before pushing it onto others.
void BaseTextEditor::setTextCursor(const QTextCursor &cursor)
{
    // workaround for QTextControl bug
    bool selectionChange = cursor.hasSelection() || textCursor().hasSelection();
    QTextCursor c = cursor;
    c.setVisualNavigation(true);
    QPlainTextEdit::setTextCursor(c);
    if (selectionChange)
        slotSelectionChanged();
}

void BaseTextEditor::gotoLine(int line, int column)
con's avatar
con committed
{
    d->m_lastCursorChangeWasInteresting = false; // avoid adding the previous position to history
con's avatar
con committed
    const int blockNumber = line - 1;
    const QTextBlock &block = document()->findBlockByNumber(blockNumber);
    if (block.isValid()) {
        QTextCursor cursor(block);
        if (column > 0) {
            cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
        } else {
            int pos = cursor.position();
            while (characterAt(pos).category() == QChar::Separator_Space) {
                ++pos;
            }
            cursor.setPosition(pos);
        }
        setTextCursor(cursor);
        centerCursor();
    }
    saveCurrentCursorPositionForNavigation();
con's avatar
con committed
}

int BaseTextEditor::position(ITextEditor::PositionOperation posOp, int at) const
{
    QTextCursor tc = textCursor();

    if (at != -1)
        tc.setPosition(at);

    if (posOp == ITextEditor::Current)
        return tc.position();

    switch (posOp) {
    case ITextEditor::EndOfLine:
        tc.movePosition(QTextCursor::EndOfLine);
        return tc.position();
    case ITextEditor::StartOfLine:
        tc.movePosition(QTextCursor::StartOfLine);
        return tc.position();
    case ITextEditor::Anchor:
        if (tc.hasSelection())
            return tc.anchor();
        break;
    case ITextEditor::EndOfDoc:
        tc.movePosition(QTextCursor::End);
        return tc.position();
    default:
        break;
    }

    return -1;
}

void BaseTextEditor::convertPosition(int pos, int *line, int *column) const
{
    QTextBlock block = document()->findBlock(pos);
    if (!block.isValid()) {
        (*line) = -1;
        (*column) = -1;
    } else {
        (*line) = block.blockNumber() + 1;
        (*column) = pos - block.position();
    }
}

QChar BaseTextEditor::characterAt(int pos) const
{
    return document()->characterAt(pos);
}

bool BaseTextEditor::event(QEvent *e)
{
    d->m_contentsChanged = false;
con's avatar
con committed
    switch (e->type()) {
    case QEvent::ShortcutOverride:
        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && d->m_snippetOverlay->isVisible()) {
            e->accept();
            return true;
        }
        e->ignore(); // we are a really nice citizen
con's avatar
con committed
        return true;
con's avatar
con committed
    default:
        break;
    }

    return QPlainTextEdit::event(e);
}

void BaseTextEditor::duplicateFrom(BaseTextEditor *editor)
{
    if (this == editor)
        return;
    setDisplayName(editor->displayName());
    d->m_revisionsVisible = editor->d->m_revisionsVisible;
    if (d->m_document == editor->d->m_document)
        return;
    d->setupDocumentSignals(editor->d->m_document);
    d->m_document = editor->d->m_document;
}

QString BaseTextEditor::displayName() const
{
    return d->m_displayName;
}

void BaseTextEditor::setDisplayName(const QString &title)
{
    d->m_displayName = title;
}

BaseTextDocument *BaseTextEditor::baseTextDocument() const
{
    return d->m_document;
}

void BaseTextEditor::setBaseTextDocument(BaseTextDocument *doc)
{
    if (doc) {
        d->setupDocumentSignals(doc);
        d->m_document = doc;
    }
}

// called before reload
con's avatar
con committed
void BaseTextEditor::memorizeCursorPosition()
{
    d->m_tempState = saveState();
}

// called after reload
con's avatar
con committed
void BaseTextEditor::restoreCursorPosition()
{
    restoreState(d->m_tempState);
    if (d->m_displaySettings.m_autoFoldFirstComment)
        d->collapseLicenseHeader();
con's avatar
con committed
}

QByteArray BaseTextEditor::saveState() const
{
    QByteArray state;
    QDataStream stream(&state, QIODevice::WriteOnly);
    stream << 0; // version number
    stream << verticalScrollBar()->value();
    stream << horizontalScrollBar()->value();
    int line, column;
    convertPosition(textCursor().position(), &line, &column);
    stream << line;
    stream << column;
    return state;
}

bool BaseTextEditor::restoreState(const QByteArray &state)
{
    int version;
    int vval;
    int hval;
    int lval;
    int cval;
    QDataStream stream(state);
    stream >> version;
    stream >> vval;
    stream >> hval;
    stream >> lval;
    stream >> cval;
    d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
    gotoLine(lval, cval);
con's avatar
con committed
    verticalScrollBar()->setValue(vval);
    horizontalScrollBar()->setValue(hval);
    saveCurrentCursorPositionForNavigation();
con's avatar
con committed
    return true;
}

void BaseTextEditor::setDefaultPath(const QString &defaultPath)
{
    baseTextDocument()->setDefaultPath(defaultPath);
}

void BaseTextEditor::setSuggestedFileName(const QString &suggestedFileName)
{
    baseTextDocument()->setSuggestedFileName(suggestedFileName);
}

void BaseTextEditor::setParenthesesMatchingEnabled(bool b)
{
    d->m_parenthesesMatchingEnabled = b;
}

bool BaseTextEditor::isParenthesesMatchingEnabled() const
{
    return d->m_parenthesesMatchingEnabled;
}

void BaseTextEditor::setHighlightCurrentLine(bool b)
{
    d->m_highlightCurrentLine = b;
con's avatar
con committed
}

bool BaseTextEditor::highlightCurrentLine() const
{
    return d->m_highlightCurrentLine;
}

void BaseTextEditor::setLineNumbersVisible(bool b)
{
    d->m_lineNumbersVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::lineNumbersVisible() const
{
    return d->m_lineNumbersVisible;
}

void BaseTextEditor::setMarksVisible(bool b)
{
    d->m_marksVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::marksVisible() const
{
    return d->m_marksVisible;
}

void BaseTextEditor::setRequestMarkEnabled(bool b)
{
    d->m_requestMarkEnabled = b;
}

bool BaseTextEditor::requestMarkEnabled() const
{
    return d->m_requestMarkEnabled;
}

void BaseTextEditor::setLineSeparatorsAllowed(bool b)
{
    d->m_lineSeparatorsAllowed = b;
}

bool BaseTextEditor::lineSeparatorsAllowed() const
{
    return d->m_lineSeparatorsAllowed;
}

void BaseTextEditor::setCodeFoldingVisible(bool b)
{
    d->m_codeFoldingVisible = b && d->m_codeFoldingSupported;
con's avatar
con committed
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::codeFoldingVisible() const
{
    return d->m_codeFoldingVisible;
}

/**
 * Sets whether code folding is supported by the syntax highlighter. When not
 * supported (the default), this makes sure the code folding is not shown.
 *
 * Needs to be called before calling setCodeFoldingVisible.
 */
void BaseTextEditor::setCodeFoldingSupported(bool b)
{
    d->m_codeFoldingSupported = b;
}

bool BaseTextEditor::codeFoldingSupported() const
{
    return d->m_codeFoldingSupported;
}

void BaseTextEditor::setMouseNavigationEnabled(bool b)
{
    d->m_behaviorSettings.m_mouseNavigation = b;
}

bool BaseTextEditor::mouseNavigationEnabled() const
{
    return d->m_behaviorSettings.m_mouseNavigation;
}

void BaseTextEditor::setScrollWheelZoomingEnabled(bool b)
{
    d->m_behaviorSettings.m_scrollWheelZooming = b;
}

bool BaseTextEditor::scrollWheelZoomingEnabled() const
{
    return d->m_behaviorSettings.m_scrollWheelZooming;
con's avatar
con committed
void BaseTextEditor::setRevisionsVisible(bool b)
{
    d->m_revisionsVisible = b;
    slotUpdateExtraAreaWidth();
}

bool BaseTextEditor::revisionsVisible() const
{
    return d->m_revisionsVisible;
}

void BaseTextEditor::setVisibleWrapColumn(int column)
{
    d->m_visibleWrapColumn = column;
    viewport()->update();
}

int BaseTextEditor::visibleWrapColumn() const
{
    return d->m_visibleWrapColumn;
}

//--------- BaseTextEditorPrivate -----------

BaseTextEditorPrivate::BaseTextEditorPrivate()
    :
    m_contentsChanged(false),
    m_lastCursorChangeWasInteresting(false),
con's avatar
con committed
    m_document(new BaseTextDocument()),
    m_parenthesesMatchingEnabled(false),
    m_extraArea(0),
    m_mouseOnCollapsedMarker(false),
con's avatar
con committed
    m_marksVisible(false),
    m_codeFoldingVisible(false),
    m_codeFoldingSupported(false),
con's avatar
con committed
    m_revisionsVisible(false),
    m_lineNumbersVisible(true),
    m_highlightCurrentLine(true),
    m_requestMarkEnabled(true),
    m_lineSeparatorsAllowed(false),
    m_visibleWrapColumn(0),
con's avatar
con committed
    m_editable(0),
    m_actionHack(0),
    m_inBlockSelectionMode(false),
    m_lastEventWasBlockSelectionEvent(false),
    m_moveLineUndoHack(false),
    m_cursorBlockNumber(-1)
con's avatar
con committed
{
}

BaseTextEditorPrivate::~BaseTextEditorPrivate()
{
}

void BaseTextEditorPrivate::setupDocumentSignals(BaseTextDocument *document)
{
    BaseTextDocument *oldDocument = q->baseTextDocument();
    if (oldDocument) {
        q->disconnect(oldDocument->document(), 0, q, 0);
        q->disconnect(oldDocument, 0, q, 0);
    }

    QTextDocument *doc = document->document();
    TextEditDocumentLayout *documentLayout = qobject_cast<TextEditDocumentLayout*>(doc->documentLayout());
    if (!documentLayout) {
        QTextOption opt = doc->defaultTextOption();
        opt.setTextDirection(Qt::LeftToRight);
        opt.setFlags(opt.flags() | QTextOption::IncludeTrailingSpaces
                | QTextOption::AddSpaceForLineAndParagraphSeparators
                );
        doc->setDefaultTextOption(opt);
        documentLayout = new TextEditDocumentLayout(doc);
        doc->setDocumentLayout(documentLayout);
    }


    q->setDocument(doc);
    QObject::connect(documentLayout, SIGNAL(updateBlock(QTextBlock)), q, SLOT(slotUpdateBlockNotify(QTextBlock)));
    QObject::connect(q, SIGNAL(requestBlockUpdate(QTextBlock)), documentLayout, SIGNAL(updateBlock(QTextBlock)));
    QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(changed()));
    QObject::connect(doc, SIGNAL(contentsChange(int,int,int)), q,
        SLOT(editorContentsChange(int,int,int)), Qt::DirectConnection);
    QObject::connect(document, SIGNAL(changed()), q, SIGNAL(changed()));
    QObject::connect(document, SIGNAL(titleChanged(QString)), q, SLOT(setDisplayName(const QString &)));
    QObject::connect(document, SIGNAL(aboutToReload()), q, SLOT(memorizeCursorPosition()));
    QObject::connect(document, SIGNAL(reloaded()), q, SLOT(restoreCursorPosition()));
    q->slotUpdateExtraAreaWidth();
}


void BaseTextEditorPrivate::snippetCheckCursor(const QTextCursor &cursor)
{
    if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
        return;

    QTextCursor start = cursor;
    start.setPosition(cursor.selectionStart());
    QTextCursor end = cursor;
    end.setPosition(cursor.selectionEnd());
    if (!m_snippetOverlay->hasCursorInSelection(start)
        || !m_snippetOverlay->hasCursorInSelection(end)) {
        m_snippetOverlay->setVisible(false);
        m_snippetOverlay->clear();
    }
}

void BaseTextEditorPrivate::snippetTabOrBacktab(bool forward)
{
    if (!m_snippetOverlay->isVisible() || m_snippetOverlay->isEmpty())
        return;
    QTextCursor cursor = q->textCursor();
    OverlaySelection final;
    if (forward) {
        for (int i = 0; i < m_snippetOverlay->m_selections.count(); ++i){
            const OverlaySelection &selection = m_snippetOverlay->m_selections.at(i);
            if (selection.m_cursor_begin.position() >= cursor.position()
                && selection.m_cursor_end.position() > cursor.position()) {
                final = selection;
                break;
            }
        }
    } else {
        for (int i = m_snippetOverlay->m_selections.count()-1; i >= 0; --i){
            const OverlaySelection &selection = m_snippetOverlay->m_selections.at(i);
            if (selection.m_cursor_end.position() < cursor.position()) {
                final = selection;
                break;
            }
        }

    }
    if (final.m_cursor_begin.isNull())
        final = forward ? m_snippetOverlay->m_selections.first() : m_snippetOverlay->m_selections.last();

    if (final.m_cursor_begin.position() == final.m_cursor_end.position()) { // empty tab stop
        cursor.setPosition(final.m_cursor_end.position());
    } else {
        cursor.setPosition(final.m_cursor_begin.position()+1);
        cursor.setPosition(final.m_cursor_end.position(), QTextCursor::KeepAnchor);
    }
    q->setTextCursor(cursor);
}

con's avatar
con committed
bool Parenthesis::hasClosingCollapse(const Parentheses &parentheses)
{
    return closeCollapseAtPos(parentheses) >= 0;
}


int Parenthesis::closeCollapseAtPos(const Parentheses &parentheses)
{
    int depth = 0;
    for (int i = 0; i < parentheses.size(); ++i) {
        const Parenthesis &p = parentheses.at(i);
        if (p.chr == QLatin1Char('{')
            || p.chr == QLatin1Char('+')
            || p.chr == QLatin1Char('[')) {
con's avatar
con committed
            ++depth;
        } else if (p.chr == QLatin1Char('}')
            || p.chr == QLatin1Char('-')
            || p.chr == QLatin1Char(']')) {
con's avatar
con committed
            if (--depth < 0)
                return p.pos;
        }
    }
    return -1;
}

int Parenthesis::collapseAtPos(const Parentheses &parentheses, QChar *character)
{
    int result = -1;
    QChar c;

    int depth = 0;
    for (int i = 0; i < parentheses.size(); ++i) {
        const Parenthesis &p = parentheses.at(i);
        if (p.chr == QLatin1Char('{')
            || p.chr == QLatin1Char('+')
            || p.chr == QLatin1Char('[')) {
con's avatar
con committed
            if (depth == 0) {
                result = p.pos;
                c = p.chr;
            }
            ++depth;
        } else if (p.chr == QLatin1Char('}')
            || p.chr == QLatin1Char('-')
            || p.chr == QLatin1Char(']')) {
con's avatar
con committed
            if (--depth < 0)
                depth = 0;
            result = -1;
        }
    }
    if (result >= 0 && character)
        *character = c;
    return result;
}


int TextBlockUserData::collapseAtPos(QChar *character) const
con's avatar
con committed
{
    return Parenthesis::collapseAtPos(m_parentheses, character);
con's avatar
con committed
}

int TextBlockUserData::braceDepthDelta() const
{
    int delta = 0;
    for (int i = 0; i < m_parentheses.size(); ++i) {
        switch (m_parentheses.at(i).chr.unicode()) {
        case '{': case '+': case '[': ++delta; break;
        case '}': case '-': case ']': --delta; break;
        default: break;
        }
    }
    return delta;
}
con's avatar
con committed

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;
}

int TextEditDocumentLayout::braceDepthDelta(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->braceDepthDelta();
    return 0;
}

int TextEditDocumentLayout::braceDepth(const QTextBlock &block)
{
    int state = block.userState();
    if (state == -1)
        return 0;
    return state >> 8;
}

void TextEditDocumentLayout::setBraceDepth(QTextBlock &block, int depth)
{
    int state = block.userState();
    if (state == -1)
        state = 0;
    state = state & 0xff;
    block.setUserState((depth << 8) | state);
}

void TextEditDocumentLayout::changeBraceDepth(QTextBlock &block, int delta)
{
    if (delta)
        setBraceDepth(block, braceDepth(block) + delta);
}
con's avatar
con committed

bool TextEditDocumentLayout::setIfdefedOut(const QTextBlock &block)
{
    return userData(block)->setIfdefedOut();
}

bool TextEditDocumentLayout::clearIfdefedOut(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->clearIfdefedOut();
    return false;
}

bool TextEditDocumentLayout::ifdefedOut(const QTextBlock &block)
{
    if (TextBlockUserData *userData = testUserData(block))
        return userData->ifdefedOut();
    return false;
}


TextEditDocumentLayout::TextEditDocumentLayout(QTextDocument *doc)
    :QPlainTextDocumentLayout(doc) {
    lastSaveRevision = 0;
    hasMarks = 0;
}

TextEditDocumentLayout::~TextEditDocumentLayout()
{
}

QRectF TextEditDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{
    QRectF r = QPlainTextDocumentLayout::blockBoundingRect(block);
    return r;
}


bool BaseTextEditor::viewportEvent(QEvent *event)