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 }