• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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;

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal