/* This is part of TeXworks, an environment for working with TeX documents Copyright (C) 2007-08 Jonathan Kew 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, see . For links to further information, or to contact the author, see . */ #include #include #include #include "TeXHighlighter.h" #include "TeXDocument.h" #include "TWUtils.h" #include // for INT_MAX QList *TeXHighlighter::syntaxRules = NULL; QList *TeXHighlighter::tagPatterns = NULL; TeXHighlighter::TeXHighlighter(QTextDocument *parent, TeXDocument *texDocument) : QSyntaxHighlighter(parent) , texDoc(texDocument) , highlightIndex(-1) , isTagging(true) , pHunspell(NULL) , spellingCodec(NULL) { loadPatterns(); spellFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); spellFormat.setUnderlineColor(Qt::red); } void TeXHighlighter::spellCheckRange(const QString &text, int index, int limit, const QTextCharFormat &spellFormat) { while (index < limit) { int start, end; if (TWUtils::findNextWord(text, index, start, end)) { if (start < index) start = index; if (end > limit) end = limit; if (start < end) { QString word = text.mid(start, end - start); int spellResult = Hunspell_spell(pHunspell, spellingCodec->fromUnicode(word).data()); if (spellResult == 0) setFormat(start, end - start, spellFormat); } } index = end; } } void TeXHighlighter::highlightBlock(const QString &text) { int index = 0; if (highlightIndex >= 0 && highlightIndex < syntaxRules->count()) { QList& highlightingRules = (*syntaxRules)[highlightIndex].rules; while (index < text.length()) { int firstIndex = INT_MAX, len; const HighlightingRule* firstRule = NULL; for (int i = 0; i < highlightingRules.size(); ++i) { HighlightingRule &rule = highlightingRules[i]; int foundIndex = text.indexOf(rule.pattern, index); if (foundIndex >= 0 && foundIndex < firstIndex) { firstIndex = foundIndex; firstRule = &rule; } } if (firstRule != NULL && (len = firstRule->pattern.matchedLength()) > 0) { if (pHunspell != NULL && firstIndex > index) spellCheckRange(text, index, firstIndex, spellFormat); setFormat(firstIndex, len, firstRule->format); index = firstIndex + len; if (pHunspell != NULL && firstRule->spellCheck) spellCheckRange(text, firstIndex, index, firstRule->spellFormat); } else break; } } if (pHunspell != NULL) spellCheckRange(text, index, text.length(), spellFormat); #if QT_VERSION >= 0x040400 /* the currentBlock() method is not available in 4.3.x */ if (texDoc != NULL) { bool changed = false; if (texDoc->removeTags(currentBlock().position(), currentBlock().length()) > 0) changed = true; if (isTagging) { int index = 0; while (index < text.length()) { int firstIndex = INT_MAX, len; TagPattern* firstPatt = NULL; for (int i = 0; i < tagPatterns->count(); ++i) { TagPattern& patt = (*tagPatterns)[i]; int foundIndex = text.indexOf(patt.pattern, index); if (foundIndex >= 0 && foundIndex < firstIndex) { firstIndex = foundIndex; firstPatt = &patt; } } if (firstPatt != NULL && (len = firstPatt->pattern.matchedLength()) > 0) { QTextCursor cursor(document()); cursor.setPosition(currentBlock().position() + firstIndex); cursor.setPosition(currentBlock().position() + firstIndex + len, QTextCursor::KeepAnchor); QString text = firstPatt->pattern.cap(1); if (text.isEmpty()) text = firstPatt->pattern.cap(0); texDoc->addTag(cursor, firstPatt->level, text); index = firstIndex + len; changed = true; } else break; } } if (changed) texDoc->tagsChanged(); } #endif } void TeXHighlighter::setActiveIndex(int index) { highlightIndex = (index >= 0 && index < syntaxRules->count()) ? index : -1; rehighlight(); } void TeXHighlighter::setSpellChecker(Hunhandle* h, QTextCodec* codec) { pHunspell = h; spellingCodec = codec; rehighlight(); } QStringList TeXHighlighter::syntaxOptions() { loadPatterns(); QStringList options; if (syntaxRules != NULL) foreach (const HighlightingSpec& spec, *syntaxRules) options << spec.name; return options; } void TeXHighlighter::loadPatterns() { if (syntaxRules != NULL) return; QDir configDir(TWUtils::getLibraryPath("configuration")); QRegExp whitespace("\\s+"); if (syntaxRules == NULL) { syntaxRules = new QList; QFile syntaxFile(configDir.filePath("syntax-patterns.txt")); QRegExp sectionRE("^\\[([^]]+)\\]"); if (syntaxFile.open(QIODevice::ReadOnly)) { HighlightingSpec spec; spec.name = tr("default"); while (1) { QByteArray ba = syntaxFile.readLine(); if (ba.size() == 0) break; if (ba[0] == '#' || ba[0] == '\n') continue; QString line = QString::fromUtf8(ba.data(), ba.size()); if (sectionRE.indexIn(line) == 0) { if (spec.rules.count() > 0) syntaxRules->append(spec); spec.rules.clear(); spec.name = sectionRE.cap(1); continue; } QStringList parts = line.split(whitespace, QString::SkipEmptyParts); if (parts.size() != 3) continue; QStringList styles = parts[0].split(QChar(';')); QStringList colors = styles[0].split(QChar('/')); QColor fg, bg; if (colors.size() <= 2) { if (colors.size() == 2) bg = QColor(colors[1]); fg = QColor(colors[0]); } HighlightingRule rule; if (fg.isValid()) rule.format.setForeground(fg); if (bg.isValid()) rule.format.setBackground(bg); if (styles.size() > 1) { if (styles[1].contains('B')) rule.format.setFontWeight(QFont::Bold); if (styles[1].contains('I')) rule.format.setFontItalic(true); if (styles[1].contains('U')) rule.format.setFontUnderline(true); } if (parts[1].compare("Y", Qt::CaseInsensitive) == 0) { rule.spellCheck = true; rule.spellFormat = rule.format; rule.spellFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); } else rule.spellCheck = false; rule.pattern = QRegExp(parts[2]); if (rule.pattern.isValid() && !rule.pattern.isEmpty()) spec.rules.append(rule); } if (spec.rules.count() > 0) syntaxRules->append(spec); } } if (tagPatterns == NULL) { // read tag-recognition patterns tagPatterns = new QList; QFile tagPatternFile(configDir.filePath("tag-patterns.txt")); if (tagPatternFile.open(QIODevice::ReadOnly)) { while (1) { QByteArray ba = tagPatternFile.readLine(); if (ba.size() == 0) break; if (ba[0] == '#' || ba[0] == '\n') continue; QString line = QString::fromUtf8(ba.data(), ba.size()); QStringList parts = line.split(whitespace, QString::SkipEmptyParts); if (parts.size() != 2) continue; TagPattern patt; bool ok; patt.level = parts[0].toInt(&ok); if (ok) { patt.pattern = QRegExp(parts[1]); if (patt.pattern.isValid() && !patt.pattern.isEmpty()) tagPatterns->append(patt); } } } } }