1 | /* |
2 | SPDX-FileCopyrightText: 2001-2005 Christoph Cullmann <cullmann@kde.org> |
3 | SPDX-FileCopyrightText: 2021 Dominik Haumann <dhaumann@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-or-later |
6 | */ |
7 | |
8 | #ifndef KTEXTEDITOR_LINERANGE_H |
9 | #define KTEXTEDITOR_LINERANGE_H |
10 | |
11 | #include <ktexteditor_export.h> |
12 | |
13 | #include <QtGlobal> |
14 | |
15 | class QDebug; |
16 | class QString; |
17 | class QStringView; |
18 | |
19 | namespace KTextEditor |
20 | { |
21 | /** |
22 | * @class LineRange linerange.h <KTextEditor/LineRange> |
23 | * |
24 | * @short An object representing lines from a start line to an end line. |
25 | * |
26 | * A LineRange is a basic class which represents a range of lines, from a start() |
27 | * line to an end() line. |
28 | * |
29 | * For simplicity and convenience, ranges always maintain their start() line to |
30 | * be before or equal to their end() line. Attempting to set either the |
31 | * start or end of the range beyond the respective end or start will result in |
32 | * both values being set to the specified line. In the constructor, the |
33 | * start and end will be swapped if necessary. |
34 | * |
35 | * @sa Range |
36 | * |
37 | * @since 5.79 |
38 | * @author Dominik Haumann \<dhaumann@kde.org\> |
39 | */ |
40 | class KTEXTEDITOR_EXPORT LineRange |
41 | { |
42 | public: |
43 | /** |
44 | * Default constructor. Creates a valid line range with both start and end |
45 | * line set to 0. |
46 | */ |
47 | constexpr LineRange() noexcept = default; |
48 | |
49 | /** |
50 | * Constructor which creates a range from \e start to \e end. |
51 | * If start is after end, they will be swapped. |
52 | * |
53 | * @param start start line |
54 | * @param end end line |
55 | */ |
56 | constexpr LineRange(int start, int end) noexcept |
57 | : m_start(qMin(a: start, b: end)) |
58 | , m_end(qMax(a: start, b: end)) |
59 | { |
60 | } |
61 | |
62 | /** |
63 | * Validity check. In the base class, returns true unless the line range starts before (0,0). |
64 | */ |
65 | constexpr bool isValid() const noexcept |
66 | { |
67 | return m_start >= 0 && m_end >= 0; |
68 | } |
69 | |
70 | /** |
71 | * Returns an invalid line range. |
72 | */ |
73 | constexpr static LineRange invalid() noexcept |
74 | { |
75 | return LineRange(-1, -1); |
76 | } |
77 | |
78 | /** |
79 | * Returns the line range as string in the format |
80 | * "[start line, end line]". |
81 | * @see fromString() |
82 | */ |
83 | QString toString() const; |
84 | |
85 | /** |
86 | * Returns a LineRange created from the string \p str containing the format |
87 | * "[start line, end line]". |
88 | * In case the string cannot be parsed, an LineRange::invalid() is returned. |
89 | * @see toString() |
90 | */ |
91 | static LineRange fromString(QStringView str) noexcept; |
92 | |
93 | /** |
94 | * @name Position |
95 | * |
96 | * The following functions provide access to, and manipulation of, the range's position. |
97 | * @{ |
98 | */ |
99 | |
100 | /** |
101 | * Get the start line of this line range. This will always be <= end(). |
102 | * |
103 | * @returns the start line of this line range. |
104 | */ |
105 | constexpr int start() const noexcept |
106 | { |
107 | return m_start; |
108 | } |
109 | |
110 | /** |
111 | * Get the end line of this line range. This will always be >= start(). |
112 | * |
113 | * @returns the end line of this line range. |
114 | */ |
115 | constexpr int end() const noexcept |
116 | { |
117 | return m_end; |
118 | } |
119 | |
120 | /** |
121 | * Set the start and end lines to \e start and \e end respectively. |
122 | * |
123 | * \note If \e start is after \e end, they will be reversed. |
124 | * |
125 | * \param start start line |
126 | * \param end end line |
127 | */ |
128 | void setRange(LineRange range) noexcept |
129 | { |
130 | setRange(start: range.start(), end: range.end()); |
131 | } |
132 | |
133 | /** |
134 | * Set the start and end lines to \e start and \e end respectively. |
135 | * |
136 | * \note If \e start is after \e end, they will be reversed. |
137 | * |
138 | * \param start start line |
139 | * \param end end line |
140 | */ |
141 | void setRange(int start, int end) noexcept |
142 | { |
143 | m_start = qMin(a: start, b: end); |
144 | m_end = qMax(a: start, b: end); |
145 | } |
146 | |
147 | /** |
148 | * Convenience function. Set the start and end lines to \p line. |
149 | * |
150 | * @param line the line number to assign to start() and end() |
151 | */ |
152 | void setBothLines(int line) noexcept |
153 | { |
154 | m_start = line; |
155 | m_end = line; |
156 | } |
157 | |
158 | /** |
159 | * Set the start line to \e start. |
160 | * |
161 | * @note If \e start is after current end, start and end will be set to new start value. |
162 | * |
163 | * @param start new start line |
164 | */ |
165 | void setStart(int start) noexcept |
166 | { |
167 | if (start > end()) { |
168 | setRange(start, end: start); |
169 | } else { |
170 | setRange(start, end: end()); |
171 | } |
172 | } |
173 | |
174 | /** |
175 | * Set the end line to \e end. |
176 | * |
177 | * @note If \e end is in front of current start, start and end will be set to new end value. |
178 | * |
179 | * @param end new end line |
180 | */ |
181 | void setEnd(int end) noexcept |
182 | { |
183 | if (end < start()) { |
184 | setRange(start: end, end); |
185 | } else { |
186 | setRange(start: start(), end); |
187 | } |
188 | } |
189 | |
190 | /** |
191 | * Expand this line range if necessary to contain \p range. |
192 | * |
193 | * @param range range which this range should contain |
194 | * |
195 | * @return \e true if expansion occurred, \e false otherwise |
196 | */ |
197 | bool expandToRange(LineRange range) noexcept |
198 | { |
199 | if (start() > range.start()) |
200 | if (end() < range.end()) { |
201 | setRange(range); |
202 | } else { |
203 | setStart(range.start()); |
204 | } |
205 | else if (end() < range.end()) { |
206 | setEnd(range.end()); |
207 | } else { |
208 | return false; |
209 | } |
210 | |
211 | return true; |
212 | } |
213 | |
214 | /** |
215 | * Confine this range if necessary to fit within \p range. |
216 | * |
217 | * @param range range which should contain this range |
218 | * |
219 | * @return \e true if confinement occurred, \e false otherwise |
220 | */ |
221 | bool confineToRange(LineRange range) noexcept |
222 | { |
223 | if (start() < range.start()) |
224 | if (end() > range.end()) { |
225 | setRange(range); |
226 | } else { |
227 | setStart(range.start()); |
228 | } |
229 | else if (end() > range.end()) { |
230 | setEnd(range.end()); |
231 | } else { |
232 | return false; |
233 | } |
234 | |
235 | return true; |
236 | } |
237 | |
238 | /** |
239 | * Check whether this line range is on one line. |
240 | * |
241 | * @return \e true if both the start and end line are equal, otherwise \e false |
242 | */ |
243 | constexpr bool onSingleLine() const noexcept |
244 | { |
245 | return start() == end(); |
246 | } |
247 | |
248 | /** |
249 | * Returns the number of lines separating the start() and end() line. |
250 | * |
251 | * @return the number of lines separating the start() and end() line; |
252 | * 0 if the start and end lines are the same. |
253 | */ |
254 | constexpr int numberOfLines() const noexcept |
255 | { |
256 | return end() - start(); |
257 | } |
258 | |
259 | // BEGIN comparison functions |
260 | /** |
261 | * @} |
262 | * |
263 | * @name Comparison |
264 | * |
265 | * The following functions perform checks against this range in comparison |
266 | * to other lines and ranges. |
267 | * @{ |
268 | */ |
269 | /** |
270 | * Check whether the this range wholly encompasses \e range. |
271 | * |
272 | * @param range range to check |
273 | * |
274 | * @return \e true, if this range contains \e range, otherwise \e false |
275 | */ |
276 | constexpr bool contains(LineRange range) const noexcept |
277 | { |
278 | return range.start() >= start() && range.end() <= end(); |
279 | } |
280 | |
281 | /** |
282 | * Returns true if this range wholly encompasses \p line. |
283 | * |
284 | * @param line line to check |
285 | * |
286 | * @return \e true if the line is wholly encompassed by this range, otherwise \e false. |
287 | */ |
288 | constexpr bool containsLine(int line) const noexcept |
289 | { |
290 | return line >= start() && line < end(); |
291 | } |
292 | |
293 | /** |
294 | * Check whether the this range overlaps with \e range. |
295 | * |
296 | * @param range range to check against |
297 | * |
298 | * @return \e true, if this range overlaps with \e range, otherwise \e false |
299 | */ |
300 | constexpr bool overlaps(LineRange range) const noexcept |
301 | { |
302 | return (range.start() <= start()) ? (range.end() > start()) : (range.end() >= end()) ? (range.start() < end()) : contains(range); |
303 | } |
304 | |
305 | /** |
306 | * Check whether the range overlaps at least part of \e line. |
307 | * |
308 | * @param line line to check |
309 | * |
310 | * @return \e true, if the range overlaps at least part of \e line, otherwise \e false |
311 | */ |
312 | constexpr bool overlapsLine(int line) const noexcept |
313 | { |
314 | return line >= start() && line <= end(); |
315 | } |
316 | //!\} |
317 | // END |
318 | |
319 | /** |
320 | * Intersects this line range with another, returning the shared lines of |
321 | * the two line ranges. |
322 | * |
323 | * @param range other line range to intersect with this |
324 | * |
325 | * @return the intersection of this range and the supplied \a range. |
326 | */ |
327 | constexpr LineRange intersect(LineRange range) const noexcept |
328 | { |
329 | return ((!isValid() || !range.isValid() || *this > range || *this < range)) ? invalid() |
330 | : LineRange(qMax(a: start(), b: range.start()), qMin(a: end(), b: range.end())); |
331 | } |
332 | |
333 | /** |
334 | * Returns the smallest range which encompasses this line range and the |
335 | * supplied \a range. |
336 | * |
337 | * @param range other range to encompass |
338 | * |
339 | * @return the smallest range which contains this range and the supplied \a range. |
340 | */ |
341 | constexpr LineRange encompass(LineRange range) const noexcept |
342 | { |
343 | return (!isValid()) ? (range.isValid() ? range : invalid()) |
344 | : (!range.isValid()) ? (*this) |
345 | : LineRange(qMin(a: start(), b: range.start()), qMax(a: end(), b: range.end())); |
346 | } |
347 | |
348 | /** |
349 | * Addition operator. Takes two ranges and returns their summation. |
350 | * |
351 | * @param r1 the first range |
352 | * @param r2 the second range |
353 | * |
354 | * @return a the summation of the two input ranges |
355 | */ |
356 | constexpr friend LineRange operator+(LineRange r1, LineRange r2) noexcept |
357 | { |
358 | return LineRange(r1.start() + r2.start(), r1.end() + r2.end()); |
359 | } |
360 | |
361 | /** |
362 | * Addition assignment operator. Adds \p r2 to this range. |
363 | * |
364 | * @param r1 the first range |
365 | * @param r2 the second range |
366 | * |
367 | * @return a reference to the line range which has just been added to |
368 | */ |
369 | friend LineRange &operator+=(LineRange &r1, LineRange r2) noexcept |
370 | { |
371 | r1.setRange(start: r1.start() + r2.start(), end: r1.end() + r2.end()); |
372 | return r1; |
373 | } |
374 | |
375 | /** |
376 | * Subtraction operator. Takes two ranges and returns the subtraction |
377 | * of \p r2 from \p r1. |
378 | * |
379 | * @param r1 the first range |
380 | * @param r2 the second range |
381 | * |
382 | * @return a range representing the subtraction of \p r2 from \p r1 |
383 | */ |
384 | constexpr friend LineRange operator-(LineRange r1, LineRange r2) noexcept |
385 | { |
386 | return LineRange(r1.start() - r2.start(), r1.end() - r2.end()); |
387 | } |
388 | |
389 | /** |
390 | * Subtraction assignment operator. Subtracts \p r2 from \p r1. |
391 | * |
392 | * @param r1 the first range |
393 | * @param r2 the second range |
394 | * |
395 | * @return a reference to the range which has just been subtracted from |
396 | */ |
397 | friend LineRange &operator-=(LineRange &r1, LineRange r2) noexcept |
398 | { |
399 | r1.setRange(start: r1.start() - r2.start(), end: r1.end() - r2.end()); |
400 | return r1; |
401 | } |
402 | |
403 | /** |
404 | * Intersects \a r1 and \a r2. |
405 | * |
406 | * @param r1 the first range |
407 | * @param r2 the second range |
408 | * |
409 | * @return the intersected range, invalid() if there is no overlap |
410 | */ |
411 | constexpr friend LineRange operator&(LineRange r1, LineRange r2) noexcept |
412 | { |
413 | return r1.intersect(range: r2); |
414 | } |
415 | |
416 | /** |
417 | * Intersects \a r1 with \a r2 and assigns the result to \a r1. |
418 | * |
419 | * @param r1 the range to assign the intersection to |
420 | * @param r2 the range to intersect \a r1 with |
421 | * |
422 | * @return a reference to this range, after the intersection has taken place |
423 | */ |
424 | friend LineRange &operator&=(LineRange &r1, LineRange r2) noexcept |
425 | { |
426 | r1.setRange(r1.intersect(range: r2)); |
427 | return r1; |
428 | } |
429 | |
430 | /** |
431 | * Equality operator. |
432 | * |
433 | * @param r1 first range to compare |
434 | * @param r2 second range to compare |
435 | * |
436 | * @return \e true if \e r1 and \e r2 equal, otherwise \e false |
437 | */ |
438 | constexpr friend bool operator==(LineRange r1, LineRange r2) noexcept |
439 | { |
440 | return r1.start() == r2.start() && r1.end() == r2.end(); |
441 | } |
442 | |
443 | /** |
444 | * Inequality operator. |
445 | * |
446 | * @param r1 first range to compare |
447 | * @param r2 second range to compare |
448 | * |
449 | * @return \e true if \e r1 and \e r2 do \e not equal, otherwise \e false |
450 | */ |
451 | constexpr friend bool operator!=(LineRange r1, LineRange r2) noexcept |
452 | { |
453 | return r1.start() != r2.start() || r1.end() != r2.end(); |
454 | } |
455 | |
456 | /** |
457 | * Greater than operator. Looks only at the lines of the two ranges, |
458 | * does not consider their size. |
459 | * |
460 | * @param r1 first range to compare |
461 | * @param r2 second range to compare |
462 | * |
463 | * @return \e true if \e r1 starts after where \e r2 ends, otherwise \e false |
464 | */ |
465 | constexpr friend bool operator>(LineRange r1, LineRange r2) noexcept |
466 | { |
467 | return r1.start() > r2.end(); |
468 | } |
469 | |
470 | /** |
471 | * Less than operator. Looks only at the lines of the two ranges, |
472 | * does not consider their size. |
473 | * |
474 | * @param r1 first range to compare |
475 | * @param r2 second range to compare |
476 | * |
477 | * @return \e true if \e r1 ends before \e r2 begins, otherwise \e false |
478 | */ |
479 | constexpr friend bool operator<(LineRange r1, LineRange r2) noexcept |
480 | { |
481 | return r1.end() < r2.start(); |
482 | } |
483 | |
484 | private: |
485 | /** |
486 | * This range's start line. |
487 | * |
488 | * @internal |
489 | */ |
490 | int m_start = 0; |
491 | |
492 | /** |
493 | * This range's end line. |
494 | * |
495 | * @internal |
496 | */ |
497 | int m_end = 0; |
498 | }; |
499 | |
500 | /** |
501 | * QHash function for KTextEditor::LineRange. |
502 | * Returns the hash value for @p range. |
503 | */ |
504 | KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::LineRange range, size_t seed = 0) noexcept; |
505 | } |
506 | |
507 | Q_DECLARE_TYPEINFO(KTextEditor::LineRange, Q_PRIMITIVE_TYPE); |
508 | |
509 | /** |
510 | * qDebug() stream operator. Writes this range to the debug output in a nicely formatted way. |
511 | */ |
512 | KTEXTEDITOR_EXPORT QDebug operator<<(QDebug s, KTextEditor::LineRange range); |
513 | |
514 | namespace QTest |
515 | { |
516 | // forward declaration of template in qtestcase.h |
517 | template<typename T> |
518 | char *toString(const T &); |
519 | |
520 | /** |
521 | * QTestLib integration to have nice output in e.g. QCOMPARE failures. |
522 | */ |
523 | template<> |
524 | KTEXTEDITOR_EXPORT char *toString(const KTextEditor::LineRange &range); |
525 | } |
526 | |
527 | #endif |
528 | |