1/*
2 SPDX-FileCopyrightText: 2005 Hamish Rodda <rodda@kde.org>
3 SPDX-FileCopyrightText: 2008-2018 Dominik Haumann <dhaumann@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "katelayoutcache.h"
9
10#include "katedocument.h"
11#include "katepartdebug.h"
12#include "katerenderer.h"
13#include "kateview.h"
14
15namespace
16{
17bool enableLayoutCache = false;
18
19bool lessThan(const KateLineLayoutMap::LineLayoutPair &lhs, const KateLineLayoutMap::LineLayoutPair &rhs)
20{
21 return lhs.first < rhs.first;
22}
23
24}
25
26// BEGIN KateLineLayoutMap
27
28void KateLineLayoutMap::clear()
29{
30 m_lineLayouts.clear();
31}
32
33void KateLineLayoutMap::insert(int realLine, std::unique_ptr<KateLineLayout> lineLayoutPtr)
34{
35 auto it = std::lower_bound(first: m_lineLayouts.begin(), last: m_lineLayouts.end(), val: LineLayoutPair(realLine, nullptr), comp: lessThan);
36 if (it != m_lineLayouts.end() && (*it) == LineLayoutPair(realLine, nullptr)) {
37 (*it).second = std::move(lineLayoutPtr);
38 } else {
39 it = std::upper_bound(first: m_lineLayouts.begin(), last: m_lineLayouts.end(), val: LineLayoutPair(realLine, nullptr), comp: lessThan);
40 m_lineLayouts.insert(position: it, x: LineLayoutPair(realLine, std::move(lineLayoutPtr)));
41 }
42}
43
44void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine)
45{
46 auto start = std::lower_bound(first: m_lineLayouts.begin(), last: m_lineLayouts.end(), val: LineLayoutPair(startRealLine, nullptr), comp: lessThan);
47 auto end = std::upper_bound(first: start, last: m_lineLayouts.end(), val: LineLayoutPair(endRealLine, nullptr), comp: lessThan);
48
49 while (start != end) {
50 (*start).second->layoutDirty = true;
51 ++start;
52 }
53}
54
55void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount, std::vector<KateTextLayout> &textLayouts)
56{
57 auto start = std::lower_bound(first: m_lineLayouts.begin(), last: m_lineLayouts.end(), val: LineLayoutPair(fromLine, nullptr), comp: lessThan);
58 auto end = std::upper_bound(first: start, last: m_lineLayouts.end(), val: LineLayoutPair(toLine, nullptr), comp: lessThan);
59
60 if (shiftAmount != 0) {
61 for (auto it = end; it != m_lineLayouts.end(); ++it) {
62 (*it).first += shiftAmount;
63 (*it).second->setLine(line: (*it).second->line() + shiftAmount);
64 }
65
66 for (auto it = start; it != end; ++it) {
67 (*it).second->clear();
68 for (auto &tl : textLayouts) {
69 if (tl.kateLineLayout() == it->second.get()) {
70 // Invalidate the layout, this will mark it as dirty
71 tl = KateTextLayout::invalid();
72 }
73 }
74 }
75
76 m_lineLayouts.erase(first: start, last: end);
77 } else {
78 for (auto it = start; it != end; ++it) {
79 (*it).second->layoutDirty = true;
80 }
81 }
82}
83
84KateLineLayout *KateLineLayoutMap::find(int i)
85{
86 const auto it = std::lower_bound(first: m_lineLayouts.begin(), last: m_lineLayouts.end(), val: LineLayoutPair(i, nullptr), comp: lessThan);
87 if (it != m_lineLayouts.end() && it->first == i) {
88 return it->second.get();
89 }
90 return nullptr;
91}
92// END KateLineLayoutMap
93
94KateLayoutCache::KateLayoutCache(KateRenderer *renderer, QObject *parent)
95 : QObject(parent)
96 , m_renderer(renderer)
97{
98 Q_ASSERT(m_renderer);
99
100 // connect to all possible editing primitives
101 connect(sender: m_renderer->doc(), signal: &KTextEditor::Document::lineWrapped, context: this, slot: &KateLayoutCache::wrapLine);
102 connect(sender: m_renderer->doc(), signal: &KTextEditor::Document::lineUnwrapped, context: this, slot: &KateLayoutCache::unwrapLine);
103 connect(sender: m_renderer->doc(), signal: &KTextEditor::Document::textInserted, context: this, slot: &KateLayoutCache::insertText);
104 connect(sender: m_renderer->doc(), signal: &KTextEditor::Document::textRemoved, context: this, slot: &KateLayoutCache::removeText);
105}
106
107void KateLayoutCache::updateViewCache(const KTextEditor::Cursor startPos, int newViewLineCount, int viewLinesScrolled)
108{
109 // qCDebug(LOG_KTE) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled;
110
111 int oldViewLineCount = m_textLayouts.size();
112 if (newViewLineCount == -1) {
113 newViewLineCount = oldViewLineCount;
114 }
115
116 enableLayoutCache = true;
117
118 int realLine;
119 if (newViewLineCount == -1) {
120 realLine = m_renderer->folding().visibleLineToLine(visibleLine: m_renderer->folding().lineToVisibleLine(line: startPos.line()));
121 } else {
122 realLine = m_renderer->folding().visibleLineToLine(visibleLine: startPos.line());
123 }
124
125 // compute the correct view line
126 int _viewLine = 0;
127 if (wrap()) {
128 if (KateLineLayout *l = line(realLine)) {
129 Q_ASSERT(l->isValid());
130 Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor());
131 bool found = false;
132 for (; _viewLine < l->viewLineCount(); ++_viewLine) {
133 const KateTextLayout &t = l->viewLine(viewLine: _viewLine);
134 if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) {
135 found = true;
136 break;
137 }
138 }
139 Q_ASSERT(found);
140 }
141 }
142
143 m_startPos = startPos;
144
145 // Move the text layouts if we've just scrolled...
146 if (viewLinesScrolled != 0) {
147 // loop backwards if we've just scrolled up...
148 bool forwards = viewLinesScrolled >= 0 ? true : false;
149 for (int z = forwards ? 0 : m_textLayouts.size() - 1; forwards ? ((size_t)z < m_textLayouts.size()) : (z >= 0); forwards ? z++ : z--) {
150 int oldZ = z + viewLinesScrolled;
151 if (oldZ >= 0 && (size_t)oldZ < m_textLayouts.size()) {
152 m_textLayouts[z] = m_textLayouts[oldZ];
153 }
154 }
155 }
156
157 // Resize functionality
158 if (newViewLineCount > oldViewLineCount) {
159 m_textLayouts.reserve(n: newViewLineCount);
160 } else if (newViewLineCount < oldViewLineCount) {
161 m_textLayouts.resize(new_size: newViewLineCount);
162 }
163
164 KateLineLayout *l = line(realLine);
165 for (int i = 0; i < newViewLineCount; ++i) {
166 if (!l) {
167 if ((size_t)i < m_textLayouts.size()) {
168 if (m_textLayouts[i].isValid()) {
169 m_textLayouts[i] = KateTextLayout::invalid();
170 }
171 } else {
172 m_textLayouts.push_back(x: KateTextLayout::invalid());
173 }
174 continue;
175 }
176
177 Q_ASSERT(l->isValid());
178 Q_ASSERT(_viewLine < l->viewLineCount());
179
180 if ((size_t)i < m_textLayouts.size()) {
181 bool dirty = false;
182 if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine
183 || (!m_textLayouts[i].isValid() && l->viewLine(viewLine: _viewLine).isValid())) {
184 dirty = true;
185 }
186 m_textLayouts[i] = l->viewLine(viewLine: _viewLine);
187 if (dirty) {
188 m_textLayouts[i].setDirty(true);
189 }
190
191 } else {
192 m_textLayouts.push_back(x: l->viewLine(viewLine: _viewLine));
193 }
194
195 // qCDebug(LOG_KTE) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() <<
196 // ")"; m_textLayouts[i].debugOutput();
197
198 _viewLine++;
199
200 if (_viewLine > l->viewLineCount() - 1) {
201 int virtualLine = l->virtualLine() + 1;
202 realLine = m_renderer->folding().visibleLineToLine(visibleLine: virtualLine);
203 _viewLine = 0;
204 if (realLine < m_renderer->doc()->lines()) {
205 l = line(realLine, virtualLine);
206 } else {
207 l = nullptr;
208 }
209 }
210 }
211
212 enableLayoutCache = false;
213}
214
215KateLineLayout *KateLayoutCache::line(int realLine, int virtualLine)
216{
217 if (auto l = m_lineLayouts.find(i: realLine)) {
218 // ensure line is OK
219 Q_ASSERT(l->line() == realLine);
220 Q_ASSERT(realLine < m_renderer->doc()->lines());
221
222 if (virtualLine != -1) {
223 l->setVirtualLine(virtualLine);
224 }
225
226 if (!l->layout()) {
227 l->usePlainTextLine = acceptDirtyLayouts();
228 l->textLine(forceReload: !acceptDirtyLayouts());
229 m_renderer->layoutLine(line: l, maxwidth: wrap() ? m_viewWidth : -1, cacheLayout: enableLayoutCache);
230 } else if (l->layoutDirty && !acceptDirtyLayouts()) {
231 // reset textline
232 l->usePlainTextLine = false;
233 l->textLine(forceReload: true);
234 m_renderer->layoutLine(line: l, maxwidth: wrap() ? m_viewWidth : -1, cacheLayout: enableLayoutCache);
235 }
236
237 Q_ASSERT(l->layout() && (!l->layoutDirty || acceptDirtyLayouts()));
238
239 return l;
240 }
241
242 if (realLine < 0 || realLine >= m_renderer->doc()->lines()) {
243 return nullptr;
244 }
245
246 KateLineLayout *l = new KateLineLayout(*m_renderer);
247 l->setLine(line: realLine, virtualLine);
248
249 // Mark it dirty, because it may not have the syntax highlighting applied
250 // mark this here, to allow layoutLine to use plainLines...
251 if (acceptDirtyLayouts()) {
252 l->usePlainTextLine = true;
253 }
254
255 m_renderer->layoutLine(line: l, maxwidth: wrap() ? m_viewWidth : -1, cacheLayout: enableLayoutCache);
256 Q_ASSERT(l->isValid());
257
258 if (acceptDirtyLayouts()) {
259 l->layoutDirty = true;
260 }
261
262 // transfer ownership to m_lineLayouts
263 m_lineLayouts.insert(realLine, lineLayoutPtr: std::unique_ptr<KateLineLayout>(l));
264 return l;
265}
266
267KateTextLayout KateLayoutCache::textLayout(const KTextEditor::Cursor realCursor)
268{
269 return textLayout(realLine: realCursor.line(), viewLine: viewLine(realCursor));
270}
271
272KateTextLayout KateLayoutCache::textLayout(uint realLine, int _viewLine)
273{
274 auto l = line(realLine);
275 if (l && l->isValid()) {
276 return l->viewLine(viewLine: _viewLine);
277 }
278 return KateTextLayout::invalid();
279}
280
281KateTextLayout &KateLayoutCache::viewLine(int _viewLine)
282{
283 Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size());
284 return m_textLayouts[_viewLine];
285}
286
287int KateLayoutCache::viewCacheLineCount() const
288{
289 return m_textLayouts.size();
290}
291
292KTextEditor::Cursor KateLayoutCache::viewCacheStart() const
293{
294 return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor();
295}
296
297KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const
298{
299 return !m_textLayouts.empty() ? m_textLayouts.back().end() : KTextEditor::Cursor();
300}
301
302int KateLayoutCache::viewWidth() const
303{
304 return m_viewWidth;
305}
306
307/**
308 * This returns the view line upon which realCursor is situated.
309 * The view line is the number of lines in the view from the first line
310 * The supplied cursor should be in real lines.
311 */
312int KateLayoutCache::viewLine(const KTextEditor::Cursor realCursor)
313{
314 /**
315 * Make sure cursor column and line is valid
316 */
317 if (realCursor.column() < 0 || realCursor.line() < 0 || realCursor.line() > m_renderer->doc()->lines()) {
318 return 0;
319 }
320
321 KateLineLayout *thisLine = line(realLine: realCursor.line());
322 if (!thisLine) {
323 return 0;
324 }
325
326 for (int i = 0; i < thisLine->viewLineCount(); ++i) {
327 const KateTextLayout &l = thisLine->viewLine(viewLine: i);
328 if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) {
329 return i;
330 }
331 }
332
333 return thisLine->viewLineCount() - 1;
334}
335
336int KateLayoutCache::displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible)
337{
338 if (!virtualCursor.isValid()) {
339 return -1;
340 }
341
342 KTextEditor::Cursor work = viewCacheStart();
343
344 // only try this with valid lines!
345 if (work.isValid()) {
346 work.setLine(m_renderer->folding().lineToVisibleLine(line: work.line()));
347 }
348
349 if (!work.isValid()) {
350 return virtualCursor.line();
351 }
352
353 int limit = m_textLayouts.size();
354
355 // Efficient non-word-wrapped path
356 if (!m_renderer->view()->dynWordWrap()) {
357 int ret = virtualCursor.line() - work.line();
358 if (limitToVisible && (ret < 0)) {
359 return -1;
360 } else if (limitToVisible && (ret > limit)) {
361 return -2;
362 } else {
363 return ret;
364 }
365 }
366
367 if (work == virtualCursor) {
368 return 0;
369 }
370
371 int ret = -(int)viewLine(realCursor: viewCacheStart());
372 bool forwards = (work < virtualCursor);
373
374 // FIXME switch to using ranges? faster?
375 if (forwards) {
376 while (work.line() != virtualCursor.line()) {
377 ret += viewLineCount(realLine: m_renderer->folding().visibleLineToLine(visibleLine: work.line()));
378 work.setLine(work.line() + 1);
379 if (limitToVisible && ret > limit) {
380 return -2;
381 }
382 }
383 } else {
384 while (work.line() != virtualCursor.line()) {
385 work.setLine(work.line() - 1);
386 ret -= viewLineCount(realLine: m_renderer->folding().visibleLineToLine(visibleLine: work.line()));
387 if (limitToVisible && ret < 0) {
388 return -1;
389 }
390 }
391 }
392
393 // final difference
394 KTextEditor::Cursor realCursor = virtualCursor;
395 realCursor.setLine(m_renderer->folding().visibleLineToLine(visibleLine: realCursor.line()));
396 if (realCursor.column() == -1) {
397 realCursor.setColumn(m_renderer->doc()->lineLength(line: realCursor.line()));
398 }
399 ret += viewLine(realCursor);
400
401 if (limitToVisible && (ret < 0 || ret > limit)) {
402 return -1;
403 }
404
405 return ret;
406}
407
408int KateLayoutCache::lastViewLine(int realLine)
409{
410 if (!m_renderer->view()->dynWordWrap()) {
411 return 0;
412 }
413
414 if (KateLineLayout *l = line(realLine)) {
415 return l->viewLineCount() - 1;
416 }
417 return 0;
418}
419
420int KateLayoutCache::viewLineCount(int realLine)
421{
422 return lastViewLine(realLine) + 1;
423}
424
425void KateLayoutCache::viewCacheDebugOutput() const
426{
427 qCDebug(LOG_KTE) << "Printing values for " << m_textLayouts.size() << " lines:";
428 for (const KateTextLayout &t : std::as_const(t: m_textLayouts)) {
429 if (t.isValid()) {
430 t.debugOutput();
431 } else {
432 qCDebug(LOG_KTE) << "Line Invalid.";
433 }
434 }
435}
436
437void KateLayoutCache::wrapLine(KTextEditor::Document *, const KTextEditor::Cursor position)
438{
439 m_lineLayouts.slotEditDone(fromLine: position.line(), toLine: position.line() + 1, shiftAmount: 1, textLayouts&: m_textLayouts);
440}
441
442void KateLayoutCache::unwrapLine(KTextEditor::Document *, int line)
443{
444 m_lineLayouts.slotEditDone(fromLine: line - 1, toLine: line, shiftAmount: -1, textLayouts&: m_textLayouts);
445}
446
447void KateLayoutCache::insertText(KTextEditor::Document *, const KTextEditor::Cursor position, const QString &)
448{
449 m_lineLayouts.slotEditDone(fromLine: position.line(), toLine: position.line(), shiftAmount: 0, textLayouts&: m_textLayouts);
450}
451
452void KateLayoutCache::removeText(KTextEditor::Document *, KTextEditor::Range range, const QString &)
453{
454 m_lineLayouts.slotEditDone(fromLine: range.start().line(), toLine: range.start().line(), shiftAmount: 0, textLayouts&: m_textLayouts);
455}
456
457void KateLayoutCache::clear()
458{
459 m_textLayouts.clear();
460 m_lineLayouts.clear();
461 m_startPos = KTextEditor::Cursor(-1, -1);
462}
463
464void KateLayoutCache::setViewWidth(int width)
465{
466 m_viewWidth = width;
467 m_lineLayouts.clear();
468 m_textLayouts.clear();
469 m_startPos = KTextEditor::Cursor(-1, -1);
470}
471
472bool KateLayoutCache::wrap() const
473{
474 return m_wrap;
475}
476
477void KateLayoutCache::setWrap(bool wrap)
478{
479 m_wrap = wrap;
480 clear();
481}
482
483void KateLayoutCache::relayoutLines(int startRealLine, int endRealLine)
484{
485 if (startRealLine > endRealLine) {
486 qCWarning(LOG_KTE) << "start" << startRealLine << "before end" << endRealLine;
487 }
488
489 m_lineLayouts.relayoutLines(startRealLine, endRealLine);
490}
491
492bool KateLayoutCache::acceptDirtyLayouts() const
493{
494 return m_acceptDirtyLayouts;
495}
496
497void KateLayoutCache::setAcceptDirtyLayouts(bool accept)
498{
499 m_acceptDirtyLayouts = accept;
500}
501

source code of ktexteditor/src/render/katelayoutcache.cpp