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