File indexing completed on 2024-05-12 04:37:42
0001 /* 0002 SPDX-FileCopyrightText: 2010 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "documentchangetracker.h" 0008 0009 #include <util/foregroundlock.h> 0010 #include <editor/modificationrevision.h> 0011 #include <serialization/indexedstring.h> 0012 #include <interfaces/icore.h> 0013 #include <interfaces/ilanguagecontroller.h> 0014 #include "backgroundparser.h" 0015 #include <debug.h> 0016 0017 #include <KTextEditor/Document> 0018 #include <KTextEditor/MovingInterface> 0019 0020 #include <QCoreApplication> 0021 0022 // Can be used to disable the 'clever' updating logic that ignores whitespace-only changes and such. 0023 // #define ALWAYS_UPDATE 0024 0025 using namespace KTextEditor; 0026 0027 /** 0028 * @todo Track the exact changes to the document, and then: 0029 * Do not reparse if: 0030 * - Comment added/changed 0031 * - Newlines added/changed (ready) 0032 * Complete the document for validation: 0033 * - Incomplete for-loops 0034 * - ... 0035 * Only reparse after a statement was completed (either with statement-completion or manually), or after the cursor was switched away 0036 * Incremental parsing: 0037 * - All changes within a local function (or function parameter context): Update only the context (and all its importers) 0038 * 0039 * @todo: Prevent recursive updates after insignificant changes 0040 * (whitespace changes, or changes that don't affect publicly visible stuff, eg. local incremental changes) 0041 * -> Maybe alter the file-modification caches directly 0042 * */ 0043 0044 namespace KDevelop { 0045 /** 0046 * Internal helper class for RevisionLockerAndClearer 0047 * */ 0048 class RevisionLockerAndClearerPrivate 0049 : public QObject 0050 { 0051 Q_OBJECT 0052 0053 public: 0054 RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, qint64 revision); 0055 ~RevisionLockerAndClearerPrivate() override; 0056 inline qint64 revision() const 0057 { 0058 return m_revision; 0059 } 0060 0061 private: 0062 friend class RevisionLockerAndClearer; 0063 QPointer<DocumentChangeTracker> m_tracker; 0064 qint64 m_revision; 0065 }; 0066 0067 DocumentChangeTracker::DocumentChangeTracker(KTextEditor::Document* document) 0068 : m_needUpdate(false) 0069 , m_document(document) 0070 , m_moving(nullptr) 0071 , m_url(IndexedString(document->url())) 0072 { 0073 Q_ASSERT(document); 0074 Q_ASSERT(document->url().isValid()); 0075 0076 connect(document, &Document::textInserted, this, &DocumentChangeTracker::textInserted); 0077 connect(document, &Document::lineWrapped, this, &DocumentChangeTracker::lineWrapped); 0078 connect(document, &Document::lineUnwrapped, this, &DocumentChangeTracker::lineUnwrapped); 0079 connect(document, &Document::textRemoved, this, &DocumentChangeTracker::textRemoved); 0080 connect(document, &Document::destroyed, this, &DocumentChangeTracker::documentDestroyed); 0081 connect(document, &Document::documentSavedOrUploaded, this, &DocumentChangeTracker::documentSavedOrUploaded); 0082 0083 m_moving = qobject_cast<KTextEditor::MovingInterface*>(document); 0084 Q_ASSERT(m_moving); 0085 0086 // can't use new connect syntax here, MovingInterface is not a QObject 0087 connect(m_document, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, 0088 SLOT(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); 0089 0090 ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); 0091 0092 reset(); 0093 } 0094 0095 QList<QPair<KTextEditor::Range, QString>> DocumentChangeTracker::completions() const 0096 { 0097 VERIFY_FOREGROUND_LOCKED 0098 0099 QList<QPair<KTextEditor::Range, QString>> ret; 0100 return ret; 0101 } 0102 0103 void DocumentChangeTracker::reset() 0104 { 0105 VERIFY_FOREGROUND_LOCKED 0106 0107 // We don't reset the insertion here, as it may continue 0108 m_needUpdate = false; 0109 0110 m_revisionAtLastReset = acquireRevision(m_moving->revision()); 0111 Q_ASSERT(m_revisionAtLastReset); 0112 } 0113 0114 RevisionReference DocumentChangeTracker::currentRevision() 0115 { 0116 VERIFY_FOREGROUND_LOCKED 0117 0118 return acquireRevision(m_moving->revision()); 0119 } 0120 0121 RevisionReference DocumentChangeTracker::revisionAtLastReset() const 0122 { 0123 VERIFY_FOREGROUND_LOCKED 0124 0125 return m_revisionAtLastReset; 0126 } 0127 0128 bool DocumentChangeTracker::needUpdate() const 0129 { 0130 VERIFY_FOREGROUND_LOCKED 0131 0132 return m_needUpdate; 0133 } 0134 0135 void DocumentChangeTracker::updateChangedRange(int delay) 0136 { 0137 // Q_ASSERT(m_moving->revision() != m_revisionAtLastReset->revision()); // May happen after reload 0138 0139 // When reloading, textRemoved is called with an invalid m_document->url(). For that reason, we use m_url instead. 0140 0141 ModificationRevision::setEditorRevisionForFile(m_url, m_moving->revision()); 0142 0143 if (needUpdate()) { 0144 ICore::self()->languageController()->backgroundParser()->addDocument(m_url, 0145 TopDUContext::AllDeclarationsContextsAndUses, 0146 0, nullptr, 0147 ParseJob::IgnoresSequentialProcessing, 0148 delay); 0149 } 0150 } 0151 0152 static Cursor cursorAdd(Cursor c, const QString& text) 0153 { 0154 c.setLine(c.line() + text.count(QLatin1Char('\n'))); 0155 c.setColumn(c.column() + (text.length() - qMin(0, text.lastIndexOf(QLatin1Char('\n'))))); 0156 return c; 0157 } 0158 0159 int DocumentChangeTracker::recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, 0160 const QString& text, bool removal) 0161 { 0162 const auto languages = ICore::self()->languageController()->languagesForUrl(doc->url()); 0163 int delay = ILanguageSupport::NoUpdateRequired; 0164 for (const auto& lang : languages) { 0165 // take the largest value, because NoUpdateRequired is -2 and we want to make sure 0166 // that if one language requires an update it actually happens 0167 delay = qMax<int>(lang->suggestedReparseDelayForChange(doc, range, text, removal), delay); 0168 } 0169 0170 return delay; 0171 } 0172 0173 void DocumentChangeTracker::lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position) 0174 { 0175 textInserted(document, position, QStringLiteral("\n")); 0176 } 0177 0178 void DocumentChangeTracker::lineUnwrapped(KTextEditor::Document* document, int line) 0179 { 0180 textRemoved(document, {{document->lineLength(line), line}, {0, line + 1}}, QStringLiteral("\n")); 0181 } 0182 0183 void DocumentChangeTracker::textInserted(Document* document, const Cursor& cursor, const QString& text) 0184 { 0185 /// TODO: get this data from KTextEditor directly, make its signal public 0186 KTextEditor::Range range(cursor, cursorAdd(cursor, text)); 0187 0188 if (!m_lastInsertionPosition.isValid() || m_lastInsertionPosition == cursor) { 0189 m_currentCleanedInsertion.append(text); 0190 m_lastInsertionPosition = range.end(); 0191 } 0192 0193 auto delay = recommendedDelay(document, range, text, false); 0194 m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; 0195 updateChangedRange(delay); 0196 } 0197 0198 void DocumentChangeTracker::textRemoved(Document* document, const KTextEditor::Range& oldRange, const QString& oldText) 0199 { 0200 m_currentCleanedInsertion.clear(); 0201 m_lastInsertionPosition = KTextEditor::Cursor::invalid(); 0202 0203 auto delay = recommendedDelay(document, oldRange, oldText, true); 0204 m_needUpdate = delay != ILanguageSupport::NoUpdateRequired; 0205 updateChangedRange(delay); 0206 } 0207 0208 void DocumentChangeTracker::documentSavedOrUploaded(KTextEditor::Document* doc, bool) 0209 { 0210 ModificationRevision::clearModificationCache(IndexedString(doc->url())); 0211 } 0212 0213 void DocumentChangeTracker::documentDestroyed(QObject*) 0214 { 0215 m_document = nullptr; 0216 m_moving = nullptr; 0217 } 0218 0219 DocumentChangeTracker::~DocumentChangeTracker() 0220 { 0221 Q_ASSERT(m_document); 0222 ModificationRevision::clearEditorRevisionForFile(KDevelop::IndexedString(m_document->url())); 0223 } 0224 0225 Document* DocumentChangeTracker::document() const 0226 { 0227 return m_document; 0228 } 0229 0230 MovingInterface* DocumentChangeTracker::documentMovingInterface() const 0231 { 0232 return m_moving; 0233 } 0234 0235 void DocumentChangeTracker::aboutToInvalidateMovingInterfaceContent(Document*) 0236 { 0237 // Release all revisions! They must not be used any more. 0238 qCDebug(LANGUAGE) << "clearing all revisions"; 0239 m_revisionLocks.clear(); 0240 m_revisionAtLastReset = RevisionReference(); 0241 ModificationRevision::setEditorRevisionForFile(m_url, 0); 0242 } 0243 0244 KDevelop::RangeInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::RangeInRevision range, 0245 qint64 fromRevision, qint64 toRevision) const 0246 { 0247 VERIFY_FOREGROUND_LOCKED 0248 0249 if ((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { 0250 m_moving->transformCursor(range.start.line, range.start.column, KTextEditor::MovingCursor::MoveOnInsert, 0251 fromRevision, toRevision); 0252 m_moving->transformCursor(range.end.line, range.end.column, KTextEditor::MovingCursor::StayOnInsert, 0253 fromRevision, toRevision); 0254 } 0255 0256 return range; 0257 } 0258 0259 KDevelop::CursorInRevision DocumentChangeTracker::transformBetweenRevisions(KDevelop::CursorInRevision cursor, 0260 qint64 fromRevision, qint64 toRevision, 0261 KTextEditor::MovingCursor::InsertBehavior behavior) 0262 const 0263 { 0264 VERIFY_FOREGROUND_LOCKED 0265 0266 if ((fromRevision == -1 || holdingRevision(fromRevision)) && (toRevision == -1 || holdingRevision(toRevision))) { 0267 m_moving->transformCursor(cursor.line, cursor.column, behavior, fromRevision, toRevision); 0268 } 0269 0270 return cursor; 0271 } 0272 0273 RangeInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Range range, qint64 toRevision) const 0274 { 0275 return transformBetweenRevisions(RangeInRevision::castFromSimpleRange(range), -1, toRevision); 0276 } 0277 0278 CursorInRevision DocumentChangeTracker::transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision, 0279 MovingCursor::InsertBehavior behavior) const 0280 { 0281 return transformBetweenRevisions(CursorInRevision::castFromSimpleCursor(cursor), -1, toRevision, behavior); 0282 } 0283 0284 KTextEditor::Range DocumentChangeTracker::transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const 0285 { 0286 return transformBetweenRevisions(range, fromRevision, -1).castToSimpleRange(); 0287 } 0288 0289 KTextEditor::Cursor DocumentChangeTracker::transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision, 0290 MovingCursor::InsertBehavior behavior) const 0291 { 0292 return transformBetweenRevisions(cursor, fromRevision, -1, behavior).castToSimpleCursor(); 0293 } 0294 0295 RevisionLockerAndClearerPrivate::RevisionLockerAndClearerPrivate(DocumentChangeTracker* tracker, 0296 qint64 revision) : m_tracker(tracker) 0297 , m_revision(revision) 0298 { 0299 VERIFY_FOREGROUND_LOCKED 0300 0301 moveToThread(QCoreApplication::instance()->thread()); 0302 0303 // Lock the revision 0304 m_tracker->lockRevision(revision); 0305 } 0306 0307 RevisionLockerAndClearerPrivate::~RevisionLockerAndClearerPrivate() 0308 { 0309 if (m_tracker) 0310 m_tracker->unlockRevision(m_revision); 0311 } 0312 0313 RevisionLockerAndClearer::~RevisionLockerAndClearer() 0314 { 0315 m_p->deleteLater(); // Will be deleted in the foreground thread, as the object was re-owned to the foreground 0316 } 0317 0318 RevisionReference DocumentChangeTracker::acquireRevision(qint64 revision) 0319 { 0320 VERIFY_FOREGROUND_LOCKED 0321 0322 if (!holdingRevision(revision) && revision != m_moving->revision()) 0323 return RevisionReference(); 0324 0325 RevisionReference ret(new RevisionLockerAndClearer); 0326 ret->m_p = new RevisionLockerAndClearerPrivate(this, revision); 0327 return ret; 0328 } 0329 0330 bool DocumentChangeTracker::holdingRevision(qint64 revision) const 0331 { 0332 VERIFY_FOREGROUND_LOCKED 0333 0334 return m_revisionLocks.contains(revision); 0335 } 0336 0337 void DocumentChangeTracker::lockRevision(qint64 revision) 0338 { 0339 VERIFY_FOREGROUND_LOCKED 0340 0341 QMap<qint64, int>::iterator it = m_revisionLocks.find(revision); 0342 if (it != m_revisionLocks.end()) 0343 ++(*it); 0344 else 0345 { 0346 m_revisionLocks.insert(revision, 1); 0347 m_moving->lockRevision(revision); 0348 } 0349 } 0350 0351 void DocumentChangeTracker::unlockRevision(qint64 revision) 0352 { 0353 VERIFY_FOREGROUND_LOCKED 0354 0355 QMap<qint64, int>::iterator it = m_revisionLocks.find(revision); 0356 if (it == m_revisionLocks.end()) { 0357 qCDebug(LANGUAGE) << "cannot unlock revision" << revision << ", probably the revisions have been cleared"; 0358 return; 0359 } 0360 --(*it); 0361 0362 if (*it == 0) { 0363 m_moving->unlockRevision(revision); 0364 m_revisionLocks.erase(it); 0365 } 0366 } 0367 0368 qint64 RevisionLockerAndClearer::revision() const 0369 { 0370 return m_p->revision(); 0371 } 0372 0373 RangeInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::RangeInRevision& range, 0374 const KDevelop::RevisionLockerAndClearer::Ptr& to) const 0375 { 0376 VERIFY_FOREGROUND_LOCKED 0377 0378 if (!m_p->m_tracker || !valid() || (to && !to->valid())) 0379 return range; 0380 0381 qint64 fromRevision = revision(); 0382 qint64 toRevision = -1; 0383 0384 if (to) 0385 toRevision = to->revision(); 0386 0387 return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); 0388 } 0389 0390 CursorInRevision RevisionLockerAndClearer::transformToRevision(const KDevelop::CursorInRevision& cursor, 0391 const KDevelop::RevisionLockerAndClearer::Ptr& to, 0392 MovingCursor::InsertBehavior behavior) const 0393 { 0394 VERIFY_FOREGROUND_LOCKED 0395 0396 if (!m_p->m_tracker || !valid() || (to && !to->valid())) 0397 return cursor; 0398 0399 qint64 fromRevision = revision(); 0400 qint64 toRevision = -1; 0401 0402 if (to) 0403 toRevision = to->revision(); 0404 0405 return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); 0406 } 0407 0408 RangeInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::RangeInRevision& range, 0409 const KDevelop::RevisionLockerAndClearer::Ptr& from) 0410 const 0411 { 0412 VERIFY_FOREGROUND_LOCKED 0413 0414 if (!m_p->m_tracker || !valid()) 0415 return range; 0416 0417 qint64 toRevision = revision(); 0418 qint64 fromRevision = -1; 0419 0420 if (from) 0421 fromRevision = from->revision(); 0422 0423 return m_p->m_tracker->transformBetweenRevisions(range, fromRevision, toRevision); 0424 } 0425 0426 CursorInRevision RevisionLockerAndClearer::transformFromRevision(const KDevelop::CursorInRevision& cursor, 0427 const KDevelop::RevisionLockerAndClearer::Ptr& from, 0428 MovingCursor::InsertBehavior behavior) const 0429 { 0430 VERIFY_FOREGROUND_LOCKED 0431 0432 if (!m_p->m_tracker) 0433 return cursor; 0434 0435 qint64 toRevision = revision(); 0436 qint64 fromRevision = -1; 0437 0438 if (from) 0439 fromRevision = from->revision(); 0440 0441 return m_p->m_tracker->transformBetweenRevisions(cursor, fromRevision, toRevision, behavior); 0442 } 0443 0444 KTextEditor::Range RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::RangeInRevision& range) const 0445 { 0446 return transformToRevision(range, KDevelop::RevisionLockerAndClearer::Ptr()).castToSimpleRange(); 0447 } 0448 0449 KTextEditor::Cursor RevisionLockerAndClearer::transformToCurrentRevision(const KDevelop::CursorInRevision& cursor, 0450 MovingCursor::InsertBehavior behavior) const 0451 { 0452 return transformToRevision(cursor, KDevelop::RevisionLockerAndClearer::Ptr(), behavior).castToSimpleCursor(); 0453 } 0454 0455 RangeInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Range& range) const 0456 { 0457 return transformFromRevision(RangeInRevision::castFromSimpleRange(range), RevisionReference()); 0458 } 0459 0460 CursorInRevision RevisionLockerAndClearer::transformFromCurrentRevision(const KTextEditor::Cursor& cursor, 0461 MovingCursor::InsertBehavior behavior) const 0462 { 0463 return transformFromRevision(CursorInRevision::castFromSimpleCursor(cursor), RevisionReference(), behavior); 0464 } 0465 0466 bool RevisionLockerAndClearer::valid() const 0467 { 0468 VERIFY_FOREGROUND_LOCKED 0469 0470 if (!m_p->m_tracker) 0471 return false; 0472 0473 if (revision() == -1) 0474 return true; // The 'current' revision is always valid 0475 0476 return m_p->m_tracker->holdingRevision(revision()); 0477 } 0478 0479 RevisionReference DocumentChangeTracker::diskRevision() const 0480 { 0481 ///@todo Track which revision was last saved to disk 0482 return RevisionReference(); 0483 } 0484 } 0485 0486 #include "documentchangetracker.moc" 0487 #include "moc_documentchangetracker.cpp"