~ubuntu-branches/ubuntu/oneiric/kde4libs/oneiric-proposed

« back to all changes in this revision

Viewing changes to kate/spellcheck/ontheflycheck.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2011-07-08 00:08:34 UTC
  • mto: This revision was merged to the branch mainline in revision 247.
  • Revision ID: package-import@ubuntu.com-20110708000834-dr9a8my4iml90qe5
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  This file is part of the KDE libraries and the Kate part.
2
 
 *
3
 
 *  Copyright (C) 2008-2010 by Michel Ludwig <michel.ludwig@kdemail.net>
4
 
 *  Copyright (C) 2009 by Joseph Wenninger <jowenn@kde.org>
5
 
 *
6
 
 *  This library is free software; you can redistribute it and/or
7
 
 *  modify it under the terms of the GNU Library General Public
8
 
 *  License as published by the Free Software Foundation; either
9
 
 *  version 2 of the License, or (at your option) any later version.
10
 
 *
11
 
 *  This library is distributed in the hope that it will be useful,
12
 
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 
 *  Library General Public License for more details.
15
 
 *
16
 
 *  You should have received a copy of the GNU Library General Public License
17
 
 *  along with this library; see the file COPYING.LIB.  If not, write to
18
 
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19
 
 *  Boston, MA 02110-1301, USA.
20
 
 */
21
 
 
22
 
/* If ever threads should be used again, thread communication and
23
 
 * synchronization ought to be done with blocking queued signal connections.
24
 
 */
25
 
 
26
 
#include "ontheflycheck.h"
27
 
 
28
 
#include <QTimer>
29
 
 
30
 
#include "kateconfig.h"
31
 
#include "kateglobal.h"
32
 
#include "katerenderer.h"
33
 
#include "kateview.h"
34
 
#include "spellcheck.h"
35
 
#include "spellingmenu.h"
36
 
 
37
 
#define ON_THE_FLY_DEBUG kDebug(debugArea())
38
 
 
39
 
KateOnTheFlyChecker::KateOnTheFlyChecker(KateDocument *document)
40
 
: QObject(document),
41
 
  m_document(document),
42
 
  m_backgroundChecker(NULL),
43
 
  m_currentlyCheckedItem(invalidSpellCheckQueueItem),
44
 
  m_refreshView(NULL)
45
 
{
46
 
  ON_THE_FLY_DEBUG << "created";
47
 
 
48
 
  m_viewRefreshTimer = new QTimer(this);
49
 
  m_viewRefreshTimer->setSingleShot(true);
50
 
  connect(m_viewRefreshTimer, SIGNAL(timeout()), this, SLOT(viewRefreshTimeout()));
51
 
 
52
 
  connect(document, SIGNAL(textInserted(KTextEditor::Document*, const KTextEditor::Range&)),
53
 
          this, SLOT(textInserted(KTextEditor::Document*, const KTextEditor::Range&)));
54
 
  connect(document, SIGNAL(textRemoved(KTextEditor::Document*, const KTextEditor::Range&)),
55
 
          this, SLOT(textRemoved(KTextEditor::Document*, const KTextEditor::Range&)));
56
 
  connect(document, SIGNAL(viewCreated(KTextEditor::Document*, KTextEditor::View*)),
57
 
          this, SLOT(addView(KTextEditor::Document*, KTextEditor::View*)));
58
 
  connect(document, SIGNAL(highlightingModeChanged (KTextEditor::Document*)),
59
 
          this, SLOT(updateConfig()));
60
 
  connect(document, SIGNAL(respellCheckBlock(KateDocument*, int, int)),
61
 
          this, SLOT(handleRespellCheckBlock(KateDocument*, int, int)));
62
 
 
63
 
  // load the settings for the speller
64
 
  updateConfig();
65
 
 
66
 
  foreach(KTextEditor::View* view, document->views()) {
67
 
    addView(document, view);
68
 
  }
69
 
  refreshSpellCheck();
70
 
}
71
 
 
72
 
KateOnTheFlyChecker::~KateOnTheFlyChecker()
73
 
{
74
 
  freeDocument();
75
 
}
76
 
 
77
 
int KateOnTheFlyChecker::debugArea()
78
 
{
79
 
  static int s_area = KDebug::registerArea("Kate (On-The-Fly Spellcheck)");
80
 
  return s_area;
81
 
}
82
 
 
83
 
QPair<KTextEditor::Range, QString> KateOnTheFlyChecker::getMisspelledItem(const KTextEditor::Cursor &cursor) const
84
 
{
85
 
  foreach(const MisspelledItem &item, m_misspelledList) {
86
 
    KTextEditor::MovingRange *movingRange = item.first;
87
 
    if(movingRange->contains(cursor)) {
88
 
      return QPair<KTextEditor::Range, QString>(*movingRange, item.second);
89
 
    }
90
 
  }
91
 
  return QPair<KTextEditor::Range, QString>(KTextEditor::Range::invalid(), QString());
92
 
}
93
 
 
94
 
QString KateOnTheFlyChecker::dictionaryForMisspelledRange(const KTextEditor::Range& range) const
95
 
{
96
 
  foreach(const MisspelledItem &item, m_misspelledList) {
97
 
    KTextEditor::MovingRange *movingRange = item.first;
98
 
    if(*movingRange == range) {
99
 
      return item.second;
100
 
    }
101
 
  }
102
 
  return QString();
103
 
}
104
 
 
105
 
void KateOnTheFlyChecker::clearMisspellingForWord(const QString& word)
106
 
{
107
 
  MisspelledList misspelledList = m_misspelledList; // make a copy
108
 
  foreach(const MisspelledItem &item, misspelledList) {
109
 
    KTextEditor::MovingRange *movingRange = item.first;
110
 
    if(m_document->text(*movingRange) == word) {
111
 
      deleteMovingRange(movingRange);
112
 
    }
113
 
  }
114
 
}
115
 
 
116
 
const KateOnTheFlyChecker::SpellCheckItem KateOnTheFlyChecker::invalidSpellCheckQueueItem =
117
 
                           SpellCheckItem(NULL, "");
118
 
 
119
 
void KateOnTheFlyChecker::handleRespellCheckBlock(KateDocument *kateDocument, int start, int end)
120
 
{
121
 
  Q_ASSERT(kateDocument == m_document);
122
 
  Q_UNUSED(kateDocument);
123
 
 
124
 
  ON_THE_FLY_DEBUG << start << end;
125
 
  KTextEditor::Range range(start, 0, end, m_document->lineLength(end));
126
 
  bool listEmpty = m_modificationList.isEmpty();
127
 
  KTextEditor::MovingRange *movingRange = m_document->newMovingRange(range);
128
 
  movingRange->setFeedback(this);
129
 
  // we don't handle this directly as the highlighting information might not be up-to-date yet
130
 
  m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange));
131
 
  ON_THE_FLY_DEBUG << "added" << *movingRange;
132
 
  if(listEmpty) {
133
 
    QTimer::singleShot(0, this, SLOT(handleModifiedRanges()));
134
 
  }
135
 
}
136
 
 
137
 
void KateOnTheFlyChecker::textInserted(KTextEditor::Document *document, const KTextEditor::Range &range)
138
 
{
139
 
  Q_ASSERT(document == m_document);
140
 
  Q_UNUSED(document);
141
 
  if(!range.isValid()) {
142
 
    return;
143
 
  }
144
 
 
145
 
  bool listEmptyAtStart = m_modificationList.isEmpty();
146
 
 
147
 
  // don't consider a range that is not within the document range
148
 
  const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range);
149
 
  if(!documentIntersection.isValid()) {
150
 
    return;
151
 
  }
152
 
  // for performance reasons we only want to schedule spellchecks for ranges that are visible
153
 
  foreach(KTextEditor::View* i, m_document->views()) {
154
 
    KateView *view = static_cast<KateView*>(i);
155
 
    KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange());
156
 
    if(visibleIntersection.isValid()) { // allow empty intersections
157
 
      // we don't handle this directly as the highlighting information might not be up-to-date yet
158
 
      KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection);
159
 
      movingRange->setFeedback(this);
160
 
      m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange));
161
 
      ON_THE_FLY_DEBUG << "added" << *movingRange;
162
 
    }
163
 
  }
164
 
 
165
 
  if(listEmptyAtStart && !m_modificationList.isEmpty()) {
166
 
    QTimer::singleShot(0, this, SLOT(handleModifiedRanges()));
167
 
  }
168
 
}
169
 
 
170
 
void KateOnTheFlyChecker::handleInsertedText(const KTextEditor::Range &range)
171
 
{
172
 
  KTextEditor::Range consideredRange = range;
173
 
  ON_THE_FLY_DEBUG << m_document << range;
174
 
 
175
 
  bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem;
176
 
 
177
 
  if(spellCheckInProgress) {
178
 
    KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first;
179
 
    if(spellCheckRange->contains(consideredRange)) {
180
 
      consideredRange = *spellCheckRange;
181
 
      stopCurrentSpellCheck();
182
 
      deleteMovingRangeQuickly(spellCheckRange);
183
 
    }
184
 
    else if(consideredRange.contains(*spellCheckRange)) {
185
 
      stopCurrentSpellCheck();
186
 
      deleteMovingRangeQuickly(spellCheckRange);
187
 
    }
188
 
    else if(consideredRange.overlaps(*spellCheckRange)) {
189
 
      consideredRange.expandToRange(*spellCheckRange);
190
 
      stopCurrentSpellCheck();
191
 
      deleteMovingRangeQuickly(spellCheckRange);
192
 
    }
193
 
    else {
194
 
      spellCheckInProgress = false;
195
 
    }
196
 
  }
197
 
  for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin();
198
 
                                          i != m_spellCheckQueue.end();) {
199
 
    KTextEditor::MovingRange *spellCheckRange = (*i).first;
200
 
    if(spellCheckRange->contains(consideredRange)) {
201
 
      consideredRange = *spellCheckRange;
202
 
      ON_THE_FLY_DEBUG << "erasing range " << *i;
203
 
      i = m_spellCheckQueue.erase(i);
204
 
      deleteMovingRangeQuickly(spellCheckRange);
205
 
    }
206
 
    else if(consideredRange.contains(*spellCheckRange)) {
207
 
      ON_THE_FLY_DEBUG << "erasing range " << *i;
208
 
      i = m_spellCheckQueue.erase(i);
209
 
      deleteMovingRangeQuickly(spellCheckRange);
210
 
    }
211
 
    else if(consideredRange.overlaps(*spellCheckRange)) {
212
 
      consideredRange.expandToRange(*spellCheckRange);
213
 
      ON_THE_FLY_DEBUG << "erasing range " << *i;
214
 
      i = m_spellCheckQueue.erase(i);
215
 
      deleteMovingRangeQuickly(spellCheckRange);
216
 
    }
217
 
    else {
218
 
      ++i;
219
 
    }
220
 
  }
221
 
  KTextEditor::Range spellCheckRange = findWordBoundaries(consideredRange.start(),
222
 
                                                          consideredRange.end());
223
 
  const bool emptyAtStart = m_spellCheckQueue.isEmpty();
224
 
 
225
 
  queueSpellCheckVisibleRange(spellCheckRange);
226
 
 
227
 
  if(spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) {
228
 
    QTimer::singleShot(0, this, SLOT(performSpellCheck()));
229
 
  }
230
 
}
231
 
 
232
 
void KateOnTheFlyChecker::textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range)
233
 
{
234
 
  Q_ASSERT(document == m_document);
235
 
  Q_UNUSED(document);
236
 
  if(!range.isValid()) {
237
 
    return;
238
 
  }
239
 
 
240
 
  bool listEmptyAtStart = m_modificationList.isEmpty();
241
 
 
242
 
  // don't consider a range that is behind the end of the document
243
 
  const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range);
244
 
  if(!documentIntersection.isValid()) { // the intersection might however be empty if the last
245
 
    return;                             // word has been removed, for example
246
 
  }
247
 
 
248
 
  // for performance reasons we only want to schedule spellchecks for ranges that are visible
249
 
  foreach(KTextEditor::View *i, m_document->views()) {
250
 
    KateView *view = static_cast<KateView*>(i);
251
 
    KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange());
252
 
    if(visibleIntersection.isValid()) { // see above
253
 
      // we don't handle this directly as the highlighting information might not be up-to-date yet
254
 
      KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection);
255
 
      movingRange->setFeedback(this);
256
 
      m_modificationList.push_back(ModificationItem(TEXT_REMOVED, movingRange));
257
 
      ON_THE_FLY_DEBUG << "added" << *movingRange << view->visibleRange();
258
 
    }
259
 
  }
260
 
  if(listEmptyAtStart && !m_modificationList.isEmpty()) {
261
 
    QTimer::singleShot(0, this, SLOT(handleModifiedRanges()));
262
 
  }
263
 
}
264
 
 
265
 
inline bool rangesAdjacent(const KTextEditor::Range &r1, const KTextEditor::Range &r2)
266
 
{
267
 
  return (r1.end() == r2.start()) || (r2.end() == r1.start());
268
 
}
269
 
 
270
 
void KateOnTheFlyChecker::handleRemovedText(const KTextEditor::Range &range)
271
 
{
272
 
 
273
 
  ON_THE_FLY_DEBUG << range;
274
 
 
275
 
  QList<KTextEditor::Range> rangesToReCheck;
276
 
  for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin();
277
 
                                          i != m_spellCheckQueue.end();) {
278
 
    KTextEditor::MovingRange *spellCheckRange = (*i).first;
279
 
    if(rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range)) {
280
 
      ON_THE_FLY_DEBUG << "erasing range " << *i;
281
 
      if(!spellCheckRange->isEmpty()) {
282
 
        rangesToReCheck.push_back(*spellCheckRange);
283
 
      }
284
 
      deleteMovingRangeQuickly(spellCheckRange);
285
 
      i = m_spellCheckQueue.erase(i);
286
 
    }
287
 
    else {
288
 
      ++i;
289
 
    }
290
 
  }
291
 
  bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem;
292
 
  const bool emptyAtStart = m_spellCheckQueue.isEmpty();
293
 
  if(spellCheckInProgress) {
294
 
    KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first;
295
 
    ON_THE_FLY_DEBUG << *spellCheckRange;
296
 
    if(m_document->documentRange().contains(*spellCheckRange)
297
 
         && (rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range))
298
 
         && !spellCheckRange->isEmpty()) {
299
 
      rangesToReCheck.push_back(*spellCheckRange);
300
 
      ON_THE_FLY_DEBUG << "added the range " << *spellCheckRange;
301
 
      stopCurrentSpellCheck();
302
 
      deleteMovingRangeQuickly(spellCheckRange);
303
 
    }
304
 
    else if(spellCheckRange->isEmpty()) {
305
 
      stopCurrentSpellCheck();
306
 
      deleteMovingRangeQuickly(spellCheckRange);
307
 
    }
308
 
    else {
309
 
      spellCheckInProgress = false;
310
 
    }
311
 
  }
312
 
  for(QList<KTextEditor::Range>::iterator i = rangesToReCheck.begin(); i != rangesToReCheck.end(); ++i) {
313
 
    queueSpellCheckVisibleRange(*i);
314
 
  }
315
 
 
316
 
  KTextEditor::Range spellCheckRange = findWordBoundaries(range.start(), range.start());
317
 
  KTextEditor::Cursor spellCheckEnd = spellCheckRange.end();
318
 
 
319
 
  queueSpellCheckVisibleRange(spellCheckRange);
320
 
 
321
 
  if(range.numberOfLines() > 0) {
322
 
    //FIXME: there is no currently no way of doing this better as we only get notifications for removals of
323
 
    //       of single lines, i.e. we don't know here how many lines have been removed in total
324
 
    KTextEditor::Cursor nextLineStart(spellCheckEnd.line() + 1, 0);
325
 
    const KTextEditor::Cursor documentEnd = m_document->documentEnd();
326
 
    if(nextLineStart < documentEnd) {
327
 
      KTextEditor::Range rangeBelow = KTextEditor::Range(nextLineStart, documentEnd);
328
 
 
329
 
      const QList<KTextEditor::View*>& viewList = m_document->views();
330
 
      for(QList<KTextEditor::View*>::const_iterator i = viewList.begin(); i != viewList.end(); ++i) {
331
 
        KateView *view = static_cast<KateView*>(*i);
332
 
        const KTextEditor::Range visibleRange = view->visibleRange();
333
 
        KTextEditor::Range intersection = visibleRange.intersect(rangeBelow);
334
 
        if(intersection.isValid()) {
335
 
          queueSpellCheckVisibleRange(view, intersection);
336
 
        }
337
 
      }
338
 
    }
339
 
  }
340
 
 
341
 
  ON_THE_FLY_DEBUG << "finished";
342
 
  if(spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) {
343
 
    QTimer::singleShot(0, this, SLOT(performSpellCheck()));
344
 
  }
345
 
}
346
 
 
347
 
void KateOnTheFlyChecker::freeDocument()
348
 
{
349
 
  ON_THE_FLY_DEBUG;
350
 
 
351
 
  // empty the spell check queue
352
 
  for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin();
353
 
                                      i != m_spellCheckQueue.end();) {
354
 
      ON_THE_FLY_DEBUG << "erasing range " << *i;
355
 
      KTextEditor::MovingRange *movingRange = (*i).first;
356
 
      deleteMovingRangeQuickly(movingRange);
357
 
      i = m_spellCheckQueue.erase(i);
358
 
  }
359
 
  if(m_currentlyCheckedItem != invalidSpellCheckQueueItem) {
360
 
      KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first;
361
 
      deleteMovingRangeQuickly(movingRange);
362
 
  }
363
 
  stopCurrentSpellCheck();
364
 
  
365
 
  MisspelledList misspelledList = m_misspelledList; // make a copy!
366
 
  foreach(const MisspelledItem &i, misspelledList) {
367
 
    deleteMovingRange(i.first);
368
 
  }
369
 
  m_misspelledList.clear();
370
 
  clearModificationList();
371
 
}
372
 
 
373
 
void KateOnTheFlyChecker::performSpellCheck()
374
 
{
375
 
  if(m_currentlyCheckedItem != invalidSpellCheckQueueItem) {
376
 
    ON_THE_FLY_DEBUG << "exited as a check is currently in progress";
377
 
    return;
378
 
  }
379
 
  if(m_spellCheckQueue.isEmpty()) {
380
 
    ON_THE_FLY_DEBUG << "exited as there is nothing to do";
381
 
    return;
382
 
  }
383
 
  m_currentlyCheckedItem = m_spellCheckQueue.takeFirst();
384
 
 
385
 
  KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first;
386
 
  const QString& language = m_currentlyCheckedItem.second;
387
 
  ON_THE_FLY_DEBUG << "for the range " << *spellCheckRange;
388
 
  // clear all the highlights that are currently present in the range that
389
 
  // is supposed to be checked
390
 
  const MovingRangeList highlightsList = installedMovingRanges(*spellCheckRange); // make a copy!
391
 
  deleteMovingRanges(highlightsList);
392
 
 
393
 
  m_currentDecToEncOffsetList.clear();
394
 
  KateDocument::OffsetList encToDecOffsetList;
395
 
  QString text = m_document->decodeCharacters(*spellCheckRange,
396
 
                                              m_currentDecToEncOffsetList,
397
 
                                              encToDecOffsetList);
398
 
  ON_THE_FLY_DEBUG << "next spell checking" << text;
399
 
  if(text.isEmpty()) { // passing an empty string to Sonnet can lead to a bad allocation exception
400
 
    spellCheckDone();  // (bug 225867)
401
 
    return;
402
 
  }
403
 
  if(m_speller.language() != language) {
404
 
    m_speller.setLanguage(language);
405
 
  }
406
 
  if(!m_backgroundChecker) {
407
 
    m_backgroundChecker = new Sonnet::BackgroundChecker(m_speller, this);
408
 
    connect(m_backgroundChecker,
409
 
            SIGNAL(misspelling(const QString&,int)),
410
 
            this,
411
 
            SLOT(misspelling(const QString&,int)));
412
 
    connect(m_backgroundChecker, SIGNAL(done()), this, SLOT(spellCheckDone()));
413
 
 
414
 
#if KDE_IS_VERSION(4,5,2)
415
 
// guard necessary to ensure compilation of KatePart's git repository against <= 4.5.1
416
 
    m_backgroundChecker->restore(KGlobal::config().data());
417
 
#endif
418
 
  }
419
 
  m_backgroundChecker->setSpeller(m_speller);
420
 
  m_backgroundChecker->setText(text); // don't call 'start()' after this!
421
 
}
422
 
 
423
 
void KateOnTheFlyChecker::removeRangeFromEverything(KTextEditor::MovingRange *movingRange)
424
 
{
425
 
  Q_ASSERT(m_document == movingRange->document());
426
 
  ON_THE_FLY_DEBUG << *movingRange << "(" << movingRange << ")";
427
 
 
428
 
  if(removeRangeFromModificationList(movingRange)) {
429
 
    return; // range was part of the modification queue, so we don't have
430
 
            // to look further for it
431
 
  }
432
 
 
433
 
  if(removeRangeFromSpellCheckQueue(movingRange)) {
434
 
    return; // range was part of the spell check queue, so it cannot have been
435
 
            // a misspelled range
436
 
  }
437
 
 
438
 
  for(MisspelledList::iterator i = m_misspelledList.begin(); i != m_misspelledList.end();) {
439
 
    if((*i).first == movingRange) {
440
 
      i = m_misspelledList.erase(i);
441
 
    }
442
 
    else {
443
 
      ++i;
444
 
    }
445
 
  }
446
 
}
447
 
 
448
 
bool KateOnTheFlyChecker::removeRangeFromCurrentSpellCheck(KTextEditor::MovingRange *range)
449
 
{
450
 
  if(m_currentlyCheckedItem != invalidSpellCheckQueueItem
451
 
    && m_currentlyCheckedItem.first == range) {
452
 
    stopCurrentSpellCheck();
453
 
    return true;
454
 
  }
455
 
  return false;
456
 
}
457
 
 
458
 
void KateOnTheFlyChecker::stopCurrentSpellCheck()
459
 
{
460
 
  m_currentDecToEncOffsetList.clear();
461
 
  m_currentlyCheckedItem = invalidSpellCheckQueueItem;
462
 
  if(m_backgroundChecker) {
463
 
    m_backgroundChecker->stop();
464
 
  }
465
 
}
466
 
 
467
 
bool KateOnTheFlyChecker::removeRangeFromSpellCheckQueue(KTextEditor::MovingRange *range)
468
 
{
469
 
  if(removeRangeFromCurrentSpellCheck(range)) {
470
 
    if(!m_spellCheckQueue.isEmpty()) {
471
 
      QTimer::singleShot(0, this, SLOT(performSpellCheck()));
472
 
    }
473
 
    return true;
474
 
  }
475
 
  bool found = false;
476
 
  for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin();
477
 
                                      i != m_spellCheckQueue.end();) {
478
 
    if((*i).first == range) {
479
 
      i = m_spellCheckQueue.erase(i);
480
 
      found = true;
481
 
    }
482
 
    else {
483
 
      ++i;
484
 
    }
485
 
  }
486
 
  return found;
487
 
}
488
 
 
489
 
void KateOnTheFlyChecker::rangeEmpty(KTextEditor::MovingRange *range)
490
 
{
491
 
  ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")";
492
 
  deleteMovingRange (range);
493
 
}
494
 
 
495
 
void KateOnTheFlyChecker::rangeInvalid (KTextEditor::MovingRange* range)
496
 
{
497
 
  ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")";
498
 
  deleteMovingRange (range);
499
 
}
500
 
 
501
 
void KateOnTheFlyChecker::mouseEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view)
502
 
{
503
 
  KateView *kateView = static_cast<KateView*>(view);
504
 
  kateView->spellingMenu()->mouseEnteredMisspelledRange(range);
505
 
}
506
 
 
507
 
void KateOnTheFlyChecker::mouseExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view)
508
 
{
509
 
  KateView *kateView = static_cast<KateView*>(view);
510
 
  kateView->spellingMenu()->mouseExitedMisspelledRange(range);
511
 
}
512
 
 
513
 
/**
514
 
 * It is not enough to use 'caret/Entered/ExitedRange' only as the cursor doesn't move when some
515
 
 * text has been selected.
516
 
 **/
517
 
void KateOnTheFlyChecker::caretEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view)
518
 
{
519
 
  KateView *kateView = static_cast<KateView*>(view);
520
 
  kateView->spellingMenu()->caretEnteredMisspelledRange(range);
521
 
}
522
 
 
523
 
void KateOnTheFlyChecker::caretExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view)
524
 
{
525
 
  KateView *kateView = static_cast<KateView*>(view);
526
 
  kateView->spellingMenu()->caretExitedMisspelledRange(range);
527
 
}
528
 
 
529
 
void KateOnTheFlyChecker::deleteMovingRange(KTextEditor::MovingRange *range)
530
 
{
531
 
  ON_THE_FLY_DEBUG << range;
532
 
  // remove it from all our structures
533
 
  removeRangeFromEverything(range);
534
 
  range->setFeedback(NULL);
535
 
  foreach(KTextEditor::View *view, m_document->views()) {
536
 
    static_cast<KateView*>(view)->spellingMenu()->rangeDeleted(range);
537
 
  }
538
 
  delete(range);
539
 
}
540
 
 
541
 
void KateOnTheFlyChecker::deleteMovingRanges(const QList<KTextEditor::MovingRange*>& list)
542
 
{
543
 
  foreach(KTextEditor::MovingRange *r, list) {
544
 
    deleteMovingRange(r);
545
 
  }
546
 
}
547
 
 
548
 
KTextEditor::Range KateOnTheFlyChecker::findWordBoundaries(const KTextEditor::Cursor& begin,
549
 
                                                           const KTextEditor::Cursor& end)
550
 
{
551
 
  // FIXME: QTextBoundaryFinder should be ideally used for this, but it is currently
552
 
  //        still broken in Qt
553
 
  const QRegExp boundaryRegExp("\\b");
554
 
  const QRegExp boundaryQuoteRegExp("\\b\\w+'\\w*$");  // handle spell checking of "isn't", "doesn't", etc.
555
 
  const QRegExp extendedBoundaryRegExp("(\\W|$)");
556
 
  const QRegExp extendedBoundaryQuoteRegExp("^\\w*'\\w+\\b"); // see above
557
 
  KateDocument::OffsetList decToEncOffsetList, encToDecOffsetList;
558
 
  const int startLine = begin.line();
559
 
  const int startColumn = begin.column();
560
 
  KTextEditor::Cursor boundaryStart, boundaryEnd;
561
 
  // first we take care of the start position
562
 
  const KTextEditor::Range startLineRange(startLine, 0, startLine, m_document->lineLength(startLine));
563
 
  QString decodedLineText = m_document->decodeCharacters(startLineRange,
564
 
                                                         decToEncOffsetList,
565
 
                                                         encToDecOffsetList);
566
 
  int translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList,
567
 
                                                               startColumn);
568
 
  QString text = decodedLineText.mid(0, translatedColumn);
569
 
  boundaryStart.setLine(startLine);
570
 
  int match = text.lastIndexOf(boundaryQuoteRegExp);
571
 
  if(match < 0) {
572
 
    match = text.lastIndexOf(boundaryRegExp);
573
 
  }
574
 
  boundaryStart.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList, qMax(0, match)));
575
 
  // and now the end position
576
 
  const int endLine = end.line();
577
 
  const int endColumn = end.column();
