File indexing completed on 2025-01-19 04:52:00

0001 /*
0002     Copyright (c) 2020 Christian Mollekopf <mollekopf@kolabsystems.com>
0003 
0004     This library is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This library is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU Library General Public License
0015     along with this library; see the file COPYING.LIB.  If not, write to the
0016     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301, USA.
0018 */
0019 #include "spellcheckhighlighter.h"
0020 
0021 #include <QDebug>
0022 
0023 #include "../syntaxhighlighter.h"
0024 
0025 SpellcheckHighlighter::SpellcheckHighlighter(QTextDocument *parent)
0026     : QSyntaxHighlighter(parent),
0027     mSpellchecker{new Sonnet::Speller()},
0028     mLanguageGuesser{new Sonnet::GuessLanguage()}
0029 {
0030     //Danger red from our color scheme
0031     mErrorFormat.setForeground(QColor{"#ed1515"});
0032     mQuoteFormat.setForeground(QColor{"#7f8c8d"});
0033 
0034     if (!mSpellchecker->isValid()) {
0035         qWarning() << "Spellchecker is invalid";
0036     }
0037     qDebug() << "Available dictionaries: " << mSpellchecker->availableDictionaries();
0038 }
0039 
0040 void SpellcheckHighlighter::autodetectLanguage(const QString &sentence)
0041 {
0042     const auto lang = mLanguageGuesser->identify(sentence, mSpellchecker->availableLanguages());
0043     if (lang.isEmpty()) {
0044         return;
0045     }
0046     mSpellchecker->setLanguage(lang);
0047 }
0048 
0049 static bool isSpellcheckable(const QStringRef &token)
0050 {
0051     if (token.isNull() || token.isEmpty()) {
0052         return false;
0053     }
0054     if (!token.at(0).isLetter()) {
0055         return false;
0056     }
0057     //TODO ignore urls and uppercase?
0058     return true;
0059 }
0060 
0061 void SpellcheckHighlighter::highlightBlock(const QString &text)
0062 {
0063     //Avoid spellchecking quotes
0064     if (text.isEmpty() || text.at(0) == QChar{'>'}) {
0065         setFormat(0, text.length(), mQuoteFormat);
0066         return;
0067     }
0068     for (const auto &sentenceRef : split(QTextBoundaryFinder::Sentence, text)) {
0069         //Avoid spellchecking quotes
0070         if (sentenceRef.isEmpty() || sentenceRef.at(0) == QChar{'>'}) {
0071             continue;
0072         }
0073 
0074         const auto sentence = QString::fromRawData(sentenceRef.data(), sentenceRef.length());
0075 
0076         autodetectLanguage(sentence);
0077 
0078         const int offset = sentenceRef.position();
0079         for (const auto &wordRef : split(QTextBoundaryFinder::Word, sentence)) {
0080             //Avoid spellchecking words in progress
0081             //FIXME this will also prevent spellchecking a single word on a line.
0082             if (offset + wordRef.position() + wordRef.length() >= text.length()) {
0083                 continue;
0084             }
0085             if (isSpellcheckable(wordRef)) {
0086                 const auto word = QString::fromRawData(wordRef.data(), wordRef.length());
0087                 const auto format = mSpellchecker->isMisspelled(word) ? mErrorFormat : QTextCharFormat{};
0088                 setFormat(offset + wordRef.position(), wordRef.length(), format);
0089             }
0090         }
0091     }
0092 }