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 | |
15 | namespace |
16 | { |
17 | bool enableLayoutCache = false; |
18 | |
19 | bool lessThan(const KateLineLayoutMap::LineLayoutPair &lhs, const KateLineLayoutMap::LineLayoutPair &rhs) |
20 | { |
21 | return lhs.first < rhs.first; |
22 | } |
23 | |
24 | } |
25 | |
26 | // BEGIN KateLineLayoutMap |
27 | |
28 | void KateLineLayoutMap::clear() |
29 | { |
30 | m_lineLayouts.clear(); |
31 | } |
32 | |
33 | void 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 | |
44 | void 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 | |
55 | void 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 | |
84 | KateLineLayout *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 | |
94 | KateLayoutCache::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 | |
107 | void 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 | |
215 | KateLineLayout *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 | |
267 | KateTextLayout KateLayoutCache::textLayout(const KTextEditor::Cursor realCursor) |
268 | { |
269 | return textLayout(realLine: realCursor.line(), viewLine: viewLine(realCursor)); |
270 | } |
271 | |
272 | KateTextLayout 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 | |
281 | KateTextLayout &KateLayoutCache::viewLine(int _viewLine) |
282 | { |
283 | Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size()); |
284 | return m_textLayouts[_viewLine]; |
285 | } |
286 | |
287 | int KateLayoutCache::viewCacheLineCount() const |
288 | { |
289 | return m_textLayouts.size(); |
290 | } |
291 | |
292 | KTextEditor::Cursor KateLayoutCache::viewCacheStart() const |
293 | { |
294 | return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor(); |
295 | } |
296 | |
297 | KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const |
298 | { |
299 | return !m_textLayouts.empty() ? m_textLayouts.back().end() : KTextEditor::Cursor(); |
300 | } |
301 | |
302 | int 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 | */ |
312 | int 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 | |
336 | int 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 | |
408 | int 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 | |
420 | int KateLayoutCache::viewLineCount(int realLine) |
421 | { |
422 | return lastViewLine(realLine) + 1; |
423 | } |
424 | |
425 | void 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 | |
437 | void 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 | |
442 | void KateLayoutCache::unwrapLine(KTextEditor::Document *, int line) |
443 | { |
444 | m_lineLayouts.slotEditDone(fromLine: line - 1, toLine: line, shiftAmount: -1, textLayouts&: m_textLayouts); |
445 | } |
446 | |
447 | void 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 | |
452 | void 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 | |
457 | void KateLayoutCache::clear() |
458 | { |
459 | m_textLayouts.clear(); |
460 | m_lineLayouts.clear(); |
461 | m_startPos = KTextEditor::Cursor(-1, -1); |
462 | } |
463 | |
464 | void 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 | |
472 | bool KateLayoutCache::wrap() const |
473 | { |
474 | return m_wrap; |
475 | } |
476 | |
477 | void KateLayoutCache::setWrap(bool wrap) |
478 | { |
479 | m_wrap = wrap; |
480 | clear(); |
481 | } |
482 | |
483 | void 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 | |
492 | bool KateLayoutCache::acceptDirtyLayouts() const |
493 | { |
494 | return m_acceptDirtyLayouts; |
495 | } |
496 | |
497 | void KateLayoutCache::setAcceptDirtyLayouts(bool accept) |
498 | { |
499 | m_acceptDirtyLayouts = accept; |
500 | } |
501 | |