578
 
  if(endLine != startLine) {
579
 
    decToEncOffsetList.clear();
580
 
    encToDecOffsetList.clear();
581
 
    const KTextEditor::Range endLineRange(endLine, 0, endLine, m_document->lineLength(endLine));
582
 
    decodedLineText = m_document->decodeCharacters(endLineRange,
583
 
                                                   decToEncOffsetList,
584
 
                                                   encToDecOffsetList);
585
 
  }
586
 
  translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList,
587
 
                                                           endColumn);
588
 
  text = decodedLineText.mid(translatedColumn);
589
 
  boundaryEnd.setLine(endLine);
590
 
  match = extendedBoundaryQuoteRegExp.indexIn(text);
591
 
  if(match == 0) {
592
 
    match = extendedBoundaryQuoteRegExp.matchedLength();
593
 
  }
594
 
  else {
595
 
    match = extendedBoundaryRegExp.indexIn(text);
596
 
  }
597
 
  boundaryEnd.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList,
598
 
                                                              translatedColumn + qMax(0, match)));
599
 
  return KTextEditor::Range(boundaryStart, boundaryEnd);
600
 
}
601
 
 
602
 
void KateOnTheFlyChecker::misspelling(const QString &word, int start)
603
 
{
604
 
  if(m_currentlyCheckedItem == invalidSpellCheckQueueItem) {
605
 
    ON_THE_FLY_DEBUG << "exited as no spell check is taking place";
606
 
    return;
607
 
  }
608
 
  int translatedStart = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList,
609
 
                                                              start);
610
 
//   ON_THE_FLY_DEBUG << "misspelled " << word
611
 
//                                     << " at line "
612
 
//                                     << *m_currentlyCheckedItem.first
613
 
//                                     << " column " << start;
614
 
 
615
 
  KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first;
616
 
  int line = spellCheckRange->start().line();
617
 
  int rangeStart = spellCheckRange->start().column();
618
 
  int translatedEnd = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList,
619
 
                                                            start + word.length());
620
 
 
621
 
  KTextEditor::MovingRange *movingRange =
622
 
                            m_document->newMovingRange(KTextEditor::Range(line,
623
 
                                                                         rangeStart + translatedStart,
624
 
                                                                         line,
625
 
                                                                         rangeStart + translatedEnd));
626
 
  movingRange->setFeedback(this);
627
 
  KTextEditor::Attribute *attribute = new KTextEditor::Attribute();
628
 
  attribute->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
629
 
  attribute->setUnderlineColor(KateRendererConfig::global()->spellingMistakeLineColor());
630
 
 
631
 
  // don't print this range
632
 
  movingRange->setAttributeOnlyForViews (true);
633
 
 
634
 
  movingRange->setAttribute(KTextEditor::Attribute::Ptr(attribute));
635
 
  m_misspelledList.push_back(MisspelledItem(movingRange, m_currentlyCheckedItem.second));
636
 
 
637
 
  if(m_backgroundChecker) {
638
 
    m_backgroundChecker->continueChecking();
639
 
  }
640
 
}
641
 
 
642
 
void KateOnTheFlyChecker::spellCheckDone()
643
 
{
644
 
  ON_THE_FLY_DEBUG << "on-the-fly spell check done, queue length " << m_spellCheckQueue.size();
645
 
  if(m_currentlyCheckedItem == invalidSpellCheckQueueItem) {
646
 
    return;
647
 
  }
648
 
  KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first;
649
 
  stopCurrentSpellCheck();
650
 
  deleteMovingRangeQuickly(movingRange);
651
 
 
652
 
  if(!m_spellCheckQueue.empty()) {
653
 
    QTimer::singleShot(0, this, SLOT(performSpellCheck()));
654
 
  }
655
 
}
656
 
 
657
 
QList<KTextEditor::MovingRange*> KateOnTheFlyChecker::installedMovingRanges(const KTextEditor::Range& range)
658
 
{
659
 
  ON_THE_FLY_DEBUG << range;
660
 
  MovingRangeList toReturn;
661
 
 
662
 
  for(QList<SpellCheckItem>::iterator i = m_misspelledList.begin();
663
 
                                      i != m_misspelledList.end(); ++i) {
664
 
    KTextEditor::MovingRange *movingRange = (*i).first;
665
 
    if(movingRange->overlaps(range)) {
666
 
      toReturn.push_back(movingRange);
667
 
    }
668
 
  }
669
 
  return toReturn;
670
 
}
671
 
 
672
 
void KateOnTheFlyChecker::updateConfig()
673
 
{
674
 
  ON_THE_FLY_DEBUG;
675
 
  m_speller.restore(KGlobal::config().data());
676
 
 
677
 
#if KDE_IS_VERSION(4,5,2)
678
 
// guard necessary to ensure compilation of KatePart's git repository against <= 4.5.1
679
 
  if(m_backgroundChecker) {
680
 
    m_backgroundChecker->restore(KGlobal::config().data());
681
 
  }
682
 
#endif
683
 
}
684
 
 
685
 
void KateOnTheFlyChecker::refreshSpellCheck(const KTextEditor::Range &range)
686
 
{
687
 
  if(range.isValid()) {
688
 
    textInserted(m_document, range);
689
 
  }
690
 
  else {
691
 
    freeDocument();
692
 
    textInserted(m_document, m_document->documentRange());
693
 
  }
694
 
}
695
 
 
696
 
void KateOnTheFlyChecker::addView(KTextEditor::Document *document, KTextEditor::View *view)
697
 
{
698
 
  Q_ASSERT(document == m_document);
699
 
  Q_UNUSED(document);
700
 
  ON_THE_FLY_DEBUG;
701
 
  connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*)));
702
 
  connect(view, SIGNAL(displayRangeChanged(KateView*)), this, SLOT(restartViewRefreshTimer(KateView*)));
