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

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