/* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2009 Alexander Rieder Copyright (C) 2006 David Saxton */ #include "defaulthighlighter.h" #include #include #include #include #include #include using namespace Cantor; class Cantor::DefaultHighlighterPrivate { public: QTextEdit* parent; //Character formats to use for the highlighing QTextCharFormat functionFormat; QTextCharFormat variableFormat; QTextCharFormat objectFormat; QTextCharFormat keywordFormat; QTextCharFormat numberFormat; QTextCharFormat operatorFormat; QTextCharFormat errorFormat; QTextCharFormat commentFormat; QTextCharFormat stringFormat; QTextCharFormat matchingPairFormat; int lastBlockNumber; int lastPosition; // each two consecutive items build a pair QList pairs; }; DefaultHighlighter::DefaultHighlighter(QTextEdit* parent) : QSyntaxHighlighter(parent), d(new DefaultHighlighterPrivate) { d->parent=parent; d->lastBlockNumber=-1; d->lastPosition=-1; addPair('(', ')'); addPair('[', ']'); addPair('{', '}'); updateFormats(); connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), this, SLOT(updateFormats())); connect(parent, SIGNAL(cursorPositionChanged()), this, SLOT(positionChanged())); } DefaultHighlighter::~ DefaultHighlighter() { delete d; } void DefaultHighlighter::highlightBlock(const QString& text) { if ( text.isEmpty()) return; highlightPairs(text); } void DefaultHighlighter::addPair(const QChar& openSymbol, const QChar& closeSymbol) { Q_ASSERT(!d->pairs.contains(openSymbol)); Q_ASSERT(!d->pairs.contains(closeSymbol)); d->pairs << openSymbol << closeSymbol; } void DefaultHighlighter::highlightPairs(const QString& text) { const QTextCursor& cursor = d->parent->textCursor(); int cursorPos = -1; if ( cursor.blockNumber() == currentBlock().blockNumber() ) { cursorPos = cursor.position() - currentBlock().position(); // when text changes, this will be called before the positionChanged signal // gets emitted. Hence update the position so we don't highlight twice d->lastPosition = cursor.position(); } // positions of opened pairs // key: same index as the opener has in d->pairs // value: position in text where it was opened QHash* > opened; QHash* >::iterator it; ///TODO: use setCurrentBlockUserData to keep track of matched pairs /// of course, keep track of changes and update the cache properly for ( int i = 0; i < text.size(); ++i ) { int idx = d->pairs.indexOf(text[i]); if ( idx != -1 ) { if ( idx % 2 == 0 ) { // opener of a pair it = opened.find(idx); if ( it == opened.end() ) { it = opened.insert(idx, new QStack()); } (*it)->push(i); } else { // closer of a pair, find opener it = opened.find(idx - 1); if ( it == opened.end() || (*it)->isEmpty() ) { // unmatched setFormat(i, 1, errorFormat()); } else { // matched int lastPos = (*it)->pop(); // check if we have to highlight the matched pair // at the current cursor position if ( cursorPos != -1 && ( lastPos == cursorPos || lastPos == cursorPos - 1 || i == cursorPos || i == cursorPos - 1 ) ) { // yep, we want it highlighted setFormat(lastPos, 1, matchingPairFormat()); setFormat(i, 1, matchingPairFormat()); } } } } } // handled unterminated pairs foreach ( QStack* positions, opened ) { while ( !positions->isEmpty() ) { setFormat(positions->pop(), 1, errorFormat()); } } qDeleteAll(opened.values()); } DefaultHighlighter::BlockType DefaultHighlighter::currentBlockType() { QTextBlock block=currentBlock(); BlockType type=(BlockType) block.charFormat().intProperty(BlockTypeProperty); return type; } QTextCharFormat DefaultHighlighter::functionFormat() const { return d->functionFormat; } QTextCharFormat DefaultHighlighter::variableFormat() const { return d->variableFormat; } QTextCharFormat DefaultHighlighter::objectFormat() const { return d->objectFormat; } QTextCharFormat DefaultHighlighter::keywordFormat() const { return d->keywordFormat; } QTextCharFormat DefaultHighlighter::numberFormat() const { return d->numberFormat; } QTextCharFormat DefaultHighlighter::operatorFormat() const { return d->operatorFormat; } QTextCharFormat DefaultHighlighter::errorFormat() const { return d->errorFormat; } QTextCharFormat DefaultHighlighter::commentFormat() const { return d->commentFormat; } QTextCharFormat DefaultHighlighter::stringFormat() const { return d->stringFormat; } QTextCharFormat DefaultHighlighter::matchingPairFormat() const { return d->matchingPairFormat; } void DefaultHighlighter::updateFormats() { //initialize char-formats KColorScheme scheme(QPalette::Active); d->functionFormat.setForeground(scheme.foreground(KColorScheme::LinkText)); d->functionFormat.setFontWeight(QFont::DemiBold); d->variableFormat.setForeground(scheme.foreground(KColorScheme::ActiveText)); d->objectFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->objectFormat.setFontWeight(QFont::Bold); d->keywordFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->keywordFormat.setFontWeight(QFont::Bold); d->numberFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->operatorFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->operatorFormat.setFontWeight(QFont::Bold); d->errorFormat.setForeground(scheme.foreground(KColorScheme::NormalText)); d->errorFormat.setUnderlineColor(scheme.foreground(KColorScheme::NegativeText).color()); d->errorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); d->commentFormat.setForeground(scheme.foreground(KColorScheme::InactiveText)); d->stringFormat.setForeground(scheme.foreground(KColorScheme::PositiveText)); d->matchingPairFormat.setForeground(scheme.foreground(KColorScheme::NeutralText)); d->matchingPairFormat.setBackground(scheme.background(KColorScheme::NeutralBackground)); } void DefaultHighlighter::positionChanged() { const QTextCursor& cursor = d->parent->textCursor(); if ( cursor.blockNumber() != d->lastBlockNumber ) { // remove highlight from last focused block kDebug() << "cleaning up last block"; rehighlightBlock(d->parent->document()->findBlockByNumber(d->lastBlockNumber)); } d->lastBlockNumber = cursor.blockNumber(); if ( d->lastPosition == cursor.position() ) { return; } kDebug() << "position changed, rehighlight block"; rehighlightBlock(cursor.block()); d->lastPosition = cursor.position(); } #include "defaulthighlighter.moc"