File indexing completed on 2024-04-14 03:54:59

0001 /*
0002     SPDX-FileCopyrightText: 2013 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #ifndef KATE_TEXTFOLDING_H
0008 #define KATE_TEXTFOLDING_H
0009 
0010 #include <ktexteditor_export.h>
0011 
0012 #include "ktexteditor/range.h"
0013 
0014 #include <QFlags>
0015 #include <QJsonArray>
0016 #include <QJsonDocument>
0017 #include <QObject>
0018 
0019 #include <functional>
0020 
0021 namespace Kate
0022 {
0023 class TextBuffer;
0024 class TextCursor;
0025 
0026 /**
0027  * Class representing the folding information for a TextBuffer.
0028  * The interface allows to arbitrary fold given regions of a buffer as long
0029  * as they are well nested.
0030  * Multiple instances of this class can exist for the same buffer.
0031  */
0032 class KTEXTEDITOR_EXPORT TextFolding : public QObject
0033 {
0034     Q_OBJECT
0035 
0036 public:
0037     /**
0038      * Create folding object for given buffer.
0039      * @param buffer text buffer we want to provide folding info for
0040      */
0041     explicit TextFolding(TextBuffer &buffer);
0042 
0043     /**
0044      * Cleanup
0045      */
0046     ~TextFolding() override;
0047 
0048     /**
0049      * Folding state of a range
0050      */
0051     enum FoldingRangeFlag {
0052         /**
0053          * Range is persistent, e.g. it should not auto-delete after unfolding!
0054          */
0055         Persistent = 0x1,
0056 
0057         /**
0058          * Range is folded away
0059          */
0060         Folded = 0x2
0061     };
0062     Q_DECLARE_FLAGS(FoldingRangeFlags, FoldingRangeFlag)
0063 
0064     /**
0065      * Create a new folding range.
0066      * @param range folding range
0067      * @param flags initial flags for the new folding range
0068      * @return on success, id of new range >= 0, else -1, we return no pointer as folding ranges might be auto-deleted internally!
0069      *         the ids are stable for one Kate::TextFolding, e.g. you can rely in unit tests that you get 0,1,.... for successfully created ranges!
0070      */
0071     qint64 newFoldingRange(KTextEditor::Range range, FoldingRangeFlags flags = FoldingRangeFlags());
0072 
0073     /**
0074      * Returns the folding range associated with @p id.
0075      * If @p id is not a valid id, the returned range matches KTextEditor::Range::invalid().
0076      * @note This works for either persistent ranges or folded ranges.
0077      *       Note, that the highlighting does not add folds unless text is folded.
0078      *
0079      * @return the folding range for @p id
0080      */
0081     KTextEditor::Range foldingRange(qint64 id) const;
0082 
0083     /**
0084      * Fold the given range.
0085      * @param id id of the range to fold
0086      * @return success
0087      */
0088     bool foldRange(qint64 id);
0089 
0090     /**
0091      * Unfold the given range.
0092      * In addition it can be forced to remove the region, even if it is persistent.
0093      * @param id id of the range to unfold
0094      * @param remove should the range be removed from the folding after unfolding? ranges that are not persistent auto-remove themself on unfolding
0095      * @return success
0096      */
0097     bool unfoldRange(qint64 id, bool remove = false);
0098 
0099     /**
0100      * Query if a given line is visible.
0101      * Very fast, if nothing is folded, else does binary search
0102      * log(n) for n == number of folded ranges
0103      * @param line real line to query
0104      * @param foldedRangeId if the line is not visible and that pointer is not 0, will be filled with id of range hiding the line or -1
0105      * @return is that line visible?
0106      */
0107     bool isLineVisible(int line, qint64 *foldedRangeId = nullptr) const;
0108 
0109     /**
0110      * Ensure that a given line will be visible.
0111      * Potentially unfold recursively all folds hiding this line, else just returns.
0112      * @param line line to make visible
0113      */
0114     void ensureLineIsVisible(int line);
0115 
0116     /**
0117      * Query number of visible lines.
0118      * Very fast, if nothing is folded, else walks over all folded regions
0119      * O(n) for n == number of folded ranges
0120      */
0121     int visibleLines() const;
0122 
0123     /**
0124      * Convert a text buffer line to a visible line number.
0125      * Very fast, if nothing is folded, else walks over all folded regions
0126      * O(n) for n == number of folded ranges
0127      * @param line line index in the text buffer
0128      * @return index in visible lines
0129      */
0130     int lineToVisibleLine(int line) const;
0131 
0132     /**
0133      * Convert a visible line number to a line number in the text buffer.
0134      * Very fast, if nothing is folded, else walks over all folded regions
0135      * O(n) for n == number of folded ranges
0136      * @param visibleLine visible line index
0137      * @return index in text buffer lines
0138      */
0139     int visibleLineToLine(int visibleLine) const;
0140 
0141     /**
0142      * Queries which folding ranges start at the given line and returns the id + flags for all
0143      * of them. Very fast if nothing is folded, else binary search.
0144      * @param line line to query starting folding ranges
0145      * @return vector of id's + flags
0146      */
0147     QList<QPair<qint64, FoldingRangeFlags>> foldingRangesStartingOnLine(int line) const;
0148 
0149     /**
0150      * Query child folding ranges for given range id. To query the toplevel
0151      * ranges pass id -1
0152      * @param parentRangeId id of parent range, pass -1 to query top level ranges
0153      * @return vector of id's + flags for child ranges
0154      */
0155     QList<QPair<qint64, FoldingRangeFlags>> foldingRangesForParentRange(qint64 parentRangeId = -1) const;
0156 
0157     /**
0158      * Return the current known folding ranges a QJsonDocument to store in configs.
0159      * @return current folds as variant list
0160      */
0161     QJsonDocument exportFoldingRanges() const;
0162 
0163     /**
0164      * Import the folding ranges given as a QJsonDocument like read from configs.
0165      * @param folds list of folds to import
0166      */
0167     void importFoldingRanges(const QJsonDocument &folds);
0168 
0169     /**
0170      * Dump folding state as string, for unit testing and debugging
0171      * @return current state as text
0172      */
0173     QString debugDump() const;
0174 
0175     /**
0176      * Print state to stdout for testing
0177      */
0178     void debugPrint(const QString &title) const;
0179 
0180     void editEnd(int startLine, int endLine, std::function<bool(int)> isLineFoldingStart);
0181 
0182 public Q_SLOTS:
0183     /**
0184      * Clear the complete folding.
0185      * This is automatically triggered if the buffer is cleared.
0186      */
0187     void clear();
0188 
0189 Q_SIGNALS:
0190     /**
0191      * If the folding state of existing ranges changes or
0192      * ranges are added/removed, this signal is emitted.
0193      */
0194     void foldingRangesChanged();
0195 
0196 private:
0197     /**
0198      * Data holder for text folding range and its nested children
0199      */
0200     class KTEXTEDITOR_NO_EXPORT FoldingRange
0201     {
0202     public:
0203         /**
0204          * Construct new one
0205          * @param buffer text buffer to use
0206          * @param range folding range
0207          * @param flags flags for the new folding range
0208          */
0209         FoldingRange(TextBuffer &buffer, KTextEditor::Range range, FoldingRangeFlags flags);
0210 
0211         /**
0212          * Cleanup
0213          */
0214         ~FoldingRange();
0215 
0216         FoldingRange(const FoldingRange &) = delete;
0217         FoldingRange &operator=(const FoldingRange &) = delete;
0218 
0219         /**
0220          * Vector of range pointers
0221          */
0222         typedef QList<FoldingRange *> Vector;
0223 
0224         /**
0225          * start moving cursor
0226          * NO range to be more efficient
0227          */
0228         Kate::TextCursor *start;
0229 
0230         /**
0231          * end moving cursor
0232          * NO range to be more efficient
0233          */
0234         Kate::TextCursor *end;
0235 
0236         /**
0237          * parent range, if any
0238          */
0239         FoldingRange *parent;
0240 
0241         /**
0242          * nested ranges, if any
0243          * this will always be sorted and non-overlapping
0244          * nested ranges are inside these ranges
0245          */
0246         FoldingRange::Vector nestedRanges;
0247 
0248         /**
0249          * Folding range flags
0250          */
0251         FoldingRangeFlags flags;
0252 
0253         /**
0254          * id of this range
0255          */
0256         qint64 id;
0257     };
0258 
0259     /**
0260      * Clear all folding range collections but leave global id counter intact.
0261      */
0262     KTEXTEDITOR_NO_EXPORT
0263     void clearFoldingRanges();
0264 
0265     /**
0266      * Fill known folding ranges in a QVariantList to store in configs.
0267      * @param ranges ranges vector to dump
0268      * @param folds current folds as variant list, will be filled
0269      */
0270     KTEXTEDITOR_NO_EXPORT
0271     static void exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds);
0272 
0273     /**
0274      * Dump folding state of given vector as string, for unit testing and debugging.
0275      * Will recurse if wanted.
0276      * @param ranges ranges vector to dump
0277      * @param recurse recurse to nestedRanges?
0278      * @return current state as text
0279      */
0280     KTEXTEDITOR_NO_EXPORT
0281     static QString debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse);
0282 
0283     /**
0284      * Helper to insert folding range into existing ones.
0285      * Might fail, if not correctly nested.
0286      * Then the outside must take care of the passed pointer, e.g. delete it.
0287      * Will sanitize the ranges vectors, purge invalid/empty ranges.
0288      * @param parent parent folding range if any
0289      * @param existingRanges ranges into which we want to insert the new one
0290      * @param newRange new folding range
0291      * @return success, if false, newRange should be deleted afterwards, else it is registered internally
0292      */
0293     KTEXTEDITOR_NO_EXPORT
0294     bool insertNewFoldingRange(FoldingRange *parent, TextFolding::FoldingRange::Vector &existingRanges, TextFolding::FoldingRange *newRange);
0295 
0296     /**
0297      * Helper to update the folded ranges if we insert a new range into the tree.
0298      * @param newRange new folding range that was inserted, will already contain its new nested ranges, if any!
0299      * @return any updated done? if yes, the foldingRangesChanged() signal got emitted!
0300      */
0301     KTEXTEDITOR_NO_EXPORT
0302     bool updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange);
0303 
0304     /**
0305      * Helper to update the folded ranges if we remove a new range from the tree.
0306      * @param oldRange new folding range that is removed, will still contain its new nested ranges, if any!
0307      * @return any updated done? if yes, the foldingRangesChanged() signal got emitted!
0308      */
0309     KTEXTEDITOR_NO_EXPORT
0310     bool updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange);
0311 
0312     /**
0313      * Helper to append recursively topmost folded ranges from input to output vector.
0314      * @param newFoldedFoldingRanges output vector for folded ranges
0315      * @param ranges input vector to search recursively folded ranges inside
0316      */
0317     KTEXTEDITOR_NO_EXPORT
0318     void appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const;
0319 
0320     /**
0321      * Compare two ranges by their start cursor.
0322      * @param a first range
0323      * @param b second range
0324      */
0325     KTEXTEDITOR_NO_EXPORT
0326     static bool compareRangeByStart(FoldingRange *a, FoldingRange *b);
0327 
0328     /**
0329      * Compare two ranges by their end cursor.
0330      * @param a first range
0331      * @param b second range
0332      */
0333     KTEXTEDITOR_NO_EXPORT
0334     static bool compareRangeByEnd(FoldingRange *a, FoldingRange *b);
0335 
0336     /**
0337      * Compare range start with line
0338      * @param line line
0339      * @param range range
0340      */
0341     KTEXTEDITOR_NO_EXPORT
0342     static bool compareRangeByStartWithLine(int line, FoldingRange *range);
0343 
0344     /**
0345      * Compare range start with line
0346      * @param range range
0347      * @param line line
0348      */
0349     KTEXTEDITOR_NO_EXPORT
0350     static bool compareRangeByLineWithStart(FoldingRange *range, int line);
0351 
0352     /**
0353      * Internal helper that queries which folding ranges start at the given line and returns the id + flags for all
0354      * of them. Will recursively dive down starting with given vector
0355      * @param results vector that is filled with id's + flags
0356      * @param ranges ranges vector to search in
0357      * @param line line to query starting folding ranges
0358      */
0359     KTEXTEDITOR_NO_EXPORT
0360     void foldingRangesStartingOnLine(QList<QPair<qint64, FoldingRangeFlags>> &results, const TextFolding::FoldingRange::Vector &ranges, int line) const;
0361 
0362 private:
0363     /**
0364      * parent text buffer
0365      * is a reference, and no pointer, as this must always exist and can't change
0366      * can't be const, as we create text cursors!
0367      */
0368     TextBuffer &m_buffer;
0369 
0370     /**
0371      * toplevel folding ranges
0372      * this will always be sorted and non-overlapping
0373      * nested ranges are inside these ranges
0374      */
0375     FoldingRange::Vector m_foldingRanges;
0376 
0377     /**
0378      * folded folding ranges
0379      * this is a sorted vector of ranges
0380      * all non-overlapping
0381      */
0382     FoldingRange::Vector m_foldedFoldingRanges;
0383 
0384     /**
0385      * global id counter for the created ranges
0386      */
0387     qint64 m_idCounter;
0388 
0389     /**
0390      * mapping: id => range
0391      */
0392     QHash<qint64, FoldingRange *> m_idToFoldingRange;
0393 };
0394 
0395 Q_DECLARE_OPERATORS_FOR_FLAGS(TextFolding::FoldingRangeFlags)
0396 
0397 }
0398 
0399 #endif