File indexing completed on 2024-04-21 04:00:57

0001 // SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org>
0002 // SPDX-FileCopyrightText: 2020 Christian Mollekopf <mollekopf@kolabsystems.com>
0003 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0004 // SPDX-License-Identifier: LGPL-2.1-or-later
0005 
0006 #include "spellcheckhighlighter.h"
0007 #include "guesslanguage.h"
0008 #include "languagefilter_p.h"
0009 #include "loader_p.h"
0010 #include "settingsimpl_p.h"
0011 #include "speller.h"
0012 #include "tokenizer_p.h"
0013 
0014 #include "quick_debug.h"
0015 
0016 #include <QColor>
0017 #include <QHash>
0018 #include <QKeyEvent>
0019 #include <QMetaMethod>
0020 #include <QTextBoundaryFinder>
0021 #include <QTextCharFormat>
0022 #include <QTextCursor>
0023 #include <QTimer>
0024 #include <memory>
0025 
0026 using namespace Sonnet;
0027 
0028 // Cache of previously-determined languages (when using AutoDetectLanguage)
0029 // There is one such cache per block (paragraph)
0030 class LanguageCache : public QTextBlockUserData
0031 {
0032 public:
0033     // Key: QPair<start, length>
0034     // Value: language name
0035     QMap<QPair<int, int>, QString> languages;
0036 
0037     // Remove all cached language information after @p pos
0038     void invalidate(int pos)
0039     {
0040         QMutableMapIterator<QPair<int, int>, QString> it(languages);
0041         it.toBack();
0042         while (it.hasPrevious()) {
0043             it.previous();
0044             if (it.key().first + it.key().second >= pos) {
0045                 it.remove();
0046             } else {
0047                 break;
0048             }
0049         }
0050     }
0051 
0052     QString languageAtPos(int pos) const
0053     {
0054         // The data structure isn't really great for such lookups...
0055         QMapIterator<QPair<int, int>, QString> it(languages);
0056         while (it.hasNext()) {
0057             it.next();
0058             if (it.key().first <= pos && it.key().first + it.key().second >= pos) {
0059                 return it.value();
0060             }
0061         }
0062         return QString();
0063     }
0064 };
0065 
0066 class HighlighterPrivate
0067 {
0068 public:
0069     HighlighterPrivate(SpellcheckHighlighter *qq)
0070         : q(qq)
0071     {
0072         tokenizer = std::make_unique<WordTokenizer>();
0073         active = true;
0074         automatic = false;
0075         autoDetectLanguageDisabled = false;
0076         connected = false;
0077         wordCount = 0;
0078         errorCount = 0;
0079         intraWordEditing = false;
0080         completeRehighlightRequired = false;
0081         spellColor = spellColor.isValid() ? spellColor : Qt::red;
0082         languageFilter = std::make_unique<LanguageFilter>(new SentenceTokenizer());
0083 
0084         loader = Loader::openLoader();
0085         loader->settings()->restore();
0086 
0087         spellchecker = std::make_unique<Speller>();
0088         spellCheckerFound = spellchecker->isValid();
0089         rehighlightRequest = new QTimer(q);
0090         q->connect(rehighlightRequest, &QTimer::timeout, q, &SpellcheckHighlighter::slotRehighlight);
0091 
0092         if (!spellCheckerFound) {
0093             return;
0094         }
0095 
0096         disablePercentage = loader->settings()->disablePercentageWordError();
0097         disableWordCount = loader->settings()->disableWordErrorCount();
0098 
0099         completeRehighlightRequired = true;
0100         rehighlightRequest->setInterval(0);
0101         rehighlightRequest->setSingleShot(true);
0102         rehighlightRequest->start();
0103 
0104         // Danger red from our color scheme
0105         errorFormat.setForeground(spellColor);
0106         errorFormat.setUnderlineColor(spellColor);
0107         errorFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
0108 
0109         selectedErrorFormat.setForeground(spellColor);
0110         auto bg = spellColor;
0111         bg.setAlphaF(0.1);
0112         selectedErrorFormat.setBackground(bg);
0113         selectedErrorFormat.setUnderlineColor(spellColor);
0114         selectedErrorFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline);
0115 
0116         quoteFormat.setForeground(QColor{"#7f8c8d"});
0117     }
0118 
0119     ~HighlighterPrivate();
0120     std::unique_ptr<WordTokenizer> tokenizer;
0121     std::unique_ptr<LanguageFilter> languageFilter;
0122     Loader *loader = nullptr;
0123     std::unique_ptr<Speller> spellchecker;
0124 
0125     QTextCharFormat errorFormat;
0126     QTextCharFormat selectedErrorFormat;
0127     QTextCharFormat quoteFormat;
0128     std::unique_ptr<Sonnet::GuessLanguage> languageGuesser;
0129     QString selectedWord;
0130     QQuickTextDocument *document = nullptr;
0131     int cursorPosition;
0132     int selectionStart;
0133     int selectionEnd;
0134 
0135     int autoCompleteBeginPosition = -1;
0136     int autoCompleteEndPosition = -1;
0137     int wordIsMisspelled = false;
0138     bool active;
0139     bool automatic;
0140     bool autoDetectLanguageDisabled;
0141     bool completeRehighlightRequired;
0142     bool intraWordEditing;
0143     bool spellCheckerFound; // cached d->dict->isValid() value
0144     bool connected;
0145     int disablePercentage = 0;
0146     int disableWordCount = 0;
0147     int wordCount, errorCount;
0148     QTimer *rehighlightRequest = nullptr;
0149     QColor spellColor;
0150     SpellcheckHighlighter *const q;
0151 };
0152 
0153 HighlighterPrivate::~HighlighterPrivate()
0154 {
0155 }
0156 
0157 SpellcheckHighlighter::SpellcheckHighlighter(QObject *parent)
0158     : QSyntaxHighlighter(parent)
0159     , d(new HighlighterPrivate(this))
0160 {
0161 }
0162 
0163 SpellcheckHighlighter::~SpellcheckHighlighter()
0164 {
0165     if (document()) {
0166         disconnect(document(), nullptr, this, nullptr);
0167     }
0168 }
0169 
0170 bool SpellcheckHighlighter::spellCheckerFound() const
0171 {
0172     return d->spellCheckerFound;
0173 }
0174 
0175 void SpellcheckHighlighter::slotRehighlight()
0176 {
0177     if (d->completeRehighlightRequired) {
0178         d->wordCount = 0;
0179         d->errorCount = 0;
0180         rehighlight();
0181     } else {
0182         // rehighlight the current para only (undo/redo safe)
0183         QTextCursor cursor = textCursor();
0184         if (cursor.hasSelection()) {
0185             cursor.clearSelection();
0186         }
0187         cursor.insertText(QString());
0188     }
0189     // if (d->checksDone == d->checksRequested)
0190     // d->completeRehighlightRequired = false;
0191     QTimer::singleShot(0, this, &SpellcheckHighlighter::slotAutoDetection);
0192 }
0193 
0194 bool SpellcheckHighlighter::automatic() const
0195 {
0196     return d->automatic;
0197 }
0198 
0199 bool SpellcheckHighlighter::autoDetectLanguageDisabled() const
0200 {
0201     return d->autoDetectLanguageDisabled;
0202 }
0203 
0204 bool SpellcheckHighlighter::intraWordEditing() const
0205 {
0206     return d->intraWordEditing;
0207 }
0208 
0209 void SpellcheckHighlighter::setIntraWordEditing(bool editing)
0210 {
0211     d->intraWordEditing = editing;
0212 }
0213 
0214 void SpellcheckHighlighter::setAutomatic(bool automatic)
0215 {
0216     if (automatic == d->automatic) {
0217         return;
0218     }
0219 
0220     d->automatic = automatic;
0221     if (d->automatic) {
0222         slotAutoDetection();
0223     }
0224 }
0225 
0226 void SpellcheckHighlighter::setAutoDetectLanguageDisabled(bool autoDetectDisabled)
0227 {
0228     d->autoDetectLanguageDisabled = autoDetectDisabled;
0229 }
0230 
0231 void SpellcheckHighlighter::slotAutoDetection()
0232 {
0233     bool savedActive = d->active;
0234 
0235     // don't disable just because 1 of 4 is misspelled.
0236     if (d->automatic && d->wordCount >= 10) {
0237         // tme = Too many errors
0238         /* clang-format off */
0239         bool tme = (d->errorCount >= d->disableWordCount)
0240                    && (d->errorCount * 100 >= d->disablePercentage * d->wordCount);
0241         /* clang-format on */
0242 
0243         if (d->active && tme) {
0244             d->active = false;
0245         } else if (!d->active && !tme) {
0246             d->active = true;
0247         }
0248     }
0249 
0250     if (d->active != savedActive) {
0251         if (d->active) {
0252             Q_EMIT activeChanged(tr("As-you-type spell checking enabled."));
0253         } else {
0254             qCDebug(SONNET_LOG_QUICK) << "Sonnet: Disabling spell checking, too many errors";
0255             Q_EMIT activeChanged(
0256                 tr("Too many misspelled words. "
0257                    "As-you-type spell checking disabled."));
0258         }
0259 
0260         d->completeRehighlightRequired = true;
0261         d->rehighlightRequest->setInterval(100);
0262         d->rehighlightRequest->setSingleShot(true);
0263     }
0264 }
0265 
0266 void SpellcheckHighlighter::setActive(bool active)
0267 {
0268     if (active == d->active) {
0269         return;
0270     }
0271     d->active = active;
0272     Q_EMIT activeChanged();
0273     rehighlight();
0274 
0275     if (d->active) {
0276         Q_EMIT activeChanged(tr("As-you-type spell checking enabled."));
0277     } else {
0278         Q_EMIT activeChanged(tr("As-you-type spell checking disabled."));
0279     }
0280 }
0281 
0282 bool SpellcheckHighlighter::active() const
0283 {
0284     return d->active;
0285 }
0286 
0287 static bool hasNotEmptyText(const QString &text)
0288 {
0289     for (int i = 0; i < text.length(); ++i) {
0290         if (!text.at(i).isSpace()) {
0291             return true;
0292         }
0293     }
0294     return false;
0295 }
0296 
0297 void SpellcheckHighlighter::contentsChange(int pos, int add, int rem)
0298 {
0299     // Invalidate the cache where the text has changed
0300     const QTextBlock &lastBlock = document()->findBlock(pos + add - rem);
0301     QTextBlock block = document()->findBlock(pos);
0302     do {
0303         LanguageCache *cache = dynamic_cast<LanguageCache *>(block.userData());
0304         if (cache) {
0305             cache->invalidate(pos - block.position());
0306         }
0307         block = block.next();
0308     } while (block.isValid() && block < lastBlock);
0309 }
0310 
0311 void SpellcheckHighlighter::highlightBlock(const QString &text)
0312 {
0313     if (!hasNotEmptyText(text) || !d->active || !d->spellCheckerFound) {
0314         return;
0315     }
0316 
0317     // Avoid spellchecking quotes
0318     if (text.isEmpty() || text.at(0) == QLatin1Char('>')) {
0319         setFormat(0, text.length(), d->quoteFormat);
0320         return;
0321     }
0322 
0323     if (!d->connected) {
0324         connect(textDocument(), &QTextDocument::contentsChange, this, &SpellcheckHighlighter::contentsChange);
0325         d->connected = true;
0326     }
0327     QTextCursor cursor = textCursor();
0328     const int index = cursor.position() + 1;
0329 
0330     const int lengthPosition = text.length() - 1;
0331 
0332     if (index != lengthPosition //
0333         || (lengthPosition > 0 && !text[lengthPosition - 1].isLetter())) {
0334         d->languageFilter->setBuffer(text);
0335 
0336         LanguageCache *cache = dynamic_cast<LanguageCache *>(currentBlockUserData());
0337         if (!cache) {
0338             cache = new LanguageCache;
0339             setCurrentBlockUserData(cache);
0340         }
0341 
0342         const bool autodetectLanguage = d->spellchecker->testAttribute(Speller::AutoDetectLanguage);
0343         while (d->languageFilter->hasNext()) {
0344             Sonnet::Token sentence = d->languageFilter->next();
0345             if (autodetectLanguage && !d->autoDetectLanguageDisabled) {
0346                 QString lang;
0347                 QPair<int, int> spos = QPair<int, int>(sentence.position(), sentence.length());
0348                 // try cache first
0349                 if (cache->languages.contains(spos)) {
0350                     lang = cache->languages.value(spos);
0351                 } else {
0352                     lang = d->languageFilter->language();
0353                     if (!d->languageFilter->isSpellcheckable()) {
0354                         lang.clear();
0355                     }
0356                     cache->languages[spos] = lang;
0357                 }
0358                 if (lang.isEmpty()) {
0359                     continue;
0360                 }
0361                 d->spellchecker->setLanguage(lang);
0362             }
0363 
0364             d->tokenizer->setBuffer(sentence.toString());
0365             int offset = sentence.position();
0366             while (d->tokenizer->hasNext()) {
0367                 Sonnet::Token word = d->tokenizer->next();
0368                 if (!d->tokenizer->isSpellcheckable()) {
0369                     continue;
0370                 }
0371                 ++d->wordCount;
0372                 if (d->spellchecker->isMisspelled(word.toString())) {
0373                     ++d->errorCount;
0374                     if (word.position() + offset <= cursor.position() && cursor.position() <= word.position() + offset + word.length()) {
0375                         setMisspelledSelected(word.position() + offset, word.length());
0376                     } else {
0377                         setMisspelled(word.position() + offset, word.length());
0378                     }
0379                 } else {
0380                     unsetMisspelled(word.position() + offset, word.length());
0381                 }
0382             }
0383         }
0384     }
0385     // QTimer::singleShot( 0, this, SLOT(checkWords()) );
0386     setCurrentBlockState(0);
0387 }
0388 
0389 QStringList SpellcheckHighlighter::suggestions(int mousePosition, int max)
0390 {
0391     if (!textDocument()) {
0392         return {};
0393     }
0394 
0395     Q_EMIT changeCursorPosition(mousePosition, mousePosition);
0396 
0397     QTextCursor cursor = textCursor();
0398 
0399     QTextCursor cursorAtMouse(textDocument());
0400     cursorAtMouse.setPosition(mousePosition);
0401 
0402     // Check if the user clicked a selected word
0403     const bool selectedWordClicked = cursor.hasSelection() && mousePosition >= cursor.selectionStart() && mousePosition <= cursor.selectionEnd();
0404 
0405     // Get the word under the (mouse-)cursor and see if it is misspelled.
0406     // Don't include apostrophes at the start/end of the word in the selection.
0407     QTextCursor wordSelectCursor(cursorAtMouse);
0408     wordSelectCursor.clearSelection();
0409     wordSelectCursor.select(QTextCursor::WordUnderCursor);
0410     d->selectedWord = wordSelectCursor.selectedText();
0411 
0412     // Clear the selection again, we re-select it below (without the apostrophes).
0413     wordSelectCursor.setPosition(wordSelectCursor.position() - d->selectedWord.size());
0414     if (d->selectedWord.startsWith(QLatin1Char('\'')) || d->selectedWord.startsWith(QLatin1Char('\"'))) {
0415         d->selectedWord = d->selectedWord.right(d->selectedWord.size() - 1);
0416         wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
0417     }
0418     if (d->selectedWord.endsWith(QLatin1Char('\'')) || d->selectedWord.endsWith(QLatin1Char('\"'))) {
0419         d->selectedWord.chop(1);
0420     }
0421 
0422     wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size());
0423 
0424     int endSelection = wordSelectCursor.selectionEnd();
0425     Q_EMIT wordUnderMouseChanged();
0426 
0427     bool isMouseCursorInsideWord = true;
0428     if ((mousePosition < wordSelectCursor.selectionStart() || mousePosition >= wordSelectCursor.selectionEnd()) //
0429         && (d->selectedWord.length() > 1)) {
0430         isMouseCursorInsideWord = false;
0431     }
0432 
0433     wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size());
0434 
0435     d->wordIsMisspelled = isMouseCursorInsideWord && !d->selectedWord.isEmpty() && d->spellchecker->isMisspelled(d->selectedWord);
0436     Q_EMIT wordIsMisspelledChanged();
0437 
0438     if (!d->wordIsMisspelled || selectedWordClicked) {
0439         return QStringList{};
0440     }
0441 
0442     LanguageCache *cache = dynamic_cast<LanguageCache *>(cursor.block().userData());
0443     if (cache) {
0444         const QString cachedLanguage = cache->languageAtPos(cursor.positionInBlock());
0445         if (!cachedLanguage.isEmpty()) {
0446             d->spellchecker->setLanguage(cachedLanguage);
0447         }
0448     }
0449     QStringList suggestions = d->spellchecker->suggest(d->selectedWord);
0450     if (max >= 0 && suggestions.count() > max) {
0451         suggestions = suggestions.mid(0, max);
0452     }
0453 
0454     return suggestions;
0455 }
0456 
0457 QString SpellcheckHighlighter::currentLanguage() const
0458 {
0459     return d->spellchecker->language();
0460 }
0461 
0462 void SpellcheckHighlighter::setCurrentLanguage(const QString &lang)
0463 {
0464     QString prevLang = d->spellchecker->language();
0465     d->spellchecker->setLanguage(lang);
0466     d->spellCheckerFound = d->spellchecker->isValid();
0467     if (!d->spellCheckerFound) {
0468         qCDebug(SONNET_LOG_QUICK) << "No dictionary for \"" << lang << "\" staying with the current language.";
0469         d->spellchecker->setLanguage(prevLang);
0470         return;
0471     }
0472     d->wordCount = 0;
0473     d->errorCount = 0;
0474     if (d->automatic || d->active) {
0475         d->rehighlightRequest->start(0);
0476     }
0477 }
0478 
0479 void SpellcheckHighlighter::setMisspelled(int start, int count)
0480 {
0481     setFormat(start, count, d->errorFormat);
0482 }
0483 
0484 void SpellcheckHighlighter::setMisspelledSelected(int start, int count)
0485 {
0486     setFormat(start, count, d->selectedErrorFormat);
0487 }
0488 
0489 void SpellcheckHighlighter::unsetMisspelled(int start, int count)
0490 {
0491     setFormat(start, count, QTextCharFormat());
0492 }
0493 
0494 void SpellcheckHighlighter::addWordToDictionary(const QString &word)
0495 {
0496     d->spellchecker->addToPersonal(word);
0497     rehighlight();
0498 }
0499 
0500 void SpellcheckHighlighter::ignoreWord(const QString &word)
0501 {
0502     d->spellchecker->addToSession(word);
0503     rehighlight();
0504 }
0505 
0506 void SpellcheckHighlighter::replaceWord(const QString &replacement, int at)
0507 {
0508     QTextCursor textCursorUnderUserCursor(textDocument());
0509     textCursorUnderUserCursor.setPosition(at == -1 ? d->cursorPosition : at);
0510 
0511     // Get the word under the cursor
0512     QTextCursor wordSelectCursor(textCursorUnderUserCursor);
0513     wordSelectCursor.clearSelection();
0514     wordSelectCursor.select(QTextCursor::WordUnderCursor);
0515 
0516     auto selectedWord = wordSelectCursor.selectedText();
0517 
0518     // Trim leading and trailing apostrophes
0519     wordSelectCursor.setPosition(wordSelectCursor.position() - selectedWord.size());
0520     if (selectedWord.startsWith(QLatin1Char('\'')) || selectedWord.startsWith(QLatin1Char('\"'))) {
0521         selectedWord = selectedWord.right(selectedWord.size() - 1);
0522         wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
0523     }
0524     if (selectedWord.endsWith(QLatin1Char('\'')) || d->selectedWord.endsWith(QLatin1Char('\"'))) {
0525         selectedWord.chop(1);
0526     }
0527 
0528     wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, d->selectedWord.size());
0529 
0530     wordSelectCursor.insertText(replacement);
0531 }
0532 
0533 QQuickTextDocument *SpellcheckHighlighter::quickDocument() const
0534 {
0535     return d->document;
0536 }
0537 
0538 void SpellcheckHighlighter::setQuickDocument(QQuickTextDocument *document)
0539 {
0540     if (document == d->document) {
0541         return;
0542     }
0543 
0544     if (d->document) {
0545         d->document->parent()->removeEventFilter(this);
0546         d->document->textDocument()->disconnect(this);
0547     }
0548     d->document = document;
0549     document->parent()->installEventFilter(this);
0550     setDocument(document->textDocument());
0551     Q_EMIT documentChanged();
0552 }
0553 
0554 void SpellcheckHighlighter::setDocument(QTextDocument *document)
0555 {
0556     d->connected = false;
0557     QSyntaxHighlighter::setDocument(document);
0558 }
0559 
0560 int SpellcheckHighlighter::cursorPosition() const
0561 {
0562     return d->cursorPosition;
0563 }
0564 
0565 void SpellcheckHighlighter::setCursorPosition(int position)
0566 {
0567     if (position == d->cursorPosition) {
0568         return;
0569     }
0570 
0571     d->cursorPosition = position;
0572     d->rehighlightRequest->start(0);
0573     Q_EMIT cursorPositionChanged();
0574 }
0575 
0576 int SpellcheckHighlighter::selectionStart() const
0577 {
0578     return d->selectionStart;
0579 }
0580 
0581 void SpellcheckHighlighter::setSelectionStart(int position)
0582 {
0583     if (position == d->selectionStart) {
0584         return;
0585     }
0586 
0587     d->selectionStart = position;
0588     Q_EMIT selectionStartChanged();
0589 }
0590 
0591 int SpellcheckHighlighter::selectionEnd() const
0592 {
0593     return d->selectionEnd;
0594 }
0595 
0596 void SpellcheckHighlighter::setSelectionEnd(int position)
0597 {
0598     if (position == d->selectionEnd) {
0599         return;
0600     }
0601 
0602     d->selectionEnd = position;
0603     Q_EMIT selectionEndChanged();
0604 }
0605 
0606 QTextCursor SpellcheckHighlighter::textCursor() const
0607 {
0608     QTextDocument *doc = textDocument();
0609     if (!doc) {
0610         return QTextCursor();
0611     }
0612 
0613     QTextCursor cursor(doc);
0614     if (d->selectionStart != d->selectionEnd) {
0615         cursor.setPosition(d->selectionStart);
0616         cursor.setPosition(d->selectionEnd, QTextCursor::KeepAnchor);
0617     } else {
0618         cursor.setPosition(d->cursorPosition);
0619     }
0620     return cursor;
0621 }
0622 
0623 QTextDocument *SpellcheckHighlighter::textDocument() const
0624 {
0625     if (!d->document) {
0626         return nullptr;
0627     }
0628 
0629     return d->document->textDocument();
0630 }
0631 
0632 bool SpellcheckHighlighter::wordIsMisspelled() const
0633 {
0634     return d->wordIsMisspelled;
0635 }
0636 
0637 QString SpellcheckHighlighter::wordUnderMouse() const
0638 {
0639     return d->selectedWord;
0640 }
0641 
0642 QColor SpellcheckHighlighter::misspelledColor() const
0643 {
0644     return d->spellColor;
0645 }
0646 
0647 void SpellcheckHighlighter::setMisspelledColor(const QColor &color)
0648 {
0649     if (color == d->spellColor) {
0650         return;
0651     }
0652     d->spellColor = color;
0653     Q_EMIT misspelledColorChanged();
0654 }
0655 
0656 bool SpellcheckHighlighter::isWordMisspelled(const QString &word)
0657 {
0658     return d->spellchecker->isMisspelled(word);
0659 }
0660 
0661 bool SpellcheckHighlighter::eventFilter(QObject *o, QEvent *e)
0662 {
0663     if (!d->spellCheckerFound) {
0664         return false;
0665     }
0666     if (o == d->document->parent() && (e->type() == QEvent::KeyPress)) {
0667         QKeyEvent *k = static_cast<QKeyEvent *>(e);
0668 
0669         if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return || k->key() == Qt::Key_Up || k->key() == Qt::Key_Down || k->key() == Qt::Key_Left
0670             || k->key() == Qt::Key_Right || k->key() == Qt::Key_PageUp || k->key() == Qt::Key_PageDown || k->key() == Qt::Key_Home || k->key() == Qt::Key_End
0671             || (k->modifiers() == Qt::ControlModifier
0672                 && (k->key() == Qt::Key_A || k->key() == Qt::Key_B || k->key() == Qt::Key_E || k->key() == Qt::Key_N
0673                     || k->key() == Qt::Key_P))) { /* clang-format on */
0674             if (intraWordEditing()) {
0675                 setIntraWordEditing(false);
0676                 d->completeRehighlightRequired = true;
0677                 d->rehighlightRequest->setInterval(500);
0678                 d->rehighlightRequest->setSingleShot(true);
0679                 d->rehighlightRequest->start();
0680             }
0681         } else {
0682             setIntraWordEditing(true);
0683         }
0684         if (k->key() == Qt::Key_Space //
0685             || k->key() == Qt::Key_Enter //
0686             || k->key() == Qt::Key_Return) {
0687             QTimer::singleShot(0, this, SLOT(slotAutoDetection()));
0688         }
0689     } else if (d->document && e->type() == QEvent::MouseButtonPress) {
0690         if (intraWordEditing()) {
0691             setIntraWordEditing(false);
0692             d->completeRehighlightRequired = true;
0693             d->rehighlightRequest->setInterval(0);
0694             d->rehighlightRequest->setSingleShot(true);
0695             d->rehighlightRequest->start();
0696         }
0697     }
0698     return false;
0699 }
0700 
0701 #include "moc_spellcheckhighlighter.cpp"