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"