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"