File indexing completed on 2024-09-29 06:34:00
0001 /* 0002 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org> 0003 0004 Based on code of the SmartCursor/Range by: 0005 SPDX-FileCopyrightText: 2003-2005 Hamish Rodda <rodda@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "katetextrange.h" 0011 #include "katedocument.h" 0012 #include "katetextbuffer.h" 0013 0014 namespace Kate 0015 { 0016 TextRange::TextRange(TextBuffer &buffer, KTextEditor::Range range, InsertBehaviors insertBehavior, EmptyBehavior emptyBehavior) 0017 : m_buffer(buffer) 0018 , m_start(buffer, this, range.start(), (insertBehavior & ExpandLeft) ? Kate::TextCursor::StayOnInsert : Kate::TextCursor::MoveOnInsert) 0019 , m_end(buffer, this, range.end(), (insertBehavior & ExpandRight) ? Kate::TextCursor::MoveOnInsert : Kate::TextCursor::StayOnInsert) 0020 , m_view(nullptr) 0021 , m_feedback(nullptr) 0022 , m_zDepth(0.0) 0023 , m_attributeOnlyForViews(false) 0024 , m_invalidateIfEmpty(emptyBehavior == InvalidateIfEmpty) 0025 { 0026 // remember this range in buffer 0027 m_buffer.m_ranges.insert(this); 0028 0029 // check if range now invalid, there can happen no feedback, as m_feedback == 0 0030 // only place where KTextEditor::LineRange::invalid() for old range makes sense, as we were yet not registered! 0031 checkValidity(KTextEditor::LineRange::invalid()); 0032 } 0033 0034 TextRange::~TextRange() 0035 { 0036 // reset feedback, don't want feedback during destruction 0037 m_feedback = nullptr; 0038 0039 // remove range from m_ranges 0040 fixLookup(toLineRange(), KTextEditor::LineRange::invalid()); 0041 0042 // remove this range from the buffer 0043 m_buffer.m_ranges.remove(this); 0044 0045 // trigger update, if we have attribute 0046 // notify right view 0047 // here we can ignore feedback, even with feedback, we want none if the range is deleted! 0048 if (m_attribute) { 0049 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), true /* we have a attribute */); 0050 } 0051 } 0052 0053 void TextRange::setInsertBehaviors(InsertBehaviors _insertBehaviors) 0054 { 0055 // nothing to do? 0056 if (_insertBehaviors == insertBehaviors()) { 0057 return; 0058 } 0059 0060 // modify cursors 0061 m_start.setInsertBehavior((_insertBehaviors & ExpandLeft) ? KTextEditor::MovingCursor::StayOnInsert : KTextEditor::MovingCursor::MoveOnInsert); 0062 m_end.setInsertBehavior((_insertBehaviors & ExpandRight) ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert); 0063 0064 // notify world 0065 if (m_attribute || m_feedback) { 0066 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), true /* we have a attribute */); 0067 } 0068 } 0069 0070 KTextEditor::MovingRange::InsertBehaviors TextRange::insertBehaviors() const 0071 { 0072 InsertBehaviors behaviors = DoNotExpand; 0073 0074 if (m_start.insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) { 0075 behaviors |= ExpandLeft; 0076 } 0077 0078 if (m_end.insertBehavior() == KTextEditor::MovingCursor::MoveOnInsert) { 0079 behaviors |= ExpandRight; 0080 } 0081 0082 return behaviors; 0083 } 0084 0085 void TextRange::setEmptyBehavior(EmptyBehavior emptyBehavior) 0086 { 0087 // nothing to do? 0088 if (m_invalidateIfEmpty == (emptyBehavior == InvalidateIfEmpty)) { 0089 return; 0090 } 0091 0092 // remember value 0093 m_invalidateIfEmpty = (emptyBehavior == InvalidateIfEmpty); 0094 0095 // invalidate range? 0096 if (endInternal() <= startInternal()) { 0097 setRange(KTextEditor::Range::invalid()); 0098 } 0099 } 0100 0101 void TextRange::setRange(KTextEditor::Range range) 0102 { 0103 // avoid work if nothing changed! 0104 if (range == toRange()) { 0105 return; 0106 } 0107 0108 // remember old line range 0109 const auto oldLineRange = toLineRange(); 0110 0111 // change start and end cursor 0112 m_start.setPosition(range.start()); 0113 m_end.setPosition(range.end()); 0114 0115 // check if range now invalid, don't emit feedback here, will be handled below 0116 // otherwise you can't delete ranges in feedback! 0117 checkValidity(oldLineRange, false); 0118 0119 // no attribute or feedback set, be done 0120 if (!m_attribute && !m_feedback) { 0121 return; 0122 } 0123 0124 // get full range 0125 int startLineMin = oldLineRange.start(); 0126 if (oldLineRange.start() == -1 || (m_start.lineInternal() != -1 && m_start.lineInternal() < oldLineRange.start())) { 0127 startLineMin = m_start.line(); 0128 } 0129 0130 int endLineMax = oldLineRange.end(); 0131 if (oldLineRange.end() == -1 || m_end.lineInternal() > oldLineRange.end()) { 0132 endLineMax = m_end.lineInternal(); 0133 } 0134 0135 // notify buffer about attribute change, it will propagate the changes 0136 // notify right view 0137 m_buffer.notifyAboutRangeChange(m_view, {startLineMin, endLineMax}, m_attribute); 0138 0139 // perhaps need to notify stuff! 0140 if (m_feedback) { 0141 // do this last: may delete this range 0142 if (!toRange().isValid()) { 0143 m_feedback->rangeInvalid(this); 0144 } else if (toRange().isEmpty()) { 0145 m_feedback->rangeEmpty(this); 0146 } 0147 } 0148 } 0149 0150 void TextRange::checkValidity(KTextEditor::LineRange oldLineRange, bool notifyAboutChange) 0151 { 0152 // in any case: reset the flag, to avoid multiple runs 0153 m_isCheckValidityRequired = false; 0154 0155 // check if any cursor is invalid or the range is zero size and it should be invalidated then 0156 if (!m_start.isValid() || !m_end.isValid() || (m_invalidateIfEmpty && m_end <= m_start)) { 0157 m_start.setPosition(-1, -1); 0158 m_end.setPosition(-1, -1); 0159 } 0160 0161 // for ranges which are allowed to become empty, normalize them, if the end has moved to the front of the start 0162 if (!m_invalidateIfEmpty && m_end < m_start) { 0163 m_end.setPosition(m_start); 0164 } 0165 0166 // fix lookup 0167 fixLookup(oldLineRange, toLineRange()); 0168 0169 // perhaps need to notify stuff! 0170 if (notifyAboutChange && m_feedback) { 0171 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), false /* attribute not interesting here */); 0172 0173 // do this last: may delete this range 0174 if (!toRange().isValid()) { 0175 m_feedback->rangeInvalid(this); 0176 } else if (toRange().isEmpty()) { 0177 m_feedback->rangeEmpty(this); 0178 } 0179 } 0180 } 0181 0182 void TextRange::fixLookup(KTextEditor::LineRange oldLineRange, KTextEditor::LineRange lineRange) 0183 { 0184 // nothing changed? 0185 if (oldLineRange == lineRange) { 0186 return; 0187 } 0188 0189 // now, not both can be invalid 0190 Q_ASSERT(oldLineRange.start() >= 0 || lineRange.start() >= 0); 0191 Q_ASSERT(oldLineRange.end() >= 0 || lineRange.end() >= 0); 0192 0193 // get full range 0194 int startLineMin = oldLineRange.start(); 0195 if (oldLineRange.start() == -1 || (lineRange.start() != -1 && lineRange.start() < oldLineRange.start())) { 0196 startLineMin = lineRange.start(); 0197 } 0198 0199 int endLineMax = oldLineRange.end(); 0200 if (oldLineRange.end() == -1 || lineRange.end() > oldLineRange.end()) { 0201 endLineMax = lineRange.end(); 0202 } 0203 0204 // get start block 0205 int blockIdx = m_buffer.blockForLine(startLineMin); 0206 Q_ASSERT(blockIdx >= 0); 0207 0208 // remove this range from m_ranges 0209 auto it = m_buffer.m_blocks.begin() + blockIdx; 0210 auto end = m_buffer.m_blocks.end(); 0211 for (; it != end; ++it) { 0212 // either insert or remove range 0213 TextBlock *block = *it; 0214 if ((lineRange.end() < block->startLine()) || (lineRange.start() >= (block->startLine() + block->lines()))) { 0215 block->removeRange(this); 0216 } else { 0217 block->updateRange(this); 0218 } 0219 0220 // ok, reached end block 0221 if (endLineMax < (block->startLine() + block->lines())) { 0222 return; 0223 } 0224 } 0225 0226 // we should not be here, really, then endLine is wrong 0227 Q_ASSERT(false); 0228 } 0229 0230 void TextRange::setView(KTextEditor::View *view) 0231 { 0232 // nothing changes, nop 0233 if (view == m_view) { 0234 return; 0235 } 0236 0237 // remember the new attribute 0238 m_view = view; 0239 0240 // notify buffer about attribute change, it will propagate the changes 0241 // notify all views (can be optimized later) 0242 if (m_attribute || m_feedback) { 0243 m_buffer.notifyAboutRangeChange(nullptr, toLineRange(), m_attribute); 0244 } 0245 } 0246 0247 void TextRange::setAttribute(KTextEditor::Attribute::Ptr attribute) 0248 { 0249 // nothing changes, nop, only pointer compare 0250 if (attribute == m_attribute) { 0251 return; 0252 } 0253 0254 // remember the new attribute 0255 m_attribute = attribute; 0256 0257 // notify buffer about attribute change, it will propagate the changes 0258 // notify right view 0259 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), true /* even for nullptr attribute, we had before one => repaint */); 0260 } 0261 0262 void TextRange::setFeedback(KTextEditor::MovingRangeFeedback *feedback) 0263 { 0264 // nothing changes, nop 0265 if (feedback == m_feedback) { 0266 return; 0267 } 0268 0269 // remember the new feedback object 0270 m_feedback = feedback; 0271 0272 // notify buffer about feedback change, it will propagate the changes 0273 // notify right view 0274 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), m_attribute); 0275 } 0276 0277 void TextRange::setAttributeOnlyForViews(bool onlyForViews) 0278 { 0279 // just set the value, no need to trigger updates, printing is not interruptable 0280 m_attributeOnlyForViews = onlyForViews; 0281 } 0282 0283 void TextRange::setZDepth(qreal zDepth) 0284 { 0285 // nothing changes, nop 0286 if (zDepth == m_zDepth) { 0287 return; 0288 } 0289 0290 // remember the new attribute 0291 m_zDepth = zDepth; 0292 0293 // notify buffer about attribute change, it will propagate the changes 0294 if (m_attribute) { 0295 m_buffer.notifyAboutRangeChange(m_view, toLineRange(), m_attribute); 0296 } 0297 } 0298 0299 KTextEditor::Document *Kate::TextRange::document() const 0300 { 0301 return m_buffer.document(); 0302 } 0303 0304 }