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 #ifndef KDEVPLATFORM_DOCUMENTCHANGETRACKER_H
0008 #define KDEVPLATFORM_DOCUMENTCHANGETRACKER_H
0009 
0010 #include <language/languageexport.h>
0011 #include <QExplicitlySharedDataPointer>
0012 #include <QPointer>
0013 #include <QPair>
0014 #include <language/editor/rangeinrevision.h>
0015 #include <serialization/indexedstring.h>
0016 
0017 #include <KTextEditor/MovingRange>
0018 
0019 namespace KTextEditor {
0020 class Document;
0021 class MovingRange;
0022 class MovingInterface;
0023 }
0024 
0025 namespace KDevelop {
0026 class DocumentChangeTracker;
0027 /**
0028  * These objects belongs to the foreground, and thus can only be accessed from background threads if the foreground lock is held.
0029  * */
0030 
0031 class RevisionLockerAndClearerPrivate;
0032 
0033 /**
0034  * Helper class that locks a revision, and clears it on its destruction within the foreground thread.
0035  * Just delete it using deleteLater().
0036  * */
0037 class KDEVPLATFORMLANGUAGE_EXPORT RevisionLockerAndClearer
0038     : public QSharedData
0039 {
0040 public:
0041     using Ptr = QExplicitlySharedDataPointer<RevisionLockerAndClearer>;
0042 
0043     ~RevisionLockerAndClearer();
0044 
0045     /**
0046      * Returns the revision number
0047      * */
0048     qint64 revision() const;
0049 
0050     /**
0051      * Whether the revision is still being held. It may have been lost due to document-reloads,
0052      * in which case the revision must not be used.
0053      * */
0054     bool valid() const;
0055     /**
0056      * Transform a range from this document revision to the given @p to.
0057      * */
0058     RangeInRevision transformToRevision(const RangeInRevision& range, const Ptr& to) const;
0059 
0060     /**
0061      * Transform a cursor from this document revision to the given @p to.
0062      * If a zero target revision is given, the transformation is done to the current document revision.
0063      * */
0064     CursorInRevision transformToRevision(const CursorInRevision& cursor, const Ptr& to,
0065                                          KTextEditor::MovingCursor::InsertBehavior behavior =
0066                                              KTextEditor::MovingCursor::StayOnInsert) const;
0067 
0068     /**
0069      * Transforms the given range from this revision into the current revision.
0070      */
0071     KTextEditor::Range transformToCurrentRevision(const RangeInRevision& range) const;
0072 
0073     /**
0074      * Transforms the given cursor from this revision into the current revision.
0075      */
0076     KTextEditor::Cursor transformToCurrentRevision(const CursorInRevision& cursor,
0077                                                    KTextEditor::MovingCursor::InsertBehavior behavior =
0078                                                        KTextEditor::MovingCursor::StayOnInsert) const;
0079 
0080     /**
0081      * Transform ranges from the given document revision @p from to the this one.
0082      * If a zero @p from revision is given, the transformation is done from the current document revision.
0083      * */
0084     RangeInRevision transformFromRevision(const RangeInRevision& range, const Ptr& from = Ptr()) const;
0085     /**
0086      * Transform ranges from the given document revision @p from to the this one.
0087      * If a zero @p from revision is given, the transformation is done from the current document revision.
0088      * */
0089     CursorInRevision transformFromRevision(const CursorInRevision& cursor,
0090                                            const Ptr& from = Ptr(),
0091                                            KTextEditor::MovingCursor::InsertBehavior behavior =
0092                                                KTextEditor::MovingCursor::StayOnInsert) const;
0093 
0094     /**
0095      * Transforms the given range from the current revision into this revision.
0096      */
0097     RangeInRevision transformFromCurrentRevision(const KTextEditor::Range& range) const;
0098 
0099     /**
0100      * Transforms the given cursor from the current revision into this revision.
0101      */
0102     CursorInRevision transformFromCurrentRevision(const KTextEditor::Cursor& cursor,
0103                                                   KTextEditor::MovingCursor::InsertBehavior behavior =
0104                                                       KTextEditor::MovingCursor::StayOnInsert) const;
0105 
0106 private:
0107     friend class DocumentChangeTracker;
0108 
0109     RevisionLockerAndClearerPrivate* m_p;
0110 };
0111 
0112 using RevisionReference = RevisionLockerAndClearer::Ptr;
0113 
0114 class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeTracker
0115     : public QObject
0116 {
0117     Q_OBJECT
0118 
0119 public:
0120     explicit DocumentChangeTracker(KTextEditor::Document* document);
0121     ~DocumentChangeTracker() override;
0122 
0123     /**
0124      * Completions of the users current edits that are supposed to complete
0125      * not-yet-finished statements, like for example for-loops for parsing.
0126      * */
0127     virtual QList<QPair<KTextEditor::Range, QString>> completions() const;
0128 
0129     /**
0130      * Resets the tracking to the current revision.
0131      * */
0132     virtual void reset();
0133 
0134     /**
0135      * Returns the document revision at which reset() was called last.
0136      *
0137      * The revision is being locked by the tracker in MovingInterface,
0138      * it will be unlocked as soon as reset() is called, so if you want to use
0139      * the revision afterwards, you have to lock it before calling reset.
0140      *
0141      * zero is returned if the revisions were invalidated after the last call.
0142      * */
0143     RevisionReference revisionAtLastReset() const;
0144 
0145     /**
0146      * Returns the current revision (which is not locked by the tracker)
0147      * */
0148     RevisionReference currentRevision();
0149 
0150     /**
0151      * Whether the changes that happened since the last reset are significant enough to require an update
0152      * */
0153     virtual bool needUpdate() const;
0154 
0155     /**
0156      * Returns the tracked document
0157      **/
0158     KTextEditor::Document* document() const;
0159 
0160     KTextEditor::MovingInterface* documentMovingInterface() const;
0161 
0162     /**
0163      * Returns the revision object which locks the revision representing the on-disk state.
0164      * Returns a zero object if the file is not on disk.
0165      * */
0166     RevisionReference diskRevision() const;
0167 
0168     /**
0169      * Returns whether the given revision is being current held, so that it can be used
0170      * for transformations in MovingInterface
0171      * */
0172     bool holdingRevision(qint64 revision) const;
0173 
0174     /**
0175      * Use this function to acquire a revision. As long as the returned object is stored somewhere,
0176      * the revision can be used for transformations in MovingInterface, and especially for
0177      * DocumentChangeTracker::transformBetweenRevisions.
0178      *
0179      * Returns a zero revision object if the revision could not be acquired (it wasn't held).
0180      * */
0181     RevisionReference acquireRevision(qint64 revision);
0182 
0183     /**
0184      * Safely maps the given range between the two given revisions.
0185      * The mapping is only done if both the from- and to- revision are held,
0186      * else the original range is returned.
0187      *
0188      * @warning: Make sure that you actually hold the referenced revisions, else no transformation will be done.
0189      * @note It is much less error-prone to use RevisionReference->transformToRevision() and RevisionReference->transformFromRevision() directly.
0190      * */
0191     RangeInRevision transformBetweenRevisions(RangeInRevision range, qint64 fromRevision, qint64 toRevision) const;
0192     CursorInRevision transformBetweenRevisions(CursorInRevision cursor, qint64 fromRevision, qint64 toRevision,
0193                                                KTextEditor::MovingCursor::InsertBehavior behavior =
0194                                                    KTextEditor::MovingCursor::StayOnInsert) const;
0195 
0196     KTextEditor::Range transformToCurrentRevision(RangeInRevision range, qint64 fromRevision) const;
0197     KTextEditor::Cursor transformToCurrentRevision(CursorInRevision cursor, qint64 fromRevision,
0198                                                    KTextEditor::MovingCursor::InsertBehavior behavior =
0199                                                        KTextEditor::MovingCursor::StayOnInsert) const;
0200 
0201     /// Transform the range from the current revision into the given one
0202     RangeInRevision transformToRevision(KTextEditor::Range range, qint64 toRevision) const;
0203     /// Transform the cursor from the current revision into the given one
0204     CursorInRevision transformToRevision(KTextEditor::Cursor cursor, qint64 toRevision,
0205                                          KTextEditor::MovingCursor::InsertBehavior behavior =
0206                                              KTextEditor::MovingCursor::StayOnInsert) const;
0207 
0208 protected:
0209     RevisionReference m_revisionAtLastReset;
0210     bool m_needUpdate;
0211     QString m_currentCleanedInsertion;
0212     KTextEditor::Cursor m_lastInsertionPosition;
0213     KTextEditor::MovingRange* m_changedRange;
0214 
0215     KTextEditor::Document* m_document;
0216     KTextEditor::MovingInterface* m_moving;
0217     KDevelop::IndexedString m_url;
0218 
0219     void updateChangedRange(int delay);
0220     int recommendedDelay(KTextEditor::Document* doc, const KTextEditor::Range& range, const QString& text,
0221                          bool removal);
0222 
0223 public Q_SLOTS:
0224     void textInserted(KTextEditor::Document* document, const KTextEditor::Cursor& position, const QString& inserted);
0225     void textRemoved(KTextEditor::Document* document, const KTextEditor::Range& range, const QString& oldText);
0226     void lineWrapped(KTextEditor::Document* document, const KTextEditor::Cursor& position);
0227     void lineUnwrapped(KTextEditor::Document* document, int line);
0228 
0229     void documentDestroyed(QObject*);
0230     void aboutToInvalidateMovingInterfaceContent (KTextEditor::Document* document);
0231     void documentSavedOrUploaded(KTextEditor::Document*, bool);
0232 
0233 private:
0234     bool checkMergeTokens(const KTextEditor::Range& range);
0235 
0236     friend class RevisionLockerAndClearerPrivate;
0237     void lockRevision(qint64 revision);
0238     void unlockRevision(qint64 revision);
0239 
0240     QMap<qint64, int> m_revisionLocks;
0241 };
0242 }
0243 #endif