File indexing completed on 2024-04-28 07:46:28

0001 /*
0002     SPDX-FileCopyrightText: 2005 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008-2018 Dominik Haumann <dhaumann@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "katelayoutcache.h"
0009 
0010 #include "katedocument.h"
0011 #include "katepartdebug.h"
0012 #include "katerenderer.h"
0013 #include "kateview.h"
0014 
0015 namespace
0016 {
0017 bool enableLayoutCache = false;
0018 
0019 bool lessThan(const KateLineLayoutMap::LineLayoutPair &lhs, const KateLineLayoutMap::LineLayoutPair &rhs)
0020 {
0021     return lhs.first < rhs.first;
0022 }
0023 
0024 }
0025 
0026 // BEGIN KateLineLayoutMap
0027 
0028 void KateLineLayoutMap::clear()
0029 {
0030     m_lineLayouts.clear();
0031 }
0032 
0033 void KateLineLayoutMap::insert(int realLine, std::unique_ptr<KateLineLayout> lineLayoutPtr)
0034 {
0035     auto it = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, nullptr), lessThan);
0036     if (it != m_lineLayouts.end() && (*it) == LineLayoutPair(realLine, nullptr)) {
0037         (*it).second = std::move(lineLayoutPtr);
0038     } else {
0039         it = std::upper_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, nullptr), lessThan);
0040         m_lineLayouts.insert(it, LineLayoutPair(realLine, std::move(lineLayoutPtr)));
0041     }
0042 }
0043 
0044 void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine)
0045 {
0046     auto start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(startRealLine, nullptr), lessThan);
0047     auto end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(endRealLine, nullptr), lessThan);
0048 
0049     while (start != end) {
0050         (*start).second->layoutDirty = true;
0051         ++start;
0052     }
0053 }
0054 
0055 void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount, std::vector<KateTextLayout> &textLayouts)
0056 {
0057     auto start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(fromLine, nullptr), lessThan);
0058     auto end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(toLine, nullptr), lessThan);
0059 
0060     if (shiftAmount != 0) {
0061         for (auto it = end; it != m_lineLayouts.end(); ++it) {
0062             (*it).first += shiftAmount;
0063             (*it).second->setLine((*it).second->line() + shiftAmount);
0064         }
0065 
0066         for (auto it = start; it != end; ++it) {
0067             (*it).second->clear();
0068             for (auto &tl : textLayouts) {
0069                 if (tl.kateLineLayout() == it->second.get()) {
0070                     // Invalidate the layout, this will mark it as dirty
0071                     tl = KateTextLayout::invalid();
0072                 }
0073             }
0074         }
0075 
0076         m_lineLayouts.erase(start, end);
0077     } else {
0078         for (auto it = start; it != end; ++it) {
0079             (*it).second->layoutDirty = true;
0080         }
0081     }
0082 }
0083 
0084 KateLineLayout *KateLineLayoutMap::find(int i)
0085 {
0086     const auto it = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(i, nullptr), lessThan);
0087     if (it != m_lineLayouts.end() && it->first == i) {
0088         return it->second.get();
0089     }
0090     return nullptr;
0091 }
0092 // END KateLineLayoutMap
0093 
0094 KateLayoutCache::KateLayoutCache(KateRenderer *renderer, QObject *parent)
0095     : QObject(parent)
0096     , m_renderer(renderer)
0097 {
0098     Q_ASSERT(m_renderer);
0099 
0100     // connect to all possible editing primitives
0101     connect(m_renderer->doc(), &KTextEditor::Document::lineWrapped, this, &KateLayoutCache::wrapLine);
0102     connect(m_renderer->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateLayoutCache::unwrapLine);
0103     connect(m_renderer->doc(), &KTextEditor::Document::textInserted, this, &KateLayoutCache::insertText);
0104     connect(m_renderer->doc(), &KTextEditor::Document::textRemoved, this, &KateLayoutCache::removeText);
0105 }
0106 
0107 void KateLayoutCache::updateViewCache(const KTextEditor::Cursor startPos, int newViewLineCount, int viewLinesScrolled)
0108 {
0109     // qCDebug(LOG_KTE) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled;
0110 
0111     int oldViewLineCount = m_textLayouts.size();
0112     if (newViewLineCount == -1) {
0113         newViewLineCount = oldViewLineCount;
0114     }
0115 
0116     enableLayoutCache = true;
0117 
0118     int realLine;
0119     if (newViewLineCount == -1) {
0120         realLine = m_renderer->folding().visibleLineToLine(m_renderer->folding().lineToVisibleLine(startPos.line()));
0121     } else {
0122         realLine = m_renderer->folding().visibleLineToLine(startPos.line());
0123     }
0124 
0125     // compute the correct view line
0126     int _viewLine = 0;
0127     if (wrap()) {
0128         if (KateLineLayout *l = line(realLine)) {
0129             Q_ASSERT(l->isValid());
0130             Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor());
0131             bool found = false;
0132             for (; _viewLine < l->viewLineCount(); ++_viewLine) {
0133                 const KateTextLayout &t = l->viewLine(_viewLine);
0134                 if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) {
0135                     found = true;
0136                     break;
0137                 }
0138             }
0139             Q_ASSERT(found);
0140         }
0141     }
0142 
0143     m_startPos = startPos;
0144 
0145     // Move the text layouts if we've just scrolled...
0146     if (viewLinesScrolled != 0) {
0147         // loop backwards if we've just scrolled up...
0148         bool forwards = viewLinesScrolled >= 0 ? true : false;
0149         for (int z = forwards ? 0 : m_textLayouts.size() - 1; forwards ? ((size_t)z < m_textLayouts.size()) : (z >= 0); forwards ? z++ : z--) {
0150             int oldZ = z + viewLinesScrolled;
0151             if (oldZ >= 0 && (size_t)oldZ < m_textLayouts.size()) {
0152                 m_textLayouts[z] = m_textLayouts[oldZ];
0153             }
0154         }
0155     }
0156 
0157     // Resize functionality
0158     if (newViewLineCount > oldViewLineCount) {
0159         m_textLayouts.reserve(newViewLineCount);
0160     } else if (newViewLineCount < oldViewLineCount) {
0161         m_textLayouts.resize(newViewLineCount);
0162     }
0163 
0164     KateLineLayout *l = line(realLine);
0165     for (int i = 0; i < newViewLineCount; ++i) {
0166         if (!l) {
0167             if ((size_t)i < m_textLayouts.size()) {
0168                 if (m_textLayouts[i].isValid()) {
0169                     m_textLayouts[i] = KateTextLayout::invalid();
0170                 }
0171             } else {
0172                 m_textLayouts.push_back(KateTextLayout::invalid());
0173             }
0174             continue;
0175         }
0176 
0177         Q_ASSERT(l->isValid());
0178         Q_ASSERT(_viewLine < l->viewLineCount());
0179 
0180         if ((size_t)i < m_textLayouts.size()) {
0181             bool dirty = false;
0182             if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine
0183                 || (!m_textLayouts[i].isValid() && l->viewLine(_viewLine).isValid())) {
0184                 dirty = true;
0185             }
0186             m_textLayouts[i] = l->viewLine(_viewLine);
0187             if (dirty) {
0188                 m_textLayouts[i].setDirty(true);
0189             }
0190 
0191         } else {
0192             m_textLayouts.push_back(l->viewLine(_viewLine));
0193         }
0194 
0195         // qCDebug(LOG_KTE) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() <<
0196         // ")"; m_textLayouts[i].debugOutput();
0197 
0198         _viewLine++;
0199 
0200         if (_viewLine > l->viewLineCount() - 1) {
0201             int virtualLine = l->virtualLine() + 1;
0202             realLine = m_renderer->folding().visibleLineToLine(virtualLine);
0203             _viewLine = 0;
0204             if (realLine < m_renderer->doc()->lines()) {
0205                 l = line(realLine, virtualLine);
0206             } else {
0207                 l = nullptr;
0208             }
0209         }
0210     }
0211 
0212     enableLayoutCache = false;
0213 }
0214 
0215 KateLineLayout *KateLayoutCache::line(int realLine, int virtualLine)
0216 {
0217     if (auto l = m_lineLayouts.find(realLine)) {
0218         // ensure line is OK
0219         Q_ASSERT(l->line() == realLine);
0220         Q_ASSERT(realLine < m_renderer->doc()->lines());
0221 
0222         if (virtualLine != -1) {
0223             l->setVirtualLine(virtualLine);
0224         }
0225 
0226         if (!l->layout()) {
0227             l->usePlainTextLine = acceptDirtyLayouts();
0228             l->textLine(!acceptDirtyLayouts());
0229             m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
0230         } else if (l->layoutDirty && !acceptDirtyLayouts()) {
0231             // reset textline
0232             l->usePlainTextLine = false;
0233             l->textLine(true);
0234             m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
0235         }
0236 
0237         Q_ASSERT(l->layout() && (!l->layoutDirty || acceptDirtyLayouts()));
0238 
0239         return l;
0240     }
0241 
0242     if (realLine < 0 || realLine >= m_renderer->doc()->lines()) {
0243         return nullptr;
0244     }
0245 
0246     KateLineLayout *l = new KateLineLayout(*m_renderer);
0247     l->setLine(realLine, virtualLine);
0248 
0249     // Mark it dirty, because it may not have the syntax highlighting applied
0250     // mark this here, to allow layoutLine to use plainLines...
0251     if (acceptDirtyLayouts()) {
0252         l->usePlainTextLine = true;
0253     }
0254 
0255     m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
0256     Q_ASSERT(l->isValid());
0257 
0258     if (acceptDirtyLayouts()) {
0259         l->layoutDirty = true;
0260     }
0261 
0262     // transfer ownership to m_lineLayouts
0263     m_lineLayouts.insert(realLine, std::unique_ptr<KateLineLayout>(l));
0264     return l;
0265 }
0266 
0267 KateTextLayout KateLayoutCache::textLayout(const KTextEditor::Cursor realCursor)
0268 {
0269     return textLayout(realCursor.line(), viewLine(realCursor));
0270 }
0271 
0272 KateTextLayout KateLayoutCache::textLayout(uint realLine, int _viewLine)
0273 {
0274     auto l = line(realLine);
0275     if (l && l->isValid()) {
0276         return l->viewLine(_viewLine);
0277     }
0278     return KateTextLayout::invalid();
0279 }
0280 
0281 KateTextLayout &KateLayoutCache::viewLine(int _viewLine)
0282 {
0283     Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size());
0284     return m_textLayouts[_viewLine];
0285 }
0286 
0287 int KateLayoutCache::viewCacheLineCount() const
0288 {
0289     return m_textLayouts.size();
0290 }
0291 
0292 KTextEditor::Cursor KateLayoutCache::viewCacheStart() const
0293 {
0294     return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor();
0295 }
0296 
0297 KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const
0298 {
0299     return !m_textLayouts.empty() ? m_textLayouts.back().end() : KTextEditor::Cursor();
0300 }
0301 
0302 int KateLayoutCache::viewWidth() const
0303 {
0304     return m_viewWidth;
0305 }
0306 
0307 /**
0308  * This returns the view line upon which realCursor is situated.
0309  * The view line is the number of lines in the view from the first line
0310  * The supplied cursor should be in real lines.
0311  */
0312 int KateLayoutCache::viewLine(const KTextEditor::Cursor realCursor)
0313 {
0314     /**
0315      * Make sure cursor column and line is valid
0316      */
0317     if (realCursor.column() < 0 || realCursor.line() < 0 || realCursor.line() > m_renderer->doc()->lines()) {
0318         return 0;
0319     }
0320 
0321     KateLineLayout *thisLine = line(realCursor.line());
0322     if (!thisLine) {
0323         return 0;
0324     }
0325 
0326     for (int i = 0; i < thisLine->viewLineCount(); ++i) {
0327         const KateTextLayout &l = thisLine->viewLine(i);
0328         if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) {
0329             return i;
0330         }
0331     }
0332 
0333     return thisLine->viewLineCount() - 1;
0334 }
0335 
0336 int KateLayoutCache::displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible)
0337 {
0338     if (!virtualCursor.isValid()) {
0339         return -1;
0340     }
0341 
0342     KTextEditor::Cursor work = viewCacheStart();
0343 
0344     // only try this with valid lines!
0345     if (work.isValid()) {
0346         work.setLine(m_renderer->folding().lineToVisibleLine(work.line()));
0347     }
0348 
0349     if (!work.isValid()) {
0350         return virtualCursor.line();
0351     }
0352 
0353     int limit = m_textLayouts.size();
0354 
0355     // Efficient non-word-wrapped path
0356     if (!m_renderer->view()->dynWordWrap()) {
0357         int ret = virtualCursor.line() - work.line();
0358         if (limitToVisible && (ret < 0)) {
0359             return -1;
0360         } else if (limitToVisible && (ret > limit)) {
0361             return -2;
0362         } else {
0363             return ret;
0364         }
0365     }
0366 
0367     if (work == virtualCursor) {
0368         return 0;
0369     }
0370 
0371     int ret = -(int)viewLine(viewCacheStart());
0372     bool forwards = (work < virtualCursor);
0373 
0374     // FIXME switch to using ranges? faster?
0375     if (forwards) {
0376         while (work.line() != virtualCursor.line()) {
0377             ret += viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
0378             work.setLine(work.line() + 1);
0379             if (limitToVisible && ret > limit) {
0380                 return -2;
0381             }
0382         }
0383     } else {
0384         while (work.line() != virtualCursor.line()) {
0385             work.setLine(work.line() - 1);
0386             ret -= viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
0387             if (limitToVisible && ret < 0) {
0388                 return -1;
0389             }
0390         }
0391     }
0392 
0393     // final difference
0394     KTextEditor::Cursor realCursor = virtualCursor;
0395     realCursor.setLine(m_renderer->folding().visibleLineToLine(realCursor.line()));
0396     if (realCursor.column() == -1) {
0397         realCursor.setColumn(m_renderer->doc()->lineLength(realCursor.line()));
0398     }
0399     ret += viewLine(realCursor);
0400 
0401     if (limitToVisible && (ret < 0 || ret > limit)) {
0402         return -1;
0403     }
0404 
0405     return ret;
0406 }
0407 
0408 int KateLayoutCache::lastViewLine(int realLine)
0409 {
0410     if (!m_renderer->view()->dynWordWrap()) {
0411         return 0;
0412     }
0413 
0414     if (KateLineLayout *l = line(realLine)) {
0415         return l->viewLineCount() - 1;
0416     }
0417     return 0;
0418 }
0419 
0420 int KateLayoutCache::viewLineCount(int realLine)
0421 {
0422     return lastViewLine(realLine) + 1;
0423 }
0424 
0425 void KateLayoutCache::viewCacheDebugOutput() const
0426 {
0427     qCDebug(LOG_KTE) << "Printing values for " << m_textLayouts.size() << " lines:";
0428     for (const KateTextLayout &t : std::as_const(m_textLayouts)) {
0429         if (t.isValid()) {
0430             t.debugOutput();
0431         } else {
0432             qCDebug(LOG_KTE) << "Line Invalid.";
0433         }
0434     }
0435 }
0436 
0437 void KateLayoutCache::wrapLine(KTextEditor::Document *, const KTextEditor::Cursor position)
0438 {
0439     m_lineLayouts.slotEditDone(position.line(), position.line() + 1, 1, m_textLayouts);
0440 }
0441 
0442 void KateLayoutCache::unwrapLine(KTextEditor::Document *, int line)
0443 {
0444     m_lineLayouts.slotEditDone(line - 1, line, -1, m_textLayouts);
0445 }
0446 
0447 void KateLayoutCache::insertText(KTextEditor::Document *, const KTextEditor::Cursor position, const QString &)
0448 {
0449     m_lineLayouts.slotEditDone(position.line(), position.line(), 0, m_textLayouts);
0450 }
0451 
0452 void KateLayoutCache::removeText(KTextEditor::Document *, KTextEditor::Range range, const QString &)
0453 {
0454     m_lineLayouts.slotEditDone(range.start().line(), range.start().line(), 0, m_textLayouts);
0455 }
0456 
0457 void KateLayoutCache::clear()
0458 {
0459     m_textLayouts.clear();
0460     m_lineLayouts.clear();
0461     m_startPos = KTextEditor::Cursor(-1, -1);
0462 }
0463 
0464 void KateLayoutCache::setViewWidth(int width)
0465 {
0466     m_viewWidth = width;
0467     m_lineLayouts.clear();
0468     m_textLayouts.clear();
0469     m_startPos = KTextEditor::Cursor(-1, -1);
0470 }
0471 
0472 bool KateLayoutCache::wrap() const
0473 {
0474     return m_wrap;
0475 }
0476 
0477 void KateLayoutCache::setWrap(bool wrap)
0478 {
0479     m_wrap = wrap;
0480     clear();
0481 }
0482 
0483 void KateLayoutCache::relayoutLines(int startRealLine, int endRealLine)
0484 {
0485     if (startRealLine > endRealLine) {
0486         qCWarning(LOG_KTE) << "start" << startRealLine << "before end" << endRealLine;
0487     }
0488 
0489     m_lineLayouts.relayoutLines(startRealLine, endRealLine);
0490 }
0491 
0492 bool KateLayoutCache::acceptDirtyLayouts() const
0493 {
0494     return m_acceptDirtyLayouts;
0495 }
0496 
0497 void KateLayoutCache::setAcceptDirtyLayouts(bool accept)
0498 {
0499     m_acceptDirtyLayouts = accept;
0500 }