11#include "highlighter.h"
13#include "languagefilter_p.h"
15#include "settingsimpl_p.h"
17#include "tokenizer_p.h"
26#include <QPlainTextEdit>
27#include <QTextCharFormat>
44 void invalidate(
int pos)
48 while (it.hasPrevious()) {
50 if (it.key().first + it.key().second >= pos) {
58 QString languageAtPos(
int pos)
const
62 while (it.hasNext()) {
64 if (it.key().first <= pos && it.key().first + it.key().second >= pos) {
72class HighlighterPrivate
75 HighlighterPrivate(Highlighter *qq,
const QColor &col)
77 , plainTextEdit(nullptr)
81 tokenizer =
new WordTokenizer();
84 autoDetectLanguageDisabled =
false;
87 intraWordEditing =
false;
88 completeRehighlightRequired =
false;
90 languageFilter =
new LanguageFilter(
new SentenceTokenizer());
92 loader = Loader::openLoader();
93 loader->settings()->restore();
96 spellCheckerFound = spellchecker->
isValid();
97 rehighlightRequest =
new QTimer(q);
100 if (!spellCheckerFound) {
104 disablePercentage = loader->settings()->disablePercentageWordError();
105 disableWordCount = loader->settings()->disableWordErrorCount();
107 completeRehighlightRequired =
true;
110 rehighlightRequest->
start();
113 ~HighlighterPrivate();
114 WordTokenizer *tokenizer =
nullptr;
115 LanguageFilter *languageFilter =
nullptr;
116 Loader *loader =
nullptr;
117 Speller *spellchecker =
nullptr;
122 bool autoDetectLanguageDisabled;
123 bool completeRehighlightRequired;
124 bool intraWordEditing;
125 bool spellCheckerFound;
127 int disablePercentage = 0;
128 int disableWordCount = 0;
129 int wordCount, errorCount;
130 QTimer *rehighlightRequest =
nullptr;
132 Highlighter *
const q;
135HighlighterPrivate::~HighlighterPrivate()
138 delete languageFilter;
144 , d(new HighlighterPrivate(this, _col))
148 d->textEdit->viewport()->installEventFilter(
this);
153 , d(new HighlighterPrivate(this, col))
155 d->plainTextEdit = edit;
157 d->plainTextEdit->installEventFilter(
this);
158 d->plainTextEdit->viewport()->installEventFilter(
this);
161Highlighter::~Highlighter()
163 if (d->contentsChangeConnection) {
172 return d->spellCheckerFound;
177 if (d->completeRehighlightRequired) {
185 cursor = d->textEdit->textCursor();
187 cursor = d->plainTextEdit->textCursor();
206 return d->autoDetectLanguageDisabled;
209bool Highlighter::intraWordEditing()
const
211 return d->intraWordEditing;
214void Highlighter::setIntraWordEditing(
bool editing)
216 d->intraWordEditing = editing;
233 d->autoDetectLanguageDisabled = autoDetectDisabled;
238 bool savedActive = d->active;
241 if (d->automatic && d->wordCount >= 10) {
244 bool tme = (d->errorCount >= d->disableWordCount)
245 && (d->errorCount * 100 >= d->disablePercentage * d->wordCount);
248 if (d->active && tme) {
250 }
else if (!d->active && !tme) {
255 if (d->active != savedActive) {
259 qCDebug(SONNET_LOG_UI) <<
"Sonnet: Disabling spell checking, too many errors";
261 tr(
"Too many misspelled words. "
262 "As-you-type spell checking disabled."));
265 d->completeRehighlightRequired =
true;
266 d->rehighlightRequest->setInterval(100);
267 d->rehighlightRequest->setSingleShot(
true);
273 if (active == d->active) {
291void Highlighter::contentsChange(
int pos,
int add,
int rem)
297 LanguageCache *cache =
dynamic_cast<LanguageCache *
>(block.
userData());
299 cache->invalidate(pos - block.
position());
301 block = block.
next();
302 }
while (block.
isValid() && block < lastBlock);
305static bool hasNotEmptyText(
const QString &text)
307 for (
int i = 0; i < text.
length(); ++i) {
315void Highlighter::highlightBlock(
const QString &text)
317 if (!hasNotEmptyText(text) || !d->active || !d->spellCheckerFound) {
321 if (!d->contentsChangeConnection) {
325 d->languageFilter->setBuffer(text);
329 cache =
new LanguageCache;
333 const bool autodetectLanguage = d->spellchecker->testAttribute(Speller::AutoDetectLanguage);
334 while (d->languageFilter->hasNext()) {
335 Token sentence = d->languageFilter->next();
336 if (autodetectLanguage && !d->autoDetectLanguageDisabled) {
338 QPair<int, int> spos = QPair<int, int>(sentence.position(), sentence.length());
340 if (cache->languages.
contains(spos)) {
341 lang = cache->languages.
value(spos);
343 lang = d->languageFilter->language();
344 if (!d->languageFilter->isSpellcheckable()) {
347 cache->languages[spos] = lang;
352 d->spellchecker->setLanguage(lang);
355 d->tokenizer->setBuffer(sentence.toString());
356 int offset = sentence.position();
357 while (d->tokenizer->hasNext()) {
358 Token word = d->tokenizer->next();
359 if (!d->tokenizer->isSpellcheckable()) {
363 if (d->spellchecker->isMisspelled(word.toString())) {
365 setMisspelled(word.position() + offset, word.length());
367 unsetMisspelled(word.position() + offset, word.length());
377 return d->spellchecker->language();
382 QString prevLang = d->spellchecker->language();
383 d->spellchecker->setLanguage(lang);
384 d->spellCheckerFound = d->spellchecker->isValid();
385 if (!d->spellCheckerFound) {
386 qCDebug(SONNET_LOG_UI) <<
"No dictionary for \"" << lang <<
"\" staying with the current language.";
387 d->spellchecker->setLanguage(prevLang);
392 if (d->automatic || d->active) {
393 d->rehighlightRequest->start(0);
397void Highlighter::setMisspelled(
int start,
int count)
406void Highlighter::unsetMisspelled(
int start,
int count)
413 if (!d->spellCheckerFound) {
419 if (d->rehighlightRequest->isActive()) {
420 d->rehighlightRequest->start(500);
439 if (intraWordEditing()) {
440 setIntraWordEditing(
false);
441 d->completeRehighlightRequired =
true;
442 d->rehighlightRequest->setInterval(500);
443 d->rehighlightRequest->setSingleShot(
true);
444 d->rehighlightRequest->start();
447 setIntraWordEditing(
true);
454 }
else if (((d->textEdit && (o == d->textEdit->viewport()))
455 || (d->plainTextEdit && (o == d->plainTextEdit->viewport())))
458 if (intraWordEditing()) {
459 setIntraWordEditing(
false);
460 d->completeRehighlightRequired =
true;
461 d->rehighlightRequest->setInterval(0);
462 d->rehighlightRequest->setSingleShot(
true);
463 d->rehighlightRequest->start();
471 d->spellchecker->addToPersonal(word);
476 d->spellchecker->addToSession(word);
481 QStringList suggestions = d->spellchecker->suggest(word);
482 if (max >= 0 && suggestions.
count() > max) {
483 suggestions = suggestions.
mid(0, max);
490 LanguageCache *cache =
dynamic_cast<LanguageCache *
>(cursor.
block().userData());
493 if (!cachedLanguage.
isEmpty()) {
494 d->spellchecker->setLanguage(cachedLanguage);
497 QStringList suggestions = d->spellchecker->suggest(word);
498 if (max >= 0 && suggestions.
count() > max) {
499 suggestions = suggestions.
mid(0, max);
506 return d->spellchecker->isMisspelled(word);
511 d->spellColor = color;
516 return d->loader->settings()->checkerEnabledByDefault();
521 d->contentsChangeConnection = {};
526#include "moc_highlighter.cpp"
bool autoDetectLanguageDisabled() const
Returns whether the automatic language detection is disabled, overriding the Sonnet settings.
void slotAutoDetection()
Run auto detection, disabling spell checking if too many errors are found.
void addWordToDictionary(const QString &word)
Adds the given word permanently to the dictionary.
void slotRehighlight()
Force a new highlighting.
void activeChanged(const QString &description)
Emitted when as-you-type spell checking is enabled or disabled.
QString currentLanguage() const
Returns the current language used for spell checking.
void ignoreWord(const QString &word)
Ignores the given word.
bool isActive() const
Returns the state of spell checking.
bool isWordMisspelled(const QString &word)
Checks if a given word is marked as misspelled by the highlighter.
QStringList suggestionsForWord(const QString &word, int max=10)
Returns a list of suggested replacements for the given misspelled word.
void setActive(bool active)
Enable/Disable spell checking.
bool automatic() const
Returns the state of the automatic disabling of spell checking.
bool spellCheckerFound() const
Returns whether a spell checking backend with support for the currentLanguage was found.
void setMisspelledColor(const QColor &color)
Sets the color in which the highlighter underlines misspelled words.
void setAutomatic(bool automatic)
Sets whether to automatically disable spell checking if there's too many errors.
void setAutoDetectLanguageDisabled(bool autoDetectDisabled)
Sets whether to disable the automatic language detection.
bool checkerEnabledByDefault() const
Return true if checker is enabled by default.
void setDocument(QTextDocument *document)
Set a new QTextDocument for this highlighter to operate on.
void setCurrentLanguage(const QString &language)
Set language to use for spell checking.
Q_SCRIPTABLE Q_NOREPLY void start()
bool isSpace(char32_t ucs4)
bool isValid() const const
Qt::KeyboardModifiers modifiers() const const
qsizetype count() const const
QList< T > mid(qsizetype pos, qsizetype length) const const
bool contains(const Key &key) const const
T value(const Key &key, const T &defaultValue) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
void installEventFilter(QObject *filterObj)
QString tr(const char *sourceText, const char *disambiguation, int n)
const QChar at(qsizetype position) const const
bool isEmpty() const const
qsizetype length() const const
QTextBlockUserData * currentBlockUserData() const const
QTextDocument * document() const const
void setCurrentBlockState(int newState)
void setCurrentBlockUserData(QTextBlockUserData *data)
void setDocument(QTextDocument *doc)
bool isValid() const const
QTextBlock next() const const
int position() const const
QTextBlockUserData * userData() const const
void setFontUnderline(bool underline)
void setUnderlineColor(const QColor &color)
void setUnderlineStyle(UnderlineStyle style)
QTextBlock block() const const
bool hasSelection() const const
void insertText(const QString &text)
int positionInBlock() const const
void contentsChange(int position, int charsRemoved, int charsAdded)
QTextBlock findBlock(int pos) const const
void setInterval(int msec)
void setSingleShot(bool singleShot)