1/*
2 SPDX-FileCopyrightText: 2013 Christoph Cullmann <cullmann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#ifndef KATE_TEXTFOLDING_H
8#define KATE_TEXTFOLDING_H
9
10#include <ktexteditor_export.h>
11
12#include "ktexteditor/range.h"
13
14#include <QFlags>
15#include <QJsonArray>
16#include <QJsonDocument>
17#include <QObject>
18
19#include <functional>
20
21namespace Kate
22{
23class TextBuffer;
24class TextCursor;
25
26/**
27 * Class representing the folding information for a TextBuffer.
28 * The interface allows to arbitrary fold given regions of a buffer as long
29 * as they are well nested.
30 * Multiple instances of this class can exist for the same buffer.
31 */
32class KTEXTEDITOR_EXPORT TextFolding : public QObject
33{
34 Q_OBJECT
35
36public:
37 /**
38 * Create folding object for given buffer.
39 * @param buffer text buffer we want to provide folding info for
40 */
41 explicit TextFolding(TextBuffer &buffer);
42
43 /**
44 * Cleanup
45 */
46 ~TextFolding() override;
47
48 /**
49 * Folding state of a range
50 */
51 enum FoldingRangeFlag {
52 /**
53 * Range is persistent, e.g. it should not auto-delete after unfolding!
54 */
55 Persistent = 0x1,
56
57 /**
58 * Range is folded away
59 */
60 Folded = 0x2
61 };
62 Q_DECLARE_FLAGS(FoldingRangeFlags, FoldingRangeFlag)
63
64 /**
65 * Create a new folding range.
66 * @param range folding range
67 * @param flags initial flags for the new folding range
68 * @return on success, id of new range >= 0, else -1, we return no pointer as folding ranges might be auto-deleted internally!
69 * 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!
70 */
71 qint64 newFoldingRange(KTextEditor::Range range, FoldingRangeFlags flags = FoldingRangeFlags());
72
73 /**
74 * Returns the folding range associated with @p id.
75 * If @p id is not a valid id, the returned range matches KTextEditor::Range::invalid().
76 * @note This works for either persistent ranges or folded ranges.
77 * Note, that the highlighting does not add folds unless text is folded.
78 *
79 * @return the folding range for @p id
80 */
81 KTextEditor::Range foldingRange(qint64 id) const;
82
83 /**
84 * Fold the given range.
85 * @param id id of the range to fold
86 * @return success
87 */
88 bool foldRange(qint64 id);
89
90 /**
91 * Unfold the given range.
92 * In addition it can be forced to remove the region, even if it is persistent.
93 * @param id id of the range to unfold
94 * @param remove should the range be removed from the folding after unfolding? ranges that are not persistent auto-remove themself on unfolding
95 * @return success
96 */
97 bool unfoldRange(qint64 id, bool remove = false);
98
99 /**
100 * Query if a given line is visible.
101 * Very fast, if nothing is folded, else does binary search
102 * log(n) for n == number of folded ranges
103 * @param line real line to query
104 * @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
105 * @return is that line visible?
106 */
107 bool isLineVisible(int line, qint64 *foldedRangeId = nullptr) const;
108
109 /**
110 * Ensure that a given line will be visible.
111 * Potentially unfold recursively all folds hiding this line, else just returns.
112 * @param line line to make visible
113 */
114 void ensureLineIsVisible(int line);
115
116 /**
117 * Query number of visible lines.
118 * Very fast, if nothing is folded, else walks over all folded regions
119 * O(n) for n == number of folded ranges
120 */
121 int visibleLines() const;
122
123 /**
124 * Convert a text buffer line to a visible line number.
125 * Very fast, if nothing is folded, else walks over all folded regions
126 * O(n) for n == number of folded ranges
127 * @param line line index in the text buffer
128 * @return index in visible lines
129 */
130 int lineToVisibleLine(int line) const;
131
132 /**
133 * Convert a visible line number to a line number in the text buffer.
134 * Very fast, if nothing is folded, else walks over all folded regions
135 * O(n) for n == number of folded ranges
136 * @param visibleLine visible line index
137 * @return index in text buffer lines
138 */
139 int visibleLineToLine(int visibleLine) const;
140
141 /**
142 * Queries which folding ranges start at the given line and returns the id + flags for all
143 * of them. Very fast if nothing is folded, else binary search.
144 * @param line line to query starting folding ranges
145 * @return vector of id's + flags
146 */
147 QList<QPair<qint64, FoldingRangeFlags>> foldingRangesStartingOnLine(int line) const;
148
149 /**
150 * Query child folding ranges for given range id. To query the toplevel
151 * ranges pass id -1
152 * @param parentRangeId id of parent range, pass -1 to query top level ranges
153 * @return vector of id's + flags for child ranges
154 */
155 QList<QPair<qint64, FoldingRangeFlags>> foldingRangesForParentRange(qint64 parentRangeId = -1) const;
156
157 /**
158 * Return the current known folding ranges a QJsonDocument to store in configs.
159 * @return current folds as variant list
160 */
161 QJsonDocument exportFoldingRanges() const;
162
163 /**
164 * Import the folding ranges given as a QJsonDocument like read from configs.
165 * @param folds list of folds to import
166 */
167 void importFoldingRanges(const QJsonDocument &folds);
168
169 /**
170 * Dump folding state as string, for unit testing and debugging
171 * @return current state as text
172 */
173 QString debugDump() const;
174
175 /**
176 * Print state to stdout for testing
177 */
178 void debugPrint(const QString &title) const;
179
180 void editEnd(int startLine, int endLine, std::function<bool(int)> isLineFoldingStart);
181
182public Q_SLOTS:
183 /**
184 * Clear the complete folding.
185 * This is automatically triggered if the buffer is cleared.
186 */
187 void clear();
188
189Q_SIGNALS:
190 /**
191 * If the folding state of existing ranges changes or
192 * ranges are added/removed, this signal is emitted.
193 */
194 void foldingRangesChanged();
195
196private:
197 /**
198 * Data holder for text folding range and its nested children
199 */
200 class KTEXTEDITOR_NO_EXPORT FoldingRange
201 {
202 public:
203 /**
204 * Construct new one
205 * @param buffer text buffer to use
206 * @param range folding range
207 * @param flags flags for the new folding range
208 */
209 FoldingRange(TextBuffer &buffer, KTextEditor::Range range, FoldingRangeFlags flags);
210
211 /**
212 * Cleanup
213 */
214 ~FoldingRange();
215
216 FoldingRange(const FoldingRange &) = delete;
217 FoldingRange &operator=(const FoldingRange &) = delete;
218
219 /**
220 * Vector of range pointers
221 */
222 typedef QList<FoldingRange *> Vector;
223
224 /**
225 * start moving cursor
226 * NO range to be more efficient
227 */
228 Kate::TextCursor *start;
229
230 /**
231 * end moving cursor
232 * NO range to be more efficient
233 */
234 Kate::TextCursor *end;
235
236 /**
237 * parent range, if any
238 */
239 FoldingRange *parent;
240
241 /**
242 * nested ranges, if any
243 * this will always be sorted and non-overlapping
244 * nested ranges are inside these ranges
245 */
246 FoldingRange::Vector nestedRanges;
247
248 /**
249 * Folding range flags
250 */
251 FoldingRangeFlags flags;
252
253 /**
254 * id of this range
255 */
256 qint64 id;
257 };
258
259 /**
260 * Clear all folding range collections but leave global id counter intact.
261 */
262 KTEXTEDITOR_NO_EXPORT
263 void clearFoldingRanges();
264
265 /**
266 * Fill known folding ranges in a QVariantList to store in configs.
267 * @param ranges ranges vector to dump
268 * @param folds current folds as variant list, will be filled
269 */
270 KTEXTEDITOR_NO_EXPORT
271 static void exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds);
272
273 /**
274 * Dump folding state of given vector as string, for unit testing and debugging.
275 * Will recurse if wanted.
276 * @param ranges ranges vector to dump
277 * @param recurse recurse to nestedRanges?
278 * @return current state as text
279 */
280 KTEXTEDITOR_NO_EXPORT
281 static QString debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse);
282
283 /**
284 * Helper to insert folding range into existing ones.
285 * Might fail, if not correctly nested.
286 * Then the outside must take care of the passed pointer, e.g. delete it.
287 * Will sanitize the ranges vectors, purge invalid/empty ranges.
288 * @param parent parent folding range if any
289 * @param existingRanges ranges into which we want to insert the new one
290 * @param newRange new folding range
291 * @return success, if false, newRange should be deleted afterwards, else it is registered internally
292 */
293 KTEXTEDITOR_NO_EXPORT
294 bool insertNewFoldingRange(FoldingRange *parent, TextFolding::FoldingRange::Vector &existingRanges, TextFolding::FoldingRange *newRange);
295
296 /**
297 * Helper to update the folded ranges if we insert a new range into the tree.
298 * @param newRange new folding range that was inserted, will already contain its new nested ranges, if any!
299 * @return any updated done? if yes, the foldingRangesChanged() signal got emitted!
300 */
301 KTEXTEDITOR_NO_EXPORT
302 bool updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange);
303
304 /**
305 * Helper to update the folded ranges if we remove a new range from the tree.
306 * @param oldRange new folding range that is removed, will still contain its new nested ranges, if any!
307 * @return any updated done? if yes, the foldingRangesChanged() signal got emitted!
308 */
309 KTEXTEDITOR_NO_EXPORT
310 bool updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange);
311
312 /**
313 * Helper to append recursively topmost folded ranges from input to output vector.
314 * @param newFoldedFoldingRanges output vector for folded ranges
315 * @param ranges input vector to search recursively folded ranges inside
316 */
317 KTEXTEDITOR_NO_EXPORT
318 void appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const;
319
320 /**
321 * Compare two ranges by their start cursor.
322 * @param a first range
323 * @param b second range
324 */
325 KTEXTEDITOR_NO_EXPORT
326 static bool compareRangeByStart(FoldingRange *a, FoldingRange *b);
327
328 /**
329 * Compare two ranges by their end cursor.
330 * @param a first range
331 * @param b second range
332 */
333 KTEXTEDITOR_NO_EXPORT
334 static bool compareRangeByEnd(FoldingRange *a, FoldingRange *b);
335
336 /**
337 * Compare range start with line
338 * @param line line
339 * @param range range
340 */
341 KTEXTEDITOR_NO_EXPORT
342 static bool compareRangeByStartWithLine(int line, FoldingRange *range);
343
344 /**
345 * Compare range start with line
346 * @param range range
347 * @param line line
348 */
349 KTEXTEDITOR_NO_EXPORT
350 static bool compareRangeByLineWithStart(FoldingRange *range, int line);
351
352 /**
353 * Internal helper that queries which folding ranges start at the given line and returns the id + flags for all
354 * of them. Will recursively dive down starting with given vector
355 * @param results vector that is filled with id's + flags
356 * @param ranges ranges vector to search in
357 * @param line line to query starting folding ranges
358 */
359 KTEXTEDITOR_NO_EXPORT
360 void foldingRangesStartingOnLine(QList<QPair<qint64, FoldingRangeFlags>> &results, const TextFolding::FoldingRange::Vector &ranges, int line) const;
361
362private:
363 /**
364 * parent text buffer
365 * is a reference, and no pointer, as this must always exist and can't change
366 * can't be const, as we create text cursors!
367 */
368 TextBuffer &m_buffer;
369
370 /**
371 * toplevel folding ranges
372 * this will always be sorted and non-overlapping
373 * nested ranges are inside these ranges
374 */
375 FoldingRange::Vector m_foldingRanges;
376
377 /**
378 * folded folding ranges
379 * this is a sorted vector of ranges
380 * all non-overlapping
381 */
382 FoldingRange::Vector m_foldedFoldingRanges;
383
384 /**
385 * global id counter for the created ranges
386 */
387 qint64 m_idCounter;
388
389 /**
390 * mapping: id => range
391 */
392 QHash<qint64, FoldingRange *> m_idToFoldingRange;
393};
394
395Q_DECLARE_OPERATORS_FOR_FLAGS(TextFolding::FoldingRangeFlags)
396
397}
398
399#endif
400

source code of ktexteditor/src/buffer/katetextfolding.h