File indexing completed on 2024-10-06 12:24:16
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 QVector<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 QVector<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 QVector<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(QVector<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