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::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 | |
39 | void 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 | |
50 | void 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 | |
79 | KateLineLayout *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 | |
89 | KateLayoutCache::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 | |
102 | void 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 | |
210 | KateLineLayout *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 | |
255 | KateTextLayout KateLayoutCache::textLayout(const KTextEditor::Cursor realCursor) |
256 | { |
257 | return textLayout(realLine: realCursor.line(), viewLine: viewLine(realCursor)); |
258 | } |
259 | |
260 | KateTextLayout 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 | |
269 | KateTextLayout &KateLayoutCache::viewLine(int _viewLine) |
270 | { |
271 | Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size()); |
272 | return m_textLayouts[_viewLine]; |
273 | } |
274 | |
275 | int KateLayoutCache::viewCacheLineCount() const |
276 | { |
277 | return m_textLayouts.size(); |
278 | } |
279 | |
280 | KTextEditor::Cursor KateLayoutCache::viewCacheStart() const |
281 | { |
282 | return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor(); |
283 | } |
284 | |
285 | int 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 | */ |
295 | int 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 | |
319 | int 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 | |
393 | int 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 | |
405 | int KateLayoutCache::viewLineCount(int realLine) |
406 | { |
407 | return lastViewLine(realLine) + 1; |
408 | } |
409 | |
410 | void 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 | |
422 | void 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 | |
427 | void 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 | |
432 | void 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 | |
437 | void 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 | |
442 | void KateLayoutCache::clear() |
443 | { |
444 | m_textLayouts.clear(); |
445 | m_lineLayouts.clear(); |
446 | m_startPos = KTextEditor::Cursor(-1, -1); |
447 | } |
448 | |
449 | void 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 | |
457 | bool KateLayoutCache::wrap() const |
458 | { |
459 | return m_wrap; |
460 | } |
461 | |
462 | void KateLayoutCache::setWrap(bool wrap) |
463 | { |
464 | m_wrap = wrap; |
465 | clear(); |
466 | } |
467 | |
468 | void 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 | |
477 | bool KateLayoutCache::acceptDirtyLayouts() const |
478 | { |
479 | return m_acceptDirtyLayouts; |
480 | } |
481 | |
482 | void KateLayoutCache::setAcceptDirtyLayouts(bool accept) |
483 | { |
484 | m_acceptDirtyLayouts = accept; |
485 | } |
486 | |