703
 
  updateInstalledMovingRanges(static_cast<KateView*>(view));
704
 
}
705
 
 
706
 
void KateOnTheFlyChecker::viewDestroyed(QObject* obj)
707
 
{
708
 
  ON_THE_FLY_DEBUG;
709
 
  KTextEditor::View *view = static_cast<KTextEditor::View*>(obj);
710
 
  m_displayRangeMap.remove(view);
711
 
}
712
 
 
713
 
void KateOnTheFlyChecker::removeView(KTextEditor::View *view)
714
 
{
715
 
  ON_THE_FLY_DEBUG;
716
 
  m_displayRangeMap.remove(view);
717
 
}
718
 
 
719
 
void KateOnTheFlyChecker::updateInstalledMovingRanges(KateView *view)
720
 
{
721
 
  Q_ASSERT(m_document == view->document());
722
 
  ON_THE_FLY_DEBUG;
723
 
  KTextEditor::Range oldDisplayRange = m_displayRangeMap[view];
724
 
 
725
 
  KTextEditor::Range newDisplayRange = view->visibleRange();
726
 
  ON_THE_FLY_DEBUG << "new range: " << newDisplayRange;
727
 
  ON_THE_FLY_DEBUG << "old range: " << oldDisplayRange;
728
 
  QList<KTextEditor::MovingRange*> toDelete;
729
 
  foreach(const MisspelledItem &item, m_misspelledList) {
730
 
    KTextEditor::MovingRange *movingRange = item.first;
731
 
    if(!movingRange->overlaps(newDisplayRange)) {
732
 
      bool stillVisible = false;
733
 
      foreach(KTextEditor::View *it2, m_document->views()) {
734
 
        KateView *view2 = static_cast<KateView*>(it2);
735
 
        if(view != view2 && movingRange->overlaps(view2->visibleRange())) {
736
 
          stillVisible = true;
737
 
          break;
738
 
        }
739
 
      }
740
 
      if(!stillVisible) {
741
 
        toDelete.push_back(movingRange);
742
 
      }
743
 
    }
744
 
  }
745
 
  deleteMovingRanges (toDelete);
746
 
  m_displayRangeMap[view] = newDisplayRange;
747
 
  if(oldDisplayRange.isValid()) {
748
 
    bool emptyAtStart = m_spellCheckQueue.empty();
749
 
    for(int line = newDisplayRange.end().line(); line >= newDisplayRange.start().line(); --line) {
750
 
      if(!oldDisplayRange.containsLine(line)) {
751
 
        bool visible = false;
752
 
        foreach(KTextEditor::View *it2, m_document->views()) {
753
 
          KateView *view2 = static_cast<KateView*>(it2);
754
 
          if(view != view2 && view2->visibleRange().containsLine(line)) {
755
 
            visible = true;
756
 
            break;
757
 
          }
758
 
        }
759
 
        if(!visible) {
760
 
          queueLineSpellCheck(m_document, line);
761
 
        }
762
 
      }
763
 
    }
764
 
    if(emptyAtStart && !m_spellCheckQueue.isEmpty()) {
765
 
      QTimer::singleShot(0, this, SLOT(performSpellCheck()));
766
 
    }
767
 
  }
768
 
}
769
 
 
770
 
void KateOnTheFlyChecker::queueSpellCheckVisibleRange(const KTextEditor::Range& range)
771
 
{
772
 
  const QList<KTextEditor::View*>& viewList = m_document->views();
773
 
  for(QList<KTextEditor::View*>::const_iterator i = viewList.begin(); i != viewList.end(); ++i) {
774
 
    queueSpellCheckVisibleRange(static_cast<KateView*>(*i), range);
775
 
  }
776
 
}
777
 
 
778
 
void KateOnTheFlyChecker::queueSpellCheckVisibleRange(KateView *view, const KTextEditor::Range& range)
779
 
{
780
 
    Q_ASSERT(m_document == view->doc());
781
 
    KTextEditor::Range visibleRange = view->visibleRange();
782
 
    KTextEditor::Range intersection = visibleRange.intersect(range);
783
 
    if(intersection.isEmpty()) {
784
 
      return;
785
 
    }
786
 
 
787
 
    // clear all the highlights that are currently present in the range that
788
 
    // is supposed to be checked, necessary due to highlighting
789
 
    const MovingRangeList highlightsList = installedMovingRanges(intersection);
790
 
    deleteMovingRanges(highlightsList);
791
 
 
792
 
    QList<QPair<KTextEditor::Range, QString> > spellCheckRanges
793
 
                         = KateGlobal::self()->spellCheckManager()->spellCheckRanges(m_document,
794
 
                                                                                     intersection,
795
 
                                                                                     true);
796
 
    //we queue them up in reverse
797
 
    QListIterator<QPair<KTextEditor::Range, QString> > i(spellCheckRanges);
798
 
    i.toBack();
799
 
    while(i.hasPrevious()) {
800
 
      QPair<KTextEditor::Range, QString> p = i.previous();
801
 
      queueLineSpellCheck(p.first, p.second);
802
 
    }
803
 
}
804
 
 
805
 
void KateOnTheFlyChecker::queueLineSpellCheck(KateDocument *kateDocument, int line)
806
 
