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 // check if range now invalid, there can happen no feedback, as m_feedback == 0
27 // only place where KTextEditor::LineRange::invalid() for old range makes sense, as we were yet not registered!
28 checkValidity();
29
30 // Add the range if it is multiline
31 if (spansMultipleBlocks()) {
32 m_buffer->addMultilineRange(range: this);
33 }
34}
35
36TextRange::~TextRange()
37{
38 if (!m_buffer) {
39 return;
40 }
41 // reset feedback, don't want feedback during destruction
42 const bool hadFeedBack = m_feedback != nullptr;
43 const bool hadDynamicAttr = m_attribute
44 && (m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateCaretIn) || m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateMouseIn));
45 const bool notifyDeletion = hadFeedBack || hadDynamicAttr;
46 m_feedback = nullptr;
47
48 // remove range from cached multiline ranges
49 const auto lineRange = toLineRange();
50 if (lineRange.isValid() && spansMultipleBlocks()) {
51 m_buffer->removeMultilineRange(range: this);
52 }
53
54 // trigger update, if we have attribute
55 // notify right view
56 // here we can ignore feedback, even with feedback, we want none if the range is deleted!
57 if (m_attribute || notifyDeletion) {
58 m_buffer->notifyAboutRangeChange(view: m_view, lineRange, /*needsRepaint=*/m_attribute != nullptr, /*deletedRange=*/notifyDeletion ? this : nullptr);
59 }
60}
61
62void TextRange::setInsertBehaviors(InsertBehaviors _insertBehaviors)
63{
64 // nothing to do?
65 if (_insertBehaviors == insertBehaviors() || !m_buffer) {
66 return;
67 }
68
69 // modify cursors
70 m_start.setInsertBehavior((_insertBehaviors & ExpandLeft) ? KTextEditor::MovingCursor::StayOnInsert : KTextEditor::MovingCursor::MoveOnInsert);
71 m_end.setInsertBehavior((_insertBehaviors & ExpandRight) ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert);
72
73 // notify world
74 if (m_attribute || m_feedback) {
75 m_buffer->notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: true /* we have a attribute */);
76 }
77}
78
79KTextEditor::MovingRange::InsertBehaviors TextRange::insertBehaviors() const
80{
81 InsertBehaviors behaviors = DoNotExpand;
82
83 if (m_start.insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) {
84 behaviors |= ExpandLeft;
85 }
86
87 if (m_end.insertBehavior() == KTextEditor::MovingCursor::MoveOnInsert) {
88 behaviors |= ExpandRight;
89 }
90
91 return behaviors;
92}
93
94void TextRange::setEmptyBehavior(EmptyBehavior emptyBehavior)
95{
96 // nothing to do?
97 if (m_invalidateIfEmpty == (emptyBehavior == InvalidateIfEmpty)) {
98 return;
99 }
100
101 // remember value
102 m_invalidateIfEmpty = (emptyBehavior == InvalidateIfEmpty);
103
104 checkValidity();
105}
106
107void TextRange::setRange(KTextEditor::Range range)
108{
109 // avoid work if nothing changed!
110 if (!m_buffer || range == toRange()) {
111 return;
112 }
113
114 // remember old line range
115 const auto oldLineRange = toLineRange();
116 const bool oldSpannedMultipleBlocks = spansMultipleBlocks();
117
118 // change start and end cursor
119 m_start.setPosition(range.start());
120 m_end.setPosition(range.end());
121
122 const bool newSpansMultipleBlocks = spansMultipleBlocks();
123 if (oldSpannedMultipleBlocks != newSpansMultipleBlocks) {
124 if (oldSpannedMultipleBlocks) {
125 m_buffer->removeMultilineRange(range: this);
126 } else {
127 m_buffer->addMultilineRange(range: this);
128 }
129 }
130
131 // check if range now invalid, don't emit feedback here, will be handled below
132 // otherwise you can't delete ranges in feedback!
133 checkValidity(notifyAboutChange: false);
134
135 // no attribute or feedback set, be done
136 if (!m_attribute && !m_feedback) {
137 return;
138 }
139
140 // get full range
141 int startLineMin = oldLineRange.start();
142 if (oldLineRange.start() == -1 || (m_start.lineInternal() != -1 && m_start.lineInternal() < oldLineRange.start())) {
143 startLineMin = m_start.line();
144 }
145
146 int endLineMax = oldLineRange.end();
147 if (oldLineRange.end() == -1 || m_end.lineInternal() > oldLineRange.end()) {
148 endLineMax = m_end.lineInternal();
149 }
150
151 // notify buffer about attribute change, it will propagate the changes
152 // notify right view
153 m_buffer->notifyAboutRangeChange(view: m_view, lineRange: {startLineMin, endLineMax}, needsRepaint: m_attribute);
154
155 // perhaps need to notify stuff!
156 if (m_feedback) {
157 // do this last: may delete this range
158 if (!toRange().isValid()) {
159 m_feedback->rangeInvalid(range: this);
160 } else if (toRange().isEmpty()) {
161 m_feedback->rangeEmpty(range: this);
162 }
163 }
164}
165
166void TextRange::setRange(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute)
167{
168 // FIXME: optimize
169 setRange(range);
170 setAttribute(attribute);
171}
172
173void TextRange::setRange(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute, qreal zDepth)
174{
175 // FIXME: optimize
176 setRange(range);
177 setAttribute(attribute);
178 setZDepth(zDepth);
179}
180
181void TextRange::checkValidity(bool notifyAboutChange)
182{
183 // in any case: reset the flag, to avoid multiple runs
184 m_isCheckValidityRequired = false;
185
186 KTextEditor::Cursor end(m_end.lineInternal(), m_end.columnInternal());
187 KTextEditor::Cursor start(m_start.lineInternal(), m_start.columnInternal());
188
189 // check if any cursor is invalid or the range is zero size and it should be invalidated then
190 if (!start.isValid() || !end.isValid() || (m_invalidateIfEmpty && end <= start)) {
191 m_start.setPosition(line: -1, column: -1);
192 m_end.setPosition(line: -1, column: -1);
193 start = end = KTextEditor::Cursor::invalid();
194 }
195
196 // for ranges which are allowed to become empty, normalize them, if the end has moved to the front of the start
197 if (!m_invalidateIfEmpty && end < start) {
198 m_end.setPosition(m_start);
199 }
200
201 // perhaps need to notify stuff!
202 if (notifyAboutChange && m_feedback && m_buffer) {
203 m_buffer->notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: false /* attribute not interesting here */);
204
205 // do this last: may delete this range
206 if (!toRange().isValid()) {
207 m_feedback->rangeInvalid(range: this);
208 } else if (toRange().isEmpty()) {
209 m_feedback->rangeEmpty(range: this);
210 }
211 }
212}
213
214void TextRange::setView(KTextEditor::View *view)
215{
216 // nothing changes, nop
217 if (view == m_view || !m_buffer) {
218 return;
219 }
220
221 // remember the new attribute
222 m_view = view;
223
224 // notify buffer about attribute change, it will propagate the changes
225 // notify all views (can be optimized later)
226 if (m_attribute || m_feedback) {
227 m_buffer->notifyAboutRangeChange(view: nullptr, lineRange: toLineRange(), needsRepaint: m_attribute);
228 }
229}
230
231void TextRange::setAttribute(KTextEditor::Attribute::Ptr attribute)
232{
233 // nothing changes, nop, only pointer compare
234 if (attribute == m_attribute || !m_buffer) {
235 return;
236 }
237
238 // remember the new attribute
239 const bool hadDynamicAttr = m_attribute
240 && (m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateCaretIn) || m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateMouseIn));
241
242 m_attribute = attribute;
243
244 const bool stillHasDynAttr = m_attribute
245 && (m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateCaretIn) || m_attribute->dynamicAttribute(type: KTextEditor::Attribute::ActivateMouseIn));
246 const bool notifyDeletion = hadDynamicAttr && !stillHasDynAttr;
247
248 // notify buffer about attribute change, it will propagate the changes
249 // notify right view
250 // if dyn attr was cleared, then notify about deleted range now because if this range is deleted
251 // its deletion will not be notified because it will have no dyn attr.
252 m_buffer->notifyAboutRangeChange(view: m_view,
253 lineRange: toLineRange(),
254 needsRepaint: true /* even for nullptr attribute, we had before one => repaint */,
255 deletedRange: notifyDeletion ? this : nullptr);
256}
257
258void TextRange::setFeedback(KTextEditor::MovingRangeFeedback *feedback)
259{
260 // nothing changes, nop
261 if (feedback == m_feedback || !m_buffer) {
262 return;
263 }
264
265 // remember the new feedback object
266 const bool feedbackCleared = m_feedback && !feedback;
267 m_feedback = feedback;
268
269 // notify buffer about feedback change, it will propagate the changes
270 // notify right view
271 // if feedback was set to null, then notify about deleted range now because if this range is deleted
272 // its deletion will not be notified because it has no feedback.
273 m_buffer->notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: m_attribute, deletedRange: feedbackCleared ? this : nullptr);
274}
275
276void TextRange::setAttributeOnlyForViews(bool onlyForViews)
277{
278 // just set the value, no need to trigger updates, printing is not interruptable
279 m_attributeOnlyForViews = onlyForViews;
280}
281
282void TextRange::setZDepth(qreal zDepth)
283{
284 // nothing changes, nop
285 if (zDepth == m_zDepth || !m_buffer) {
286 return;
287 }
288
289 // remember the new attribute
290 m_zDepth = zDepth;
291
292 // notify buffer about attribute change, it will propagate the changes
293 if (m_attribute) {
294 m_buffer->notifyAboutRangeChange(view: m_view, lineRange: toLineRange(), needsRepaint: m_attribute);
295 }
296}
297
298KTextEditor::Document *Kate::TextRange::document() const
299{
300 return m_buffer ? m_buffer->document() : nullptr;
301}
302
303}
304

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