25
25
#include <QMutexLocker>
27
27
#include <kdebug.h>
29
28
#include <ktexteditor/document.h>
30
#include <ktexteditor/smartinterface.h>
29
#include <ktexteditor/movinginterface.h>
31
#include <interfaces/foregroundlock.h>
32
#include <editor/modificationrevisionset.h>
33
#include <duchain/indexedstring.h>
34
#include <interfaces/icore.h>
35
#include <interfaces/ilanguagecontroller.h>
36
#include "backgroundparser.h"
37
#include <QApplication>
39
// Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such.
40
// #define ALWAYS_UPDATE
32
42
using namespace KTextEditor;
45
* @todo Track the exact changes to the document, and then:
47
* - Comment added/changed
48
* - Newlines added/changed (ready)
49
* Complete the document for validation:
50
* - Incomplete for-loops
52
* Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away
53
* Incremental parsing:
54
* - All changes within a local function (or function parameter context): Update only the context (and all its importers)
56
* @todo: Prevent recursive updates after insignificant changes
57
* (whitespace changes, or changes that don't affect publically visible stuff, eg. local incremental changes)
58
* -> Maybe alter the file-modification caches directly
62
QRegExp whiteSpaceRegExp("\\s");
37
class DocumentChangeTrackerPrivate
40
DocumentChangeTrackerPrivate()
41
: contentsRetrieved(false)
42
, contentsRetrievedMutex(new QMutex)
46
~DocumentChangeTrackerPrivate()
48
delete contentsRetrievedMutex;
51
bool contentsRetrieved;
53
QMutex* contentsRetrievedMutex;
54
QList<SmartRange*> changedRanges;
57
DocumentChangeTracker::DocumentChangeTracker()
58
: d(new DocumentChangeTrackerPrivate)
68
DocumentChangeTracker::DocumentChangeTracker( KTextEditor::Document* document )
69
: m_needUpdate(false), m_changedRange(0), m_document(document), m_moving(0)
71
m_url = IndexedString(document->url());
72
Q_ASSERT(document->url().isValid());
74
connect(document, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range)));
75
connect(document, SIGNAL(textRemoved(KTextEditor::Document*,KTextEditor::Range, QString)), SLOT(textRemoved(KTextEditor::Document*,KTextEditor::Range, QString)));
76
connect(document, SIGNAL(textChanged(KTextEditor::Document*,KTextEditor::Range,QString, KTextEditor::Range)), SLOT(textChanged(KTextEditor::Document*,KTextEditor::Range,QString, KTextEditor::Range)));
77
connect(document, SIGNAL(destroyed(QObject*)), SLOT(documentDestroyed(QObject*)));
79
m_moving = dynamic_cast<KTextEditor::MovingInterface*>(document);
81
m_changedRange = m_moving->newMovingRange(KTextEditor::Range(), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight);
83
connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent (KTextEditor::Document*)), this, SLOT(aboutToInvalidateMovingInterfaceContent (KTextEditor::Document*)));
85
ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision());
90
QList< QPair< SimpleRange, QString > > DocumentChangeTracker::completions() const
92
VERIFY_FOREGROUND_LOCKED
94
QList< QPair< SimpleRange , QString > > ret;
98
Range DocumentChangeTracker::changedRange() const
100
VERIFY_FOREGROUND_LOCKED
102
return m_changedRange->toRange();
105
void DocumentChangeTracker::reset()
107
VERIFY_FOREGROUND_LOCKED
109
// We don't reset the insertion here, as it may continue
110
m_needUpdate = false;
111
m_changedRange->setRange(KTextEditor::Range::invalid());
113
m_revisionAtLastReset = acquireRevision(m_moving->revision());
114
Q_ASSERT(m_revisionAtLastReset);
115
m_textAtLastReset = m_document->text();
118
RevisionReference DocumentChangeTracker::currentRevision()
120
VERIFY_FOREGROUND_LOCKED
122
return acquireRevision(m_moving->revision());
125
RevisionReference DocumentChangeTracker::revisionAtLastReset() const
127
VERIFY_FOREGROUND_LOCKED
129
return m_revisionAtLastReset;
132
QString DocumentChangeTracker::textAtLastReset() const
134
VERIFY_FOREGROUND_LOCKED
136
return m_textAtLastReset;
139
bool DocumentChangeTracker::needUpdate() const
141
VERIFY_FOREGROUND_LOCKED
146
bool DocumentChangeTracker::checkMergeTokens(const KTextEditor::Range& range, QString oldText, QString newText)
148
///@todo Improve this so that it notices when we wrapped in/out of a line-comment
149
///@todo Improve this so that it really checks whether some merge-able tokens have been moved together
150
if(m_document->documentRange().contains(range))
152
if(range.start().column() == 0 || m_document->text(KTextEditor::Range(range.start().line(), range.start().column()-1, range.start().line(), range.start().column()))[0].isSpace())
154
if(range.end().column() >= m_document->lineLength(range.end().line()) || m_document->text(KTextEditor::Range(range.end().line(), range.end().column(), range.end().line(), range.end().column()+1))[0].isSpace())
160
void DocumentChangeTracker::textChanged( Document* document, Range /*oldRange*/, QString oldText, Range newRange )
162
m_currentCleanedInsertion.clear();
164
QString newText = document->text(newRange);
166
QString oldTextWithoutWhitespace = oldText;
167
oldTextWithoutWhitespace.remove(whiteSpaceRegExp);
169
QString newTextWithoutWhitespace = newText;
170
newTextWithoutWhitespace.remove(whiteSpaceRegExp);
172
if(oldTextWithoutWhitespace.isEmpty() && newTextWithoutWhitespace.isEmpty() && checkMergeTokens(newRange, oldText, newText))
174
// Only whitespace was changed, no update is required
183
m_currentCleanedInsertion.clear();
184
m_lastInsertionPosition = KTextEditor::Cursor::invalid();
186
updateChangedRange(newRange);
189
void DocumentChangeTracker::updateChangedRange( Range changed )
191
if(m_changedRange->toRange() == KTextEditor::Range::invalid())
192
m_changedRange->setRange(changed);
194
m_changedRange->setRange(changed.encompass(m_changedRange->toRange()));
196
// Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload
198
// When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead.
200
ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision());
203
ICore::self()->languageController()->backgroundParser()->addDocument(m_url.toUrl(), TopDUContext::AllDeclarationsContextsAndUses);
206
void DocumentChangeTracker::textInserted( Document* document, Range range )
208
QString text = document->text(range);
209
QString textWithoutWhitespace = text;
210
textWithoutWhitespace.remove(whiteSpaceRegExp);
212
if(textWithoutWhitespace.isEmpty() && checkMergeTokens(range, "", text))
214
// Only whitespace was changed, no update is required
216
m_needUpdate = true; // If we've inserted something else than whitespace, an update is required
223
if(m_lastInsertionPosition == KTextEditor::Cursor::invalid() || m_lastInsertionPosition == range.start())
225
m_currentCleanedInsertion.append(text);
226
m_lastInsertionPosition = range.end();
229
updateChangedRange(range);
232
void DocumentChangeTracker::textRemoved( Document* document, Range oldRange, QString oldText )
234
QString text = oldText;
236
QString textWithoutWhitespace = text;
237
textWithoutWhitespace.remove(whiteSpaceRegExp);
239
if(textWithoutWhitespace.isEmpty() && checkMergeTokens(Range(oldRange.start(), oldRange.start()), oldText, ""))
241
// Only whitespace was changed, no update is required
243
m_needUpdate = true; // If we've inserted something else than whitespace, an update is required
250
m_currentCleanedInsertion.clear();
251
m_lastInsertionPosition = KTextEditor::Cursor::invalid();
253
updateChangedRange(oldRange);
256
void DocumentChangeTracker::documentDestroyed( QObject* )
62
263
DocumentChangeTracker::~DocumentChangeTracker()
64
foreach (SmartRange* range, d->changedRanges)
65
range->removeWatcher(this);
70
QList<SmartRange*> DocumentChangeTracker::changedRanges() const
72
return d->changedRanges;
75
bool DocumentChangeTracker::addChangedRange(SmartRange* changed)
77
QMutexLocker l(d->contentsRetrievedMutex);
78
if (d->contentsRetrieved)
265
Q_ASSERT(m_document);
266
ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url()));
269
Document* DocumentChangeTracker::document() const
274
MovingInterface* DocumentChangeTracker::documentMovingInterface() const
279
void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent ( Document* )
281
// Release all revisions! They must not be used any more.
282
kDebug() << "clearing all revisions";
283
m_revisionLocks.clear();
284
m_revisionAtLastReset = RevisionReference();
285
ModificationRevision::setEditorRevisionForFile(m_url, 0);
288
KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, qint64 fromRevision, qint64 toRevision) const
290
VERIFY_FOREGROUND_LOCKED
292
if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision)))
294
m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, fromRevision, toRevision);
295
m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, fromRevision, toRevision);
301
KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, qint64 fromRevision, qint64 toRevision, KTextEditor::MovingCursor::InsertBehavior behavior) const
303
VERIFY_FOREGROUND_LOCKED
305
if((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision)))
307
m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision);
313
RangeInRevision DocumentChangeTracker::transformToRevision(SimpleRange range, qint64 toRevision) const
315
return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision);
318
CursorInRevision DocumentChangeTracker::transformToRevision(SimpleCursor cursor, qint64 toRevision, MovingCursor::InsertBehavior behavior) const
320
return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior);
323
SimpleRange DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const
325
return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange();
328
SimpleCursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, MovingCursor::InsertBehavior behavior) const
330
return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor();
333
RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision) : m_tracker(tracker), m_revision(revision)
335
VERIFY_FOREGROUND_LOCKED
337
moveToThread(QApplication::instance()->thread());
340
m_tracker->lockRevision(revision);
343
RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() {
345
m_tracker->unlockRevision(m_revision);
348
RevisionLockerAndClearer::~RevisionLockerAndClearer()
350
m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground
353
RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision)
355
VERIFY_FOREGROUND_LOCKED
357
if(!holdingRevision(revision) && revision != m_moving->revision())
358
return RevisionReference();
360
RevisionReference ret(new RevisionLockerAndClearer);
361
ret->m_p = new RevisionLockerAndClearerPrivate(this, revision);
365
bool DocumentChangeTracker::holdingRevision(qint64 revision) const
367
VERIFY_FOREGROUND_LOCKED
369
return m_revisionLocks.contains(revision);
372
void DocumentChangeTracker::lockRevision(qint64 revision)
374
VERIFY_FOREGROUND_LOCKED
376
QMap< qint64, int >::iterator it = m_revisionLocks.find(revision);
377
if(it != m_revisionLocks.end())
381
m_revisionLocks.insert(revision, 1);
382
m_moving->lockRevision(revision);
386
void DocumentChangeTracker::unlockRevision(qint64 revision)
388
VERIFY_FOREGROUND_LOCKED
390
QMap< qint64, int >::iterator it = m_revisionLocks.find(revision);
391
if(it == m_revisionLocks.end())
393
kDebug() << "cannot unlock revision" << revision << ", probably the revisions have been cleared";
400
m_moving->unlockRevision(revision);
401
m_revisionLocks.erase(it);
405
qint64 RevisionLockerAndClearer::revision() const {
406
return m_p->revision();
408
RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& to)
410
VERIFY_FOREGROUND_LOCKED
412
if(!m_p->m_tracker || !valid() || (to && !to->valid()))
415
qint64 fromRevision = revision();
416
qint64 toRevision = -1;
419
toRevision = to->revision();
421
return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision);
424
CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& to, MovingCursor::InsertBehavior behavior)
426
VERIFY_FOREGROUND_LOCKED
428
if(!m_p->m_tracker || !valid() || (to && !to->valid()))
431
qint64 fromRevision = revision();
432
qint64 toRevision = -1;
435
toRevision = to->revision();
437
return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior);
440
RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, const KDevelop::RevisionLockerAndClearer::Ptr& from)
442
VERIFY_FOREGROUND_LOCKED
444
if(!m_p->m_tracker || !valid())
447
qint64 toRevision = revision();
448
qint64 fromRevision = -1;
451
fromRevision = from->revision();
453
return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision);
456
CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, const KDevelop::RevisionLockerAndClearer::Ptr& from, MovingCursor::InsertBehavior behavior)
458
VERIFY_FOREGROUND_LOCKED
463
qint64 toRevision = revision();
464
qint64 fromRevision = -1;
467
fromRevision = from->revision();
469
return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior);
473
SimpleRange RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range)
475
return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange();
478
SimpleCursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, MovingCursor::InsertBehavior behavior)
480
return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor();
483
RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KDevelop::SimpleRange& range)
485
return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference());
488
CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KDevelop::SimpleCursor& cursor, MovingCursor::InsertBehavior behavior)
490
return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior);
493
bool RevisionLockerAndClearer::valid() const
495
VERIFY_FOREGROUND_LOCKED
81
addChangedRangeInternal(changed);
86
void DocumentChangeTracker::addChangedRangeInternal(SmartRange* changed)
88
QMutableListIterator<SmartRange*> it = d->changedRanges;
89
bool foundOverlap = false;
90
while (it.hasNext()) {
91
if (it.next() == changed) {
96
if (it.value()->overlaps(*changed)) {
97
int rangeDepth = it.value()->depth();
98
int changedDepth = changed->depth();
100
if (changedDepth < rangeDepth) {
101
// We have a new parent range
102
it.value()->removeWatcher(this);
105
// Replace current range
106
it.value() = changed;
107
changed->addWatcher(this);
111
// Remove now child range
116
// We're contained by the range we found
117
// Nothing more to do
125
d->changedRanges.append(changed);
126
changed->addWatcher(this);
130
void DocumentChangeTracker::finaliseChangedRanges()
132
QMutexLocker l(d->contentsRetrievedMutex);
133
d->contentsRetrieved = true;
136
QMutex* DocumentChangeTracker::changeMutex() const
138
return d->contentsRetrievedMutex;
141
void DocumentChangeTracker::setChangedRanges(const QList<SmartRange*>& changedRanges)
143
QMutexLocker l(d->contentsRetrievedMutex);
144
Q_ASSERT(!d->contentsRetrieved);
145
Q_ASSERT(d->changedRanges.isEmpty());
147
d->changedRanges = changedRanges;
149
foreach (SmartRange* range, d->changedRanges)
150
range->addWatcher(this);
153
bool DocumentChangeTracker::rangeChangesFinalised() const
155
return d->contentsRetrieved;
158
void DocumentChangeTracker::rangeDeleted(SmartRange *range)
160
QMutexLocker l(d->contentsRetrievedMutex);
162
int index = d->changedRanges.indexOf(range);
163
Q_ASSERT(index != -1);
164
d->changedRanges.removeAt(index);
166
if (range->parentRange())
167
addChangedRangeInternal(range->parentRange());
169
kWarning() << "Top range deleted?";
501
return true; // The 'current' revision is always valid
503
return m_p->m_tracker->holdingRevision(revision());
506
RevisionReference DocumentChangeTracker::diskRevision() const
508
///@todo Track which revision was last saved to disk
509
return RevisionReference();