1/*
2 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
3
4 Based on code of the SmartCursor/Range by:
5 SPDX-FileCopyrightText: 2003-2005 Hamish Rodda <rodda@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "katetextrange.h"
11#include "katedocument.h"
12#include "katetextbuffer.h"
13
14namespace Kate
15{
16TextRange::TextRange(TextBuffer &buffer, KTextEditor::Range range, InsertBehaviors insertBehavior, EmptyBehavior emptyBehavior)
17 : m_buffer(buffer)
18 , m_start(buffer, this, range.start(), (insertBehavior & ExpandLeft) ? Kate::TextCursor::StayOnInsert : Kate::TextCursor::MoveOnInsert)
19 , m_end(buffer, this, range.end(), (insertBehavior & ExpandRight) ? Kate::TextCursor::MoveOnInsert : Kate::TextCursor::StayOnInsert)
20 , m_view(nullptr)
21 , m_feedback(nullptr)
22 , m_zDepth(0.0)
23 , m_attributeOnlyForViews(false)
24 , m_invalidateIfEmpty(emptyBehavior == InvalidateIfEmpty)
25{
26 // remember this range in buffer
27 m_buffer.m_ranges.insert(value: this);
28
29 // check if range now invalid, there can happen no feedback, as m_feedback == 0
30 // only place where KTextEditor::LineRange::invalid() for old range makes sense, as we were yet not registered!
31 checkValidity(oldLineRange: KTextEditor::LineRange::invalid());
32}
33
34TextRange::~TextRange()
35{
36 // reset feedback, don't want feedback during destruction
37 m_feedback = nullptr;
38
39 // remove range from m_ranges
40 fixLookup(oldLineRange: toLineRange(), lineRange: KTextEditor::LineRange::invalid());
41
42 // remove this range from the buffer
43 m_buffer.m_ranges.remove(value: this);
44
45 // trigger update, if we have attribute
46 // notify right view
47 // here we can ignore feedback, even with feedback, we want none if the range is deleted!
48 if (m_attribute) {
49 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: true /* we have a attribute */);
50 }
51}
52
53void TextRange::setInsertBehaviors(InsertBehaviors _insertBehaviors)
54{
55 // nothing to do?
56 if (_insertBehaviors == insertBehaviors()) {
57 return;
58 }
59
60 // modify cursors
61 m_start.setInsertBehavior((_insertBehaviors & ExpandLeft) ? KTextEditor::MovingCursor::StayOnInsert : KTextEditor::MovingCursor::MoveOnInsert);
62 m_end.setInsertBehavior((_insertBehaviors & ExpandRight) ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert);
63
64 // notify world
65 if (m_attribute || m_feedback) {
66 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: true /* we have a attribute */);
67 }
68}
69
70KTextEditor::MovingRange::InsertBehaviors TextRange::insertBehaviors() const
71{
72 InsertBehaviors behaviors = DoNotExpand;
73
74 if (m_start.insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) {
75 behaviors |= ExpandLeft;
76 }
77
78 if (m_end.insertBehavior() == KTextEditor::MovingCursor::MoveOnInsert) {
79 behaviors |= ExpandRight;
80 }
81
82 return behaviors;
83}
84
85void TextRange::setEmptyBehavior(EmptyBehavior emptyBehavior)
86{
87 // nothing to do?
88 if (m_invalidateIfEmpty == (emptyBehavior == InvalidateIfEmpty)) {
89 return;
90 }
91
92 // remember value
93 m_invalidateIfEmpty = (emptyBehavior == InvalidateIfEmpty);
94
95 // invalidate range?
96 if (endInternal() <= startInternal()) {
97 setRange(KTextEditor::Range::invalid());
98 }
99}
100
101void TextRange::setRange(KTextEditor::Range range)
102{
103 // avoid work if nothing changed!
104 if (range == toRange()) {
105 return;
106 }
107
108 // remember old line range
109 const auto oldLineRange = toLineRange();
110
111 // change start and end cursor
112 m_start.setPosition(range.start());
113 m_end.setPosition(range.end());
114
115 // check if range now invalid, don't emit feedback here, will be handled below
116 // otherwise you can't delete ranges in feedback!
117 checkValidity(oldLineRange, notifyAboutChange: false);
118
119 // no attribute or feedback set, be done
120 if (!m_attribute && !m_feedback) {
121 return;
122 }
123
124 // get full range
125 int startLineMin = oldLineRange.start();
126 if (oldLineRange.start() == -1 || (m_start.lineInternal() != -1 && m_start.lineInternal() < oldLineRange.start())) {
127 startLineMin = m_start.line();
128 }
129
130 int endLineMax = oldLineRange.end();
131 if (oldLineRange.end() == -1 || m_end.lineInternal() > oldLineRange.end()) {
132 endLineMax = m_end.lineInternal();
133 }
134
135 // notify buffer about attribute change, it will propagate the changes
136 // notify right view
137 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: {startLineMin, endLineMax}, needsRepaint: m_attribute);
138
139 // perhaps need to notify stuff!
140 if (m_feedback) {
141 // do this last: may delete this range
142 if (!toRange().isValid()) {
143 m_feedback->rangeInvalid(range: this);
144 } else if (toRange().isEmpty()) {
145 m_feedback->rangeEmpty(range: this);
146 }
147 }
148}
149
150void TextRange::setRange(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute)
151{
152 // FIXME: optimize
153 setRange(range);
154 setAttribute(attribute);
155}
156
157void TextRange::setRange(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute, qreal zDepth)
158{
159 // FIXME: optimize
160 setRange(range);
161 setAttribute(attribute);
162 setZDepth(zDepth);
163}
164
165void TextRange::checkValidity(KTextEditor::LineRange oldLineRange, bool notifyAboutChange)
166{
167 // in any case: reset the flag, to avoid multiple runs
168 m_isCheckValidityRequired = false;
169
170 // check if any cursor is invalid or the range is zero size and it should be invalidated then
171 if (!m_start.isValid() || !m_end.isValid() || (m_invalidateIfEmpty && m_end <= m_start)) {
172 m_start.setPosition(line: -1, column: -1);
173 m_end.setPosition(line: -1, column: -1);
174 }
175
176 // for ranges which are allowed to become empty, normalize them, if the end has moved to the front of the start
177 if (!m_invalidateIfEmpty && m_end < m_start) {
178 m_end.setPosition(m_start);
179 }
180
181 // fix lookup
182 fixLookup(oldLineRange, lineRange: toLineRange());
183
184 // perhaps need to notify stuff!
185 if (notifyAboutChange && m_feedback) {
186 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: false /* attribute not interesting here */);
187
188 // do this last: may delete this range
189 if (!toRange().isValid()) {
190 m_feedback->rangeInvalid(range: this);
191 } else if (toRange().isEmpty()) {
192 m_feedback->rangeEmpty(range: this);
193 }
194 }
195}
196
197void TextRange::fixLookup(KTextEditor::LineRange oldLineRange, KTextEditor::LineRange lineRange)
198{
199 // nothing changed?
200 if (oldLineRange == lineRange) {
201 return;
202 }
203
204 // now, not both can be invalid
205 Q_ASSERT(oldLineRange.start() >= 0 || lineRange.start() >= 0);
206 Q_ASSERT(oldLineRange.end() >= 0 || lineRange.end() >= 0);
207
208 // get full range
209 int startLineMin = oldLineRange.start();
210 if (oldLineRange.start() == -1 || (lineRange.start() != -1 && lineRange.start() < oldLineRange.start())) {
211 startLineMin = lineRange.start();
212 }
213
214 int endLineMax = oldLineRange.end();
215 if (oldLineRange.end() == -1 || lineRange.end() > oldLineRange.end()) {
216 endLineMax = lineRange.end();
217 }
218
219 // get start block
220 int blockIdx = m_buffer.blockForLine(line: startLineMin);
221 Q_ASSERT(blockIdx >= 0);
222
223 // remove this range from m_ranges
224 auto it = m_buffer.m_blocks.begin() + blockIdx;
225 auto end = m_buffer.m_blocks.end();
226 for (; it != end; ++it) {
227 // either insert or remove range
228 TextBlock *block = *it;
229 if ((lineRange.end() < block->startLine()) || (lineRange.start() >= (block->startLine() + block->lines()))) {
230 block->removeRange(range: this);
231 } else {
232 block->updateRange(range: this);
233 }
234
235 // ok, reached end block
236 if (endLineMax < (block->startLine() + block->lines())) {
237 return;
238 }
239 }
240
241 // we should not be here, really, then endLine is wrong
242 Q_ASSERT(false);
243}
244
245void TextRange::setView(KTextEditor::View *view)
246{
247 // nothing changes, nop
248 if (view == m_view) {
249 return;
250 }
251
252 // remember the new attribute
253 m_view = view;
254
255 // notify buffer about attribute change, it will propagate the changes
256 // notify all views (can be optimized later)
257 if (m_attribute || m_feedback) {
258 m_buffer.notifyAboutRangeChange(view: nullptr, lineRange: toLineRange(), needsRepaint: m_attribute);
259 }
260}
261
262void TextRange::setAttribute(KTextEditor::Attribute::Ptr attribute)
263{
264 // nothing changes, nop, only pointer compare
265 if (attribute == m_attribute) {
266 return;
267 }
268
269 // remember the new attribute
270 m_attribute = attribute;
271
272 // notify buffer about attribute change, it will propagate the changes
273 // notify right view
274 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: true /* even for nullptr attribute, we had before one => repaint */);
275}
276
277void TextRange::setFeedback(KTextEditor::MovingRangeFeedback *feedback)
278{
279 // nothing changes, nop
280 if (feedback == m_feedback) {
281 return;
282 }
283
284 // remember the new feedback object
285 m_feedback = feedback;
286
287 // notify buffer about feedback change, it will propagate the changes
288 // notify right view
289 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: m_attribute);
290}
291
292void TextRange::setAttributeOnlyForViews(bool onlyForViews)
293{
294 // just set the value, no need to trigger updates, printing is not interruptable
295 m_attributeOnlyForViews = onlyForViews;
296}
297
298void TextRange::setZDepth(qreal zDepth)
299{
300 // nothing changes, nop
301 if (zDepth == m_zDepth) {
302 return;
303 }
304
305 // remember the new attribute
306 m_zDepth = zDepth;
307
308 // notify buffer about attribute change, it will propagate the changes
309 if (m_attribute) {
310 m_buffer.notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: m_attribute);
311 }
312}
313
314KTextEditor::Document *Kate::TextRange::document() const
315{
316 return m_buffer.document();
317}
318
319}
320

source code of ktexteditor/src/buffer/katetextrange.cpp