{
807
 
    const KTextEditor::Range range = KTextEditor::Range(line, 0, line, kateDocument->lineLength(line));
808
 
    // clear all the highlights that are currently present in the range that
809
 
    // is supposed to be checked, necessary due to highlighting
810
 
 
811
 
    const MovingRangeList highlightsList = installedMovingRanges(range);
812
 
    deleteMovingRanges(highlightsList);
813
 
 
814
 
    QList<QPair<KTextEditor::Range, QString> > spellCheckRanges
815
 
                             = KateGlobal::self()->spellCheckManager()->spellCheckRanges(kateDocument,
816
 
                                                                                         range,
817
 
                                                                                         true);
818
 
    //we queue them up in reverse
819
 
    QListIterator<QPair<KTextEditor::Range, QString> > i(spellCheckRanges);
820
 
    i.toBack();
821
 
    while(i.hasPrevious()) {
822
 
      QPair<KTextEditor::Range, QString> p = i.previous();
823
 
      queueLineSpellCheck(p.first, p.second);
824
 
    }
825
 
}
826
 
 
827
 
void KateOnTheFlyChecker::queueLineSpellCheck(const KTextEditor::Range& range, const QString& dictionary)
828
 
{
829
 
  ON_THE_FLY_DEBUG << m_document << range;
830
 
 
831
 
  Q_ASSERT(range.onSingleLine());
832
 
 
833
 
  if(range.isEmpty()) {
834
 
    return;
835
 
  }
836
 
 
837
 
  addToSpellCheckQueue(range, dictionary);
838
 
}
839
 
 
840
 
void KateOnTheFlyChecker::addToSpellCheckQueue(const KTextEditor::Range& range, const QString& dictionary)
841
 
{
842
 
  addToSpellCheckQueue(m_document->newMovingRange(range), dictionary);
843
 
}
844
 
 
845
 
void KateOnTheFlyChecker::addToSpellCheckQueue(KTextEditor::MovingRange *range, const QString& dictionary)
846
 
{
847
 
  ON_THE_FLY_DEBUG << m_document << *range << dictionary;
848
 
 
849
 
  range->setFeedback(this);
850
 
 
851
 
  // if the queue contains a subrange of 'range', we remove that one
852
 
  for(QList<SpellCheckItem>::iterator i = m_spellCheckQueue.begin();
853
 
                                      i != m_spellCheckQueue.end();) {
854
 
      KTextEditor::MovingRange *spellCheckRange = (*i).first;
855
 
      if(range->contains(*spellCheckRange)) {
856
 
        deleteMovingRangeQuickly(spellCheckRange);
857
 
        i = m_spellCheckQueue.erase(i);
858
 
      }
859
 
      else {
860
 
        ++i;
861
 
      }
862
 
  }
863
 
  // leave 'push_front' here as it is a LIFO queue, i.e. a stack
864
 
  m_spellCheckQueue.push_front(SpellCheckItem(range, dictionary));
865
 
  ON_THE_FLY_DEBUG << "added"
866
 
                   << *range << dictionary
867
 
                   << "to the queue, which has a length of" << m_spellCheckQueue.size();
868
 
}
869
 
 
870
 
void KateOnTheFlyChecker::viewRefreshTimeout()
871
 
{
872
 
  if(m_refreshView) {
873
 
    updateInstalledMovingRanges(m_refreshView);
874
 
  }
875
 
  m_refreshView = NULL;
876
 
}
877
 
 
878
 
void KateOnTheFlyChecker::restartViewRefreshTimer(KateView *view)
879
 
{
880
 
  if(m_refreshView && view != m_refreshView) { // a new view should be refreshed
881
 
    updateInstalledMovingRanges(m_refreshView); // so refresh the old one first
882
 
  }
883
 
  m_refreshView = view;
884
 
  m_viewRefreshTimer->start(100);
885
 
}
886
 
 
887
 
void KateOnTheFlyChecker::deleteMovingRangeQuickly(KTextEditor::MovingRange *range)
888
 
{
889
 
  range->setFeedback(NULL);
890
 
  foreach(KTextEditor::View *view, m_document->views()) {
891
 
    static_cast<KateView*>(view)->spellingMenu()->rangeDeleted(range);
892
 
  }
893
 
  delete(range);
894
 
}
895
 
 
896
 
void KateOnTheFlyChecker::handleModifiedRanges()
897
 
{
898
 
  foreach(const ModificationItem &item, m_modificationList) {
899
 
    KTextEditor::MovingRange *movingRange = item.second;
900
 
    KTextEditor::Range range = *movingRange;
901
 
    deleteMovingRangeQuickly(movingRange);
902
 
    if(item.first == TEXT_INSERTED) {
903
 
      handleInsertedText(range);
904
 
    }
905
 
    else {
906
 
      handleRemovedText(range);
907
 
    }
908
 
  }
909
 
  m_modificationList.clear();
910
 
}
911
 
 
912
 
bool KateOnTheFlyChecker::removeRangeFromModificationList(KTextEditor::MovingRange *range)
913
 
{
914
 
  bool found = false;
915
 
  for(ModificationList::iterator i = m_modificationList.begin(); i != m_modificationList.end();) {
916
 
    ModificationItem item = *i;
917
 
    KTextEditor::MovingRange *movingRange = item.second;
918
 
    if(movingRange == range) {
919
 
      found = true;
920
 
      i = m_modificationList.erase(i);
921
 
    }
922
 
    else {
923
 
      ++i;
924
 
    }
925
 
  }
926
 
  return found;
927
 
}
928
 
 
929
 
void KateOnTheFlyChecker::clearModificationList()
930
 
{
931
 
  foreach(const ModificationItem &item, m_modificationList) {
932
 
    KTextEditor::MovingRange *movingRange = item.second;
933
 
    deleteMovingRangeQuickly(movingRange);
934
 
  }
935
 
  m_modificationList.clear();
936
 
}
937
 
 
938
 
#include "ontheflycheck.moc"
939
 
 
940
 
// kate: space-indent on; indent-width 2; replace-tabs on;