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 | |
14 | namespace Kate |
15 | { |
16 | 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 | |
34 | 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 | |
53 | void TextRange::(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 | |
70 | KTextEditor::MovingRange::InsertBehaviors TextRange::() 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 | |
85 | void TextRange::(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 | |
101 | void TextRange::(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 | |
150 | void TextRange::(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute) |
151 | { |
152 | // FIXME: optimize |
153 | setRange(range); |
154 | setAttribute(attribute); |
155 | } |
156 | |
157 | void TextRange::(KTextEditor::Range range, KTextEditor::Attribute::Ptr attribute, qreal zDepth) |
158 | { |
159 | // FIXME: optimize |
160 | setRange(range); |
161 | setAttribute(attribute); |
162 | setZDepth(zDepth); |
163 | } |
164 | |
165 | void TextRange::(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 | |
197 | void TextRange::(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 | |
245 | void TextRange::(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 | |
262 | void TextRange::(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 | |
277 | void TextRange::(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 | |
292 | void TextRange::(bool onlyForViews) |
293 | { |
294 | // just set the value, no need to trigger updates, printing is not interruptable |
295 | m_attributeOnlyForViews = onlyForViews; |
296 | } |
297 | |
298 | void TextRange::(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 | |
314 | KTextEditor::Document *Kate::TextRange::() const |
315 | { |
316 | return m_buffer.document(); |
317 | } |
318 | |
319 | } |
320 | |