File indexing completed on 2024-04-21 03:57:19

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 }