File indexing completed on 2024-04-21 15:03:49

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