Kate
ontheflycheck.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries and the Kate part. 00002 * 00003 * Copyright (C) 2008-2010 by Michel Ludwig <michel.ludwig@kdemail.net> 00004 * Copyright (C) 2009 by Joseph Wenninger <jowenn@kde.org> 00005 * 00006 * This library is free software; you can redistribute it and/or 00007 * modify it under the terms of the GNU Library General Public 00008 * License as published by the Free Software Foundation; either 00009 * version 2 of the License, or (at your option) any later version. 00010 * 00011 * This library is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 * Library General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Library General Public License 00017 * along with this library; see the file COPYING.LIB. If not, write to 00018 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00019 * Boston, MA 02110-1301, USA. 00020 */ 00021 00022 /* If ever threads should be used again, thread communication and 00023 * synchronization ought to be done with blocking queued signal connections. 00024 */ 00025 00026 #include "ontheflycheck.h" 00027 00028 #include <QTimer> 00029 00030 #include "kateconfig.h" 00031 #include "kateglobal.h" 00032 #include "katerenderer.h" 00033 #include "kateview.h" 00034 #include "spellcheck.h" 00035 #include "spellingmenu.h" 00036 00037 #define ON_THE_FLY_DEBUG kDebug(debugArea()) 00038 00039 KateOnTheFlyChecker::KateOnTheFlyChecker(KateDocument *document) 00040 : QObject(document), 00041 m_document(document), 00042 m_backgroundChecker(NULL), 00043 m_currentlyCheckedItem(invalidSpellCheckQueueItem), 00044 m_refreshView(NULL) 00045 { 00046 ON_THE_FLY_DEBUG << "created"; 00047 00048 m_viewRefreshTimer = new QTimer(this); 00049 m_viewRefreshTimer->setSingleShot(true); 00050 connect(m_viewRefreshTimer, SIGNAL(timeout()), this, SLOT(viewRefreshTimeout())); 00051 00052 connect(document, SIGNAL(textInserted(KTextEditor::Document*, const KTextEditor::Range&)), 00053 this, SLOT(textInserted(KTextEditor::Document*, const KTextEditor::Range&))); 00054 connect(document, SIGNAL(textRemoved(KTextEditor::Document*, const KTextEditor::Range&)), 00055 this, SLOT(textRemoved(KTextEditor::Document*, const KTextEditor::Range&))); 00056 connect(document, SIGNAL(viewCreated(KTextEditor::Document*, KTextEditor::View*)), 00057 this, SLOT(addView(KTextEditor::Document*, KTextEditor::View*))); 00058 connect(document, SIGNAL(highlightingModeChanged (KTextEditor::Document*)), 00059 this, SLOT(updateConfig())); 00060 connect(document, SIGNAL(respellCheckBlock(KateDocument*, int, int)), 00061 this, SLOT(handleRespellCheckBlock(KateDocument*, int, int))); 00062 00063 // load the settings for the speller 00064 updateConfig(); 00065 00066 foreach(KTextEditor::View* view, document->views()) { 00067 addView(document, view); 00068 } 00069 refreshSpellCheck(); 00070 } 00071 00072 KateOnTheFlyChecker::~KateOnTheFlyChecker() 00073 { 00074 freeDocument(); 00075 } 00076 00077 int KateOnTheFlyChecker::debugArea() 00078 { 00079 static int s_area = KDebug::registerArea("Kate (On-The-Fly Spellcheck)"); 00080 return s_area; 00081 } 00082 00083 QPair<KTextEditor::Range, QString> KateOnTheFlyChecker::getMisspelledItem(const KTextEditor::Cursor &cursor) const 00084 { 00085 foreach(const MisspelledItem &item, m_misspelledList) { 00086 KTextEditor::MovingRange *movingRange = item.first; 00087 if(movingRange->contains(cursor)) { 00088 return QPair<KTextEditor::Range, QString>(*movingRange, item.second); 00089 } 00090 } 00091 return QPair<KTextEditor::Range, QString>(KTextEditor::Range::invalid(), QString()); 00092 } 00093 00094 QString KateOnTheFlyChecker::dictionaryForMisspelledRange(const KTextEditor::Range& range) const 00095 { 00096 foreach(const MisspelledItem &item, m_misspelledList) { 00097 KTextEditor::MovingRange *movingRange = item.first; 00098 if(*movingRange == range) { 00099 return item.second; 00100 } 00101 } 00102 return QString(); 00103 } 00104 00105 void KateOnTheFlyChecker::clearMisspellingForWord(const QString& word) 00106 { 00107 MisspelledList misspelledList = m_misspelledList; // make a copy 00108 foreach(const MisspelledItem &item, misspelledList) { 00109 KTextEditor::MovingRange *movingRange = item.first; 00110 if(m_document->text(*movingRange) == word) { 00111 deleteMovingRange(movingRange); 00112 } 00113 } 00114 } 00115 00116 const KateOnTheFlyChecker::SpellCheckItem KateOnTheFlyChecker::invalidSpellCheckQueueItem = 00117 SpellCheckItem(NULL, ""); 00118 00119 void KateOnTheFlyChecker::handleRespellCheckBlock(KateDocument *kateDocument, int start, int end) 00120 { 00121 Q_ASSERT(kateDocument == m_document); 00122 Q_UNUSED(kateDocument); 00123 00124 ON_THE_FLY_DEBUG << start << end; 00125 KTextEditor::Range range(start, 0, end, m_document->lineLength(end)); 00126 bool listEmpty = m_modificationList.isEmpty(); 00127 KTextEditor::MovingRange *movingRange = m_document->newMovingRange(range); 00128 movingRange->setFeedback(this); 00129 // we don't handle this directly as the highlighting information might not be up-to-date yet 00130 m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange)); 00131 ON_THE_FLY_DEBUG << "added" << *movingRange; 00132 if(listEmpty) { 00133 QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); 00134 } 00135 } 00136 00137 void KateOnTheFlyChecker::textInserted(KTextEditor::Document *document, const KTextEditor::Range &range) 00138 { 00139 Q_ASSERT(document == m_document); 00140 Q_UNUSED(document); 00141 if(!range.isValid()) { 00142 return; 00143 } 00144 00145 bool listEmptyAtStart = m_modificationList.isEmpty(); 00146 00147 // don't consider a range that is not within the document range 00148 const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range); 00149 if(!documentIntersection.isValid()) { 00150 return; 00151 } 00152 // for performance reasons we only want to schedule spellchecks for ranges that are visible 00153 foreach(KTextEditor::View* i, m_document->views()) { 00154 KateView *view = static_cast<KateView*>(i); 00155 KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange()); 00156 if(visibleIntersection.isValid()) { // allow empty intersections 00157 // we don't handle this directly as the highlighting information might not be up-to-date yet 00158 KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection); 00159 movingRange->setFeedback(this); 00160 m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange)); 00161 ON_THE_FLY_DEBUG << "added" << *movingRange; 00162 } 00163 } 00164 00165 if(listEmptyAtStart && !m_modificationList.isEmpty()) { 00166 QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); 00167 } 00168 } 00169 00170 void KateOnTheFlyChecker::handleInsertedText(const KTextEditor::Range &range) 00171 { 00172 KTextEditor::Range consideredRange = range; 00173 ON_THE_FLY_DEBUG << m_document << range; 00174 00175 bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem; 00176 00177 if(spellCheckInProgress) { 00178 KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; 00179 if(spellCheckRange->contains(consideredRange)) { 00180 consideredRange = *spellCheckRange; 00181 stopCurrentSpellCheck(); 00182 deleteMovingRangeQuickly(spellCheckRange); 00183 } 00184 else if(consideredRange.contains(*spellCheckRange)) { 00185 stopCurrentSpellCheck(); 00186 deleteMovingRangeQuickly(spellCheckRange); 00187 } 00188 else if(consideredRange.overlaps(*spellCheckRange)) { 00189 consideredRange.expandToRange(*spellCheckRange); 00190 stopCurrentSpellCheck(); 00191 deleteMovingRangeQuickly(spellCheckRange); 00192 } 00193 else { 00194 spellCheckInProgress = false; 00195 } 00196 } 00197 for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin(); 00198 i != m_spellCheckQueue.end();) { 00199 KTextEditor::MovingRange *spellCheckRange = (*i).first; 00200 if(spellCheckRange->contains(consideredRange)) { 00201 consideredRange = *spellCheckRange; 00202 ON_THE_FLY_DEBUG << "erasing range " << *i; 00203 i = m_spellCheckQueue.erase(i); 00204 deleteMovingRangeQuickly(spellCheckRange); 00205 } 00206 else if(consideredRange.contains(*spellCheckRange)) { 00207 ON_THE_FLY_DEBUG << "erasing range " << *i; 00208 i = m_spellCheckQueue.erase(i); 00209 deleteMovingRangeQuickly(spellCheckRange); 00210 } 00211 else if(consideredRange.overlaps(*spellCheckRange)) { 00212 consideredRange.expandToRange(*spellCheckRange); 00213 ON_THE_FLY_DEBUG << "erasing range " << *i; 00214 i = m_spellCheckQueue.erase(i); 00215 deleteMovingRangeQuickly(spellCheckRange); 00216 } 00217 else { 00218 ++i; 00219 } 00220 } 00221 KTextEditor::Range spellCheckRange = findWordBoundaries(consideredRange.start(), 00222 consideredRange.end()); 00223 const bool emptyAtStart = m_spellCheckQueue.isEmpty(); 00224 00225 queueSpellCheckVisibleRange(spellCheckRange); 00226 00227 if(spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) { 00228 QTimer::singleShot(0, this, SLOT(performSpellCheck())); 00229 } 00230 } 00231 00232 void KateOnTheFlyChecker::textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range) 00233 { 00234 Q_ASSERT(document == m_document); 00235 Q_UNUSED(document); 00236 if(!range.isValid()) { 00237 return; 00238 } 00239 00240 bool listEmptyAtStart = m_modificationList.isEmpty(); 00241 00242 // don't consider a range that is behind the end of the document 00243 const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range); 00244 if(!documentIntersection.isValid()) { // the intersection might however be empty if the last 00245 return; // word has been removed, for example 00246 } 00247 00248 // for performance reasons we only want to schedule spellchecks for ranges that are visible 00249 foreach(KTextEditor::View *i, m_document->views()) { 00250 KateView *view = static_cast<KateView*>(i); 00251 KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange()); 00252 if(visibleIntersection.isValid()) { // see above 00253 // we don't handle this directly as the highlighting information might not be up-to-date yet 00254 KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection); 00255 movingRange->setFeedback(this); 00256 m_modificationList.push_back(ModificationItem(TEXT_REMOVED, movingRange)); 00257 ON_THE_FLY_DEBUG << "added" << *movingRange << view->visibleRange(); 00258 } 00259 } 00260 if(listEmptyAtStart && !m_modificationList.isEmpty()) { 00261 QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); 00262 } 00263 } 00264 00265 inline bool rangesAdjacent(const KTextEditor::Range &r1, const KTextEditor::Range &r2) 00266 { 00267 return (r1.end() == r2.start()) || (r2.end() == r1.start()); 00268 } 00269 00270 void KateOnTheFlyChecker::handleRemovedText(const KTextEditor::Range &range) 00271 { 00272 00273 ON_THE_FLY_DEBUG << range; 00274 00275 QList<KTextEditor::Range> rangesToReCheck; 00276 for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin(); 00277 i != m_spellCheckQueue.end();) { 00278 KTextEditor::MovingRange *spellCheckRange = (*i).first; 00279 if(rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range)) { 00280 ON_THE_FLY_DEBUG << "erasing range " << *i; 00281 if(!spellCheckRange->isEmpty()) { 00282 rangesToReCheck.push_back(*spellCheckRange); 00283 } 00284 deleteMovingRangeQuickly(spellCheckRange); 00285 i = m_spellCheckQueue.erase(i); 00286 } 00287 else { 00288 ++i; 00289 } 00290 } 00291 bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem; 00292 const bool emptyAtStart = m_spellCheckQueue.isEmpty(); 00293 if(spellCheckInProgress) { 00294 KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; 00295 ON_THE_FLY_DEBUG << *spellCheckRange; 00296 if(m_document->documentRange().contains(*spellCheckRange) 00297 && (rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range)) 00298 && !spellCheckRange->isEmpty()) { 00299 rangesToReCheck.push_back(*spellCheckRange); 00300 ON_THE_FLY_DEBUG << "added the range " << *spellCheckRange; 00301 stopCurrentSpellCheck(); 00302 deleteMovingRangeQuickly(spellCheckRange); 00303 } 00304 else if(spellCheckRange->isEmpty()) { 00305 stopCurrentSpellCheck(); 00306 deleteMovingRangeQuickly(spellCheckRange); 00307 } 00308 else { 00309 spellCheckInProgress = false; 00310 } 00311 } 00312 for(QList<KTextEditor::Range>::iterator i = rangesToReCheck.begin(); i != rangesToReCheck.end(); ++i) { 00313 queueSpellCheckVisibleRange(*i); 00314 } 00315 00316 KTextEditor::Range spellCheckRange = findWordBoundaries(range.start(), range.start()); 00317 KTextEditor::Cursor spellCheckEnd = spellCheckRange.end(); 00318 00319 queueSpellCheckVisibleRange(spellCheckRange); 00320 00321 if(range.numberOfLines() > 0) { 00322 //FIXME: there is no currently no way of doing this better as we only get notifications for removals of 00323 // of single lines, i.e. we don't know here how many lines have been removed in total 00324 KTextEditor::Cursor nextLineStart(spellCheckEnd.line() + 1, 0); 00325 const KTextEditor::Cursor documentEnd = m_document->documentEnd(); 00326 if(nextLineStart < documentEnd) { 00327 KTextEditor::Range rangeBelow = KTextEditor::Range(nextLineStart, documentEnd); 00328 00329 const QList<KTextEditor::View*>& viewList = m_document->views(); 00330 for(QList<KTextEditor::View*>::const_iterator i = viewList.begin(); i != viewList.end(); ++i) { 00331 KateView *view = static_cast<KateView*>(*i); 00332 const KTextEditor::Range visibleRange = view->visibleRange(); 00333 KTextEditor::Range intersection = visibleRange.intersect(rangeBelow); 00334 if(intersection.isValid()) { 00335 queueSpellCheckVisibleRange(view, intersection); 00336 } 00337 } 00338 } 00339 } 00340 00341 ON_THE_FLY_DEBUG << "finished"; 00342 if(spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) { 00343 QTimer::singleShot(0, this, SLOT(performSpellCheck())); 00344 } 00345 } 00346 00347 void KateOnTheFlyChecker::freeDocument() 00348 { 00349 ON_THE_FLY_DEBUG; 00350 00351 // empty the spell check queue 00352 for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin(); 00353 i != m_spellCheckQueue.end();) { 00354 ON_THE_FLY_DEBUG << "erasing range " << *i; 00355 KTextEditor::MovingRange *movingRange = (*i).first; 00356 deleteMovingRangeQuickly(movingRange); 00357 i = m_spellCheckQueue.erase(i); 00358 } 00359 if(m_currentlyCheckedItem != invalidSpellCheckQueueItem) { 00360 KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first; 00361 deleteMovingRangeQuickly(movingRange); 00362 } 00363 stopCurrentSpellCheck(); 00364 00365 MisspelledList misspelledList = m_misspelledList; // make a copy! 00366 foreach(const MisspelledItem &i, misspelledList) { 00367 deleteMovingRange(i.first); 00368 } 00369 m_misspelledList.clear(); 00370 clearModificationList(); 00371 } 00372 00373 void KateOnTheFlyChecker::performSpellCheck() 00374 { 00375 if(m_currentlyCheckedItem != invalidSpellCheckQueueItem) { 00376 ON_THE_FLY_DEBUG << "exited as a check is currently in progress"; 00377 return; 00378 } 00379 if(m_spellCheckQueue.isEmpty()) { 00380 ON_THE_FLY_DEBUG << "exited as there is nothing to do"; 00381 return; 00382 } 00383 m_currentlyCheckedItem = m_spellCheckQueue.takeFirst(); 00384 00385 KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; 00386 const QString& language = m_currentlyCheckedItem.second; 00387 ON_THE_FLY_DEBUG << "for the range " << *spellCheckRange; 00388 // clear all the highlights that are currently present in the range that 00389 // is supposed to be checked 00390 const MovingRangeList highlightsList = installedMovingRanges(*spellCheckRange); // make a copy! 00391 deleteMovingRanges(highlightsList); 00392 00393 m_currentDecToEncOffsetList.clear(); 00394 KateDocument::OffsetList encToDecOffsetList; 00395 QString text = m_document->decodeCharacters(*spellCheckRange, 00396 m_currentDecToEncOffsetList, 00397 encToDecOffsetList); 00398 ON_THE_FLY_DEBUG << "next spell checking" << text; 00399 if(text.isEmpty()) { // passing an empty string to Sonnet can lead to a bad allocation exception 00400 spellCheckDone(); // (bug 225867) 00401 return; 00402 } 00403 if(m_speller.language() != language) { 00404 m_speller.setLanguage(language); 00405 } 00406 if(!m_backgroundChecker) { 00407 m_backgroundChecker = new Sonnet::BackgroundChecker(m_speller, this); 00408 connect(m_backgroundChecker, 00409 SIGNAL(misspelling(const QString&,int)), 00410 this, 00411 SLOT(misspelling(const QString&,int))); 00412 connect(m_backgroundChecker, SIGNAL(done()), this, SLOT(spellCheckDone())); 00413 00414 #if KDE_IS_VERSION(4,5,2) 00415 // guard necessary to ensure compilation of KatePart's git repository against <= 4.5.1 00416 m_backgroundChecker->restore(KGlobal::config().data()); 00417 #endif 00418 } 00419 m_backgroundChecker->setSpeller(m_speller); 00420 m_backgroundChecker->setText(text); // don't call 'start()' after this! 00421 } 00422 00423 void KateOnTheFlyChecker::removeRangeFromEverything(KTextEditor::MovingRange *movingRange) 00424 { 00425 Q_ASSERT(m_document == movingRange->document()); 00426 ON_THE_FLY_DEBUG << *movingRange << "(" << movingRange << ")"; 00427 00428 if(removeRangeFromModificationList(movingRange)) { 00429 return; // range was part of the modification queue, so we don't have 00430 // to look further for it 00431 } 00432 00433 if(removeRangeFromSpellCheckQueue(movingRange)) { 00434 return; // range was part of the spell check queue, so it cannot have been 00435 // a misspelled range 00436 } 00437 00438 for(MisspelledList::iterator i = m_misspelledList.begin(); i != m_misspelledList.end();) { 00439 if((*i).first == movingRange) { 00440 i = m_misspelledList.erase(i); 00441 } 00442 else { 00443 ++i; 00444 } 00445 } 00446 } 00447 00448 bool KateOnTheFlyChecker::removeRangeFromCurrentSpellCheck(KTextEditor::MovingRange *range) 00449 { 00450 if(m_currentlyCheckedItem != invalidSpellCheckQueueItem 00451 && m_currentlyCheckedItem.first == range) { 00452 stopCurrentSpellCheck(); 00453 return true; 00454 } 00455 return false; 00456 } 00457 00458 void KateOnTheFlyChecker::stopCurrentSpellCheck() 00459 { 00460 m_currentDecToEncOffsetList.clear(); 00461 m_currentlyCheckedItem = invalidSpellCheckQueueItem; 00462 if(m_backgroundChecker) { 00463 m_backgroundChecker->stop(); 00464 } 00465 } 00466 00467 bool KateOnTheFlyChecker::removeRangeFromSpellCheckQueue(KTextEditor::MovingRange *range) 00468 { 00469 if(removeRangeFromCurrentSpellCheck(range)) { 00470 if(!m_spellCheckQueue.isEmpty()) { 00471 QTimer::singleShot(0, this, SLOT(performSpellCheck())); 00472 } 00473 return true; 00474 } 00475 bool found = false; 00476 for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin(); 00477 i != m_spellCheckQueue.end();) { 00478 if((*i).first == range) { 00479 i = m_spellCheckQueue.erase(i); 00480 found = true; 00481 } 00482 else { 00483 ++i; 00484 } 00485 } 00486 return found; 00487 } 00488 00489 void KateOnTheFlyChecker::rangeEmpty(KTextEditor::MovingRange *range) 00490 { 00491 ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")"; 00492 deleteMovingRange (range); 00493 } 00494 00495 void KateOnTheFlyChecker::rangeInvalid (KTextEditor::MovingRange* range) 00496 { 00497 ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")"; 00498 deleteMovingRange (range); 00499 } 00500 00501 void KateOnTheFlyChecker::mouseEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) 00502 { 00503 KateView *kateView = static_cast<KateView*>(view); 00504 kateView->spellingMenu()->mouseEnteredMisspelledRange(range); 00505 } 00506 00507 void KateOnTheFlyChecker::mouseExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) 00508 { 00509 KateView *kateView = static_cast<KateView*>(view); 00510 kateView->spellingMenu()->mouseExitedMisspelledRange(range); 00511 } 00512 00517 void KateOnTheFlyChecker::caretEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) 00518 { 00519 KateView *kateView = static_cast<KateView*>(view); 00520 kateView->spellingMenu()->caretEnteredMisspelledRange(range); 00521 } 00522 00523 void KateOnTheFlyChecker::caretExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) 00524 { 00525 KateView *kateView = static_cast<KateView*>(view); 00526 kateView->spellingMenu()->caretExitedMisspelledRange(range); 00527 } 00528 00529 void KateOnTheFlyChecker::deleteMovingRange(KTextEditor::MovingRange *range) 00530 { 00531 ON_THE_FLY_DEBUG << range; 00532 // remove it from all our structures 00533 removeRangeFromEverything(range); 00534 range->setFeedback(NULL); 00535 foreach(KTextEditor::View *view, m_document->views()) { 00536 static_cast<KateView*>(view)->spellingMenu()->rangeDeleted(range); 00537 } 00538 delete(range); 00539 } 00540 00541 void KateOnTheFlyChecker::deleteMovingRanges(const QList<KTextEditor::MovingRange*>& list) 00542 { 00543 foreach(KTextEditor::MovingRange *r, list) { 00544 deleteMovingRange(r); 00545 } 00546 } 00547 00548 KTextEditor::Range KateOnTheFlyChecker::findWordBoundaries(const KTextEditor::Cursor& begin, 00549 const KTextEditor::Cursor& end) 00550 { 00551 // FIXME: QTextBoundaryFinder should be ideally used for this, but it is currently 00552 // still broken in Qt 00553 const QRegExp boundaryRegExp("\\b"); 00554 const QRegExp boundaryQuoteRegExp("\\b\\w+'\\w*$"); // handle spell checking of "isn't", "doesn't", etc. 00555 const QRegExp extendedBoundaryRegExp("(\\W|$)"); 00556 const QRegExp extendedBoundaryQuoteRegExp("^\\w*'\\w+\\b"); // see above 00557 KateDocument::OffsetList decToEncOffsetList, encToDecOffsetList; 00558 const int startLine = begin.line(); 00559 const int startColumn = begin.column(); 00560 KTextEditor::Cursor boundaryStart, boundaryEnd; 00561 // first we take care of the start position 00562 const KTextEditor::Range startLineRange(startLine, 0, startLine, m_document->lineLength(startLine)); 00563 QString decodedLineText = m_document->decodeCharacters(startLineRange, 00564 decToEncOffsetList, 00565 encToDecOffsetList); 00566 int translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList, 00567 startColumn); 00568 QString text = decodedLineText.mid(0, translatedColumn); 00569 boundaryStart.setLine(startLine); 00570 int match = text.lastIndexOf(boundaryQuoteRegExp); 00571 if(match < 0) { 00572 match = text.lastIndexOf(boundaryRegExp); 00573 } 00574 boundaryStart.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList, qMax(0, match))); 00575 // and now the end position 00576 const int endLine = end.line(); 00577 const int endColumn = end.column(); 00578 if(endLine != startLine) { 00579 decToEncOffsetList.clear(); 00580 encToDecOffsetList.clear(); 00581 const KTextEditor::Range endLineRange(endLine, 0, endLine, m_document->lineLength(endLine)); 00582 decodedLineText = m_document->decodeCharacters(endLineRange, 00583 decToEncOffsetList, 00584 encToDecOffsetList); 00585 } 00586 translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList, 00587 endColumn); 00588 text = decodedLineText.mid(translatedColumn); 00589 boundaryEnd.setLine(endLine); 00590 match = extendedBoundaryQuoteRegExp.indexIn(text); 00591 if(match == 0) { 00592 match = extendedBoundaryQuoteRegExp.matchedLength(); 00593 } 00594 else { 00595 match = extendedBoundaryRegExp.indexIn(text); 00596 } 00597 boundaryEnd.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList, 00598 translatedColumn + qMax(0, match))); 00599 return KTextEditor::Range(boundaryStart, boundaryEnd); 00600 } 00601 00602 void KateOnTheFlyChecker::misspelling(const QString &word, int start) 00603 { 00604 if(m_currentlyCheckedItem == invalidSpellCheckQueueItem) { 00605 ON_THE_FLY_DEBUG << "exited as no spell check is taking place"; 00606 return; 00607 } 00608 int translatedStart = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList, 00609 start); 00610 // ON_THE_FLY_DEBUG << "misspelled " << word 00611 // << " at line " 00612 // << *m_currentlyCheckedItem.first 00613 // << " column " << start; 00614 00615 KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; 00616 int line = spellCheckRange->start().line(); 00617 int rangeStart = spellCheckRange->start().column(); 00618 int translatedEnd = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList, 00619 start + word.length()); 00620 00621 KTextEditor::MovingRange *movingRange = 00622 m_document->newMovingRange(KTextEditor::Range(line, 00623 rangeStart + translatedStart, 00624 line, 00625 rangeStart + translatedEnd)); 00626 movingRange->setFeedback(this); 00627 KTextEditor::Attribute *attribute = new KTextEditor::Attribute(); 00628 attribute->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); 00629 attribute->setUnderlineColor(KateRendererConfig::global()->spellingMistakeLineColor()); 00630 00631 // don't print this range 00632 movingRange->setAttributeOnlyForViews (true); 00633 00634 movingRange->setAttribute(KTextEditor::Attribute::Ptr(attribute)); 00635 m_misspelledList.push_back(MisspelledItem(movingRange, m_currentlyCheckedItem.second)); 00636 00637 if(m_backgroundChecker) { 00638 m_backgroundChecker->continueChecking(); 00639 } 00640 } 00641 00642 void KateOnTheFlyChecker::spellCheckDone() 00643 { 00644 ON_THE_FLY_DEBUG << "on-the-fly spell check done, queue length " << m_spellCheckQueue.size(); 00645 if(m_currentlyCheckedItem == invalidSpellCheckQueueItem) { 00646 return; 00647 } 00648 KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first; 00649 stopCurrentSpellCheck(); 00650 deleteMovingRangeQuickly(movingRange); 00651 00652 if(!m_spellCheckQueue.empty()) { 00653 QTimer::singleShot(0, this, SLOT(performSpellCheck())); 00654 } 00655 } 00656 00657 QList<KTextEditor::MovingRange*> KateOnTheFlyChecker::installedMovingRanges(const KTextEditor::Range& range) 00658 { 00659 ON_THE_FLY_DEBUG << range; 00660 MovingRangeList toReturn; 00661 00662 for(QList<SpellCheckItem>::iterator i = m_misspelledList.begin(); 00663 i != m_misspelledList.end(); ++i) { 00664 KTextEditor::MovingRange *movingRange = (*i).first; 00665 if(movingRange->overlaps(range)) { 00666 toReturn.push_back(movingRange); 00667 } 00668 } 00669 return toReturn; 00670 } 00671 00672 void KateOnTheFlyChecker::updateConfig() 00673 { 00674 ON_THE_FLY_DEBUG; 00675 m_speller.restore(KGlobal::config().data()); 00676 00677 #if KDE_IS_VERSION(4,5,2) 00678 // guard necessary to ensure compilation of KatePart's git repository against <= 4.5.1 00679 if(m_backgroundChecker) { 00680 m_backgroundChecker->restore(KGlobal::config().data()); 00681 } 00682 #endif 00683 } 00684 00685 void KateOnTheFlyChecker::refreshSpellCheck(const KTextEditor::Range &range) 00686 { 00687 if(range.isValid()) { 00688 textInserted(m_document, range); 00689 } 00690 else { 00691 freeDocument(); 00692 textInserted(m_document, m_document->documentRange()); 00693 } 00694 } 00695 00696 void KateOnTheFlyChecker::addView(KTextEditor::Document *document, KTextEditor::View *view) 00697 { 00698 Q_ASSERT(document == m_document); 00699 Q_UNUSED(document); 00700 ON_THE_FLY_DEBUG; 00701 connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*))); 00702 connect(view, SIGNAL(displayRangeChanged(KateView*)), this, SLOT(restartViewRefreshTimer(KateView*))); 00703 updateInstalledMovingRanges(static_cast<KateView*>(view)); 00704 } 00705 00706 void KateOnTheFlyChecker::viewDestroyed(QObject* obj) 00707 { 00708 ON_THE_FLY_DEBUG; 00709 KTextEditor::View *view = static_cast<KTextEditor::View*>(obj); 00710 m_displayRangeMap.remove(view); 00711 } 00712 00713 void KateOnTheFlyChecker::removeView(KTextEditor::View *view) 00714 { 00715 ON_THE_FLY_DEBUG; 00716 m_displayRangeMap.remove(view); 00717 } 00718 00719 void KateOnTheFlyChecker::updateInstalledMovingRanges(KateView *view) 00720 { 00721 Q_ASSERT(m_document == view->document()); 00722 ON_THE_FLY_DEBUG; 00723 KTextEditor::Range oldDisplayRange = m_displayRangeMap[view]; 00724 00725 KTextEditor::Range newDisplayRange = view->visibleRange(); 00726 ON_THE_FLY_DEBUG << "new range: " << newDisplayRange; 00727 ON_THE_FLY_DEBUG << "old range: " << oldDisplayRange; 00728 QList<KTextEditor::MovingRange*> toDelete; 00729 foreach(const MisspelledItem &item, m_misspelledList) { 00730 KTextEditor::MovingRange *movingRange = item.first; 00731 if(!movingRange->overlaps(newDisplayRange)) { 00732 bool stillVisible = false; 00733 foreach(KTextEditor::View *it2, m_document->views()) { 00734 KateView *view2 = static_cast<KateView*>(it2); 00735 if(view != view2 && movingRange->overlaps(view2->visibleRange())) { 00736 stillVisible = true; 00737 break; 00738 } 00739 } 00740 if(!stillVisible) { 00741 toDelete.push_back(movingRange); 00742 } 00743 } 00744 } 00745 deleteMovingRanges (toDelete); 00746 m_displayRangeMap[view] = newDisplayRange; 00747 if(oldDisplayRange.isValid()) { 00748 bool emptyAtStart = m_spellCheckQueue.empty(); 00749 for(int line = newDisplayRange.end().line(); line >= newDisplayRange.start().line(); --line) { 00750 if(!oldDisplayRange.containsLine(line)) { 00751 bool visible = false; 00752 foreach(KTextEditor::View *it2, m_document->views()) { 00753 KateView *view2 = static_cast<KateView*>(it2); 00754 if(view != view2 && view2->visibleRange().containsLine(line)) { 00755 visible = true; 00756 break; 00757 } 00758 } 00759 if(!visible) { 00760 queueLineSpellCheck(m_document, line); 00761 } 00762 } 00763 } 00764 if(emptyAtStart && !m_spellCheckQueue.isEmpty()) { 00765 QTimer::singleShot(0, this, SLOT(performSpellCheck())); 00766 } 00767 } 00768 } 00769 00770 void KateOnTheFlyChecker::queueSpellCheckVisibleRange(const KTextEditor::Range& range) 00771 { 00772 const QList<KTextEditor::View*>& viewList = m_document->views(); 00773 for(QList<KTextEditor::View*>::const_iterator i = viewList.begin(); i != viewList.end(); ++i) { 00774 queueSpellCheckVisibleRange(static_cast<KateView*>(*i), range); 00775 } 00776 } 00777 00778 void KateOnTheFlyChecker::queueSpellCheckVisibleRange(KateView *view, const KTextEditor::Range& range) 00779 { 00780 Q_ASSERT(m_document == view->doc()); 00781 KTextEditor::Range visibleRange = view->visibleRange(); 00782 KTextEditor::Range intersection = visibleRange.intersect(range); 00783 if(intersection.isEmpty()) { 00784 return; 00785 } 00786 00787 // clear all the highlights that are currently present in the range that 00788 // is supposed to be checked, necessary due to highlighting 00789 const MovingRangeList highlightsList = installedMovingRanges(intersection); 00790 deleteMovingRanges(highlightsList); 00791 00792 QList<QPair<KTextEditor::Range, QString> > spellCheckRanges 00793 = KateGlobal::self()->spellCheckManager()->spellCheckRanges(m_document, 00794 intersection, 00795 true); 00796 //we queue them up in reverse 00797 QListIterator<QPair<KTextEditor::Range, QString> > i(spellCheckRanges); 00798 i.toBack(); 00799 while(i.hasPrevious()) { 00800 QPair<KTextEditor::Range, QString> p = i.previous(); 00801 queueLineSpellCheck(p.first, p.second); 00802 } 00803 } 00804 00805 void KateOnTheFlyChecker::queueLineSpellCheck(KateDocument *kateDocument, int line) 00806 { 00807 const KTextEditor::Range range = KTextEditor::Range(line, 0, line, kateDocument->lineLength(line)); 00808 // clear all the highlights that are currently present in the range that 00809 // is supposed to be checked, necessary due to highlighting 00810 00811 const MovingRangeList highlightsList = installedMovingRanges(range); 00812 deleteMovingRanges(highlightsList); 00813 00814 QList<QPair<KTextEditor::Range, QString> > spellCheckRanges 00815 = KateGlobal::self()->spellCheckManager()->spellCheckRanges(kateDocument, 00816 range, 00817 true); 00818 //we queue them up in reverse 00819 QListIterator<QPair<KTextEditor::Range, QString> > i(spellCheckRanges); 00820 i.toBack(); 00821 while(i.hasPrevious()) { 00822 QPair<KTextEditor::Range, QString> p = i.previous(); 00823 queueLineSpellCheck(p.first, p.second); 00824 } 00825 } 00826 00827 void KateOnTheFlyChecker::queueLineSpellCheck(const KTextEditor::Range& range, const QString& dictionary) 00828 { 00829 ON_THE_FLY_DEBUG << m_document << range; 00830 00831 Q_ASSERT(range.onSingleLine()); 00832 00833 if(range.isEmpty()) { 00834 return; 00835 } 00836 00837 addToSpellCheckQueue(range, dictionary); 00838 } 00839 00840 void KateOnTheFlyChecker::addToSpellCheckQueue(const KTextEditor::Range& range, const QString& dictionary) 00841 { 00842 addToSpellCheckQueue(m_document->newMovingRange(range), dictionary); 00843 } 00844 00845 void KateOnTheFlyChecker::addToSpellCheckQueue(KTextEditor::MovingRange *range, const QString& dictionary) 00846 { 00847 ON_THE_FLY_DEBUG << m_document << *range << dictionary; 00848 00849 range->setFeedback(this); 00850 00851 // if the queue contains a subrange of 'range', we remove that one 00852 for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin(); 00853 i != m_spellCheckQueue.end();) { 00854 KTextEditor::MovingRange *spellCheckRange = (*i).first; 00855 if(range->contains(*spellCheckRange)) { 00856 deleteMovingRangeQuickly(spellCheckRange); 00857 i = m_spellCheckQueue.erase(i); 00858 } 00859 else { 00860 ++i; 00861 } 00862 } 00863 // leave 'push_front' here as it is a LIFO queue, i.e. a stack 00864 m_spellCheckQueue.push_front(SpellCheckItem(range, dictionary)); 00865 ON_THE_FLY_DEBUG << "added" 00866 << *range << dictionary 00867 << "to the queue, which has a length of" << m_spellCheckQueue.size(); 00868 } 00869 00870 void KateOnTheFlyChecker::viewRefreshTimeout() 00871 { 00872 if(m_refreshView) { 00873 updateInstalledMovingRanges(m_refreshView); 00874 } 00875 m_refreshView = NULL; 00876 } 00877 00878 void KateOnTheFlyChecker::restartViewRefreshTimer(KateView *view) 00879 { 00880 if(m_refreshView && view != m_refreshView) { // a new view should be refreshed 00881 updateInstalledMovingRanges(m_refreshView); // so refresh the old one first 00882 } 00883 m_refreshView = view; 00884 m_viewRefreshTimer->start(100); 00885 } 00886 00887 void KateOnTheFlyChecker::deleteMovingRangeQuickly(KTextEditor::MovingRange *range) 00888 { 00889 range->setFeedback(NULL); 00890 foreach(KTextEditor::View *view, m_document->views()) { 00891 static_cast<KateView*>(view)->spellingMenu()->rangeDeleted(range); 00892 } 00893 delete(range); 00894 } 00895 00896 void KateOnTheFlyChecker::handleModifiedRanges() 00897 { 00898 foreach(const ModificationItem &item, m_modificationList) { 00899 KTextEditor::MovingRange *movingRange = item.second; 00900 KTextEditor::Range range = *movingRange; 00901 deleteMovingRangeQuickly(movingRange); 00902 if(item.first == TEXT_INSERTED) { 00903 handleInsertedText(range); 00904 } 00905 else { 00906 handleRemovedText(range); 00907 } 00908 } 00909 m_modificationList.clear(); 00910 } 00911 00912 bool KateOnTheFlyChecker::removeRangeFromModificationList(KTextEditor::MovingRange *range) 00913 { 00914 bool found = false; 00915 for(ModificationList::iterator i = m_modificationList.begin(); i != m_modificationList.end();) { 00916 ModificationItem item = *i; 00917 KTextEditor::MovingRange *movingRange = item.second; 00918 if(movingRange == range) { 00919 found = true; 00920 i = m_modificationList.erase(i); 00921 } 00922 else { 00923 ++i; 00924 } 00925 } 00926 return found; 00927 } 00928 00929 void KateOnTheFlyChecker::clearModificationList() 00930 { 00931 foreach(const ModificationItem &item, m_modificationList) { 00932 KTextEditor::MovingRange *movingRange = item.second; 00933 deleteMovingRangeQuickly(movingRange); 00934 } 00935 m_modificationList.clear(); 00936 } 00937 00938 #include "ontheflycheck.moc" 00939 00940 // kate: space-indent on; indent-width 2; replace-tabs on;
KDE 4.6 API Reference