KTextEditor

katelayoutcache.cpp
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(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, nullptr), lessThan);
36 m_lineLayouts.insert(it, LineLayoutPair(realLine, std::move(lineLayoutPtr)));
37}
38
39void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine)
40{
41 auto start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(startRealLine, nullptr), lessThan);
42 auto end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(endRealLine, nullptr), lessThan);
43
44 while (start != end) {
45 (*start).second->layoutDirty = true;
46 ++start;
47 }
48}
49
50void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount, std::vector<KateTextLayout> &textLayouts)
51{
52 auto start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(fromLine, nullptr), lessThan);
53 auto end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(toLine, nullptr), lessThan);
54
55 if (shiftAmount != 0) {
56 for (auto it = end; it != m_lineLayouts.end(); ++it) {
57 (*it).first += shiftAmount;
58 (*it).second->setLine((*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(start, 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(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(i, nullptr), 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(m_renderer->doc(), &KTextEditor::Document::lineWrapped, this, &KateLayoutCache::wrapLine);
97 connect(m_renderer->doc(), &KTextEditor::Document::lineUnwrapped, this, &KateLayoutCache::unwrapLine);
98 connect(m_renderer->doc(), &KTextEditor::Document::textInserted, this, &KateLayoutCache::insertText);
99 connect(m_renderer->doc(), &KTextEditor::Document::textRemoved, this, &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(m_renderer->folding().lineToVisibleLine(startPos.line()));
116 } else {
117 realLine = m_renderer->folding().visibleLineToLine(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(l->length() >= startPos.column() || m_renderer->view()->wrapCursor());
126 bool found = false;
127 for (; _viewLine < l->viewLineCount(); ++_viewLine) {
128 const KateTextLayout &t = l->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(newViewLineCount);
155 } else if (newViewLineCount < oldViewLineCount) {
156 m_textLayouts.resize(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(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).isValid())) {
179 dirty = true;
180 }
181 m_textLayouts[i] = l->viewLine(_viewLine);
182 if (dirty) {
183 m_textLayouts[i].setDirty(true);
184 }
185
186 } else {
187 m_textLayouts.push_back(l->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(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(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 if (!l->layout()) {
222 l->usePlainTextLine = acceptDirtyLayouts();
223 l->textLine(!acceptDirtyLayouts());
224 m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
225 } else if (l->layoutDirty && !acceptDirtyLayouts()) {
226 // reset textline
227 l->usePlainTextLine = false;
228 l->textLine(true);
229 m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
230 }
231
232 Q_ASSERT(l->layout() && (!l->layoutDirty || acceptDirtyLayouts()));
233
234 return l;
235 }
236
237 if (realLine < 0 || realLine >= m_renderer->doc()->lines()) {
238 return nullptr;
239 }
240
241 KateLineLayout *l = new KateLineLayout(*m_renderer);
242 l->setLine(realLine, virtualLine);
243
244 // Mark it dirty, because it may not have the syntax highlighting applied
245 // mark this here, to allow layoutLine to use plainLines...
246 if (acceptDirtyLayouts()) {
247 l->usePlainTextLine = true;
248 }
249
250 m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
251 Q_ASSERT(l->isValid());
252
253 if (acceptDirtyLayouts()) {
254 l->layoutDirty = true;
255 }
256
257 // transfer ownership to m_lineLayouts
258 m_lineLayouts.insert(realLine, std::unique_ptr<KateLineLayout>(l));
259 return l;
260}
261
263{
264 return textLayout(realCursor.line(), viewLine(realCursor));
265}
266
267KateTextLayout KateLayoutCache::textLayout(uint realLine, int _viewLine)
268{
269 auto l = line(realLine);
270 if (l && l->isValid()) {
271 return l->viewLine(_viewLine);
272 }
273 return KateTextLayout::invalid();
274}
275
277{
278 Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size());
279 return m_textLayouts[_viewLine];
280}
281
282int KateLayoutCache::viewCacheLineCount() const
283{
284 return m_textLayouts.size();
285}
286
287KTextEditor::Cursor KateLayoutCache::viewCacheStart() const
288{
289 return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor();
290}
291
292KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const
293{
294 return !m_textLayouts.empty() ? m_textLayouts.back().end() : KTextEditor::Cursor();
295}
296
297int KateLayoutCache::viewWidth() const
298{
299 return m_viewWidth;
300}
301
302/**
303 * This returns the view line upon which realCursor is situated.
304 * The view line is the number of lines in the view from the first line
305 * The supplied cursor should be in real lines.
306 */
308{
309 /**
310 * Make sure cursor column and line is valid
311 */
312 if (realCursor.column() < 0 || realCursor.line() < 0 || realCursor.line() > m_renderer->doc()->lines()) {
313 return 0;
314 }
315
316 KateLineLayout *thisLine = line(realCursor.line());
317 if (!thisLine) {
318 return 0;
319 }
320
321 for (int i = 0; i < thisLine->viewLineCount(); ++i) {
322 const KateTextLayout &l = thisLine->viewLine(i);
323 if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) {
324 return i;
325 }
326 }
327
328 return thisLine->viewLineCount() - 1;
329}
330
331int KateLayoutCache::displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible)
332{
333 if (!virtualCursor.isValid()) {
334 return -1;
335 }
336
337 KTextEditor::Cursor work = viewCacheStart();
338
339 // only try this with valid lines!
340 if (work.isValid()) {
341 work.setLine(m_renderer->folding().lineToVisibleLine(work.line()));
342 }
343
344 if (!work.isValid()) {
345 return virtualCursor.line();
346 }
347
348 int limit = m_textLayouts.size();
349
350 // Efficient non-word-wrapped path
351 if (!m_renderer->view()->dynWordWrap()) {
352 int ret = virtualCursor.line() - work.line();
353 if (limitToVisible && (ret < 0)) {
354 return -1;
355 } else if (limitToVisible && (ret > limit)) {
356 return -2;
357 } else {
358 return ret;
359 }
360 }
361
362 if (work == virtualCursor) {
363 return 0;
364 }
365
366 int ret = -(int)viewLine(viewCacheStart());
367 bool forwards = (work < virtualCursor);
368
369 // FIXME switch to using ranges? faster?
370 if (forwards) {
371 while (work.line() != virtualCursor.line()) {
372 ret += viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
373 work.setLine(work.line() + 1);
374 if (limitToVisible && ret > limit) {
375 return -2;
376 }
377 }
378 } else {
379 while (work.line() != virtualCursor.line()) {
380 work.setLine(work.line() - 1);
381 ret -= viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
382 if (limitToVisible && ret < 0) {
383 return -1;
384 }
385 }
386 }
387
388 // final difference
389 KTextEditor::Cursor realCursor = virtualCursor;
390 realCursor.setLine(m_renderer->folding().visibleLineToLine(realCursor.line()));
391 if (realCursor.column() == -1) {
392 realCursor.setColumn(m_renderer->doc()->lineLength(realCursor.line()));
393 }
394 ret += viewLine(realCursor);
395
396 if (limitToVisible && (ret < 0 || ret > limit)) {
397 return -1;
398 }
399
400 return ret;
401}
402
403int KateLayoutCache::lastViewLine(int realLine)
404{
405 if (!m_renderer->view()->dynWordWrap()) {
406 return 0;
407 }
408
409 if (KateLineLayout *l = line(realLine)) {
410 return l->viewLineCount() - 1;
411 }
412 return 0;
413}
414
415int KateLayoutCache::viewLineCount(int realLine)
416{
417 return lastViewLine(realLine) + 1;
418}
419
420void KateLayoutCache::viewCacheDebugOutput() const
421{
422 qCDebug(LOG_KTE) << "Printing values for " << m_textLayouts.size() << " lines:";
423 for (const KateTextLayout &t : std::as_const(m_textLayouts)) {
424 if (t.isValid()) {
425 t.debugOutput();
426 } else {
427 qCDebug(LOG_KTE) << "Line Invalid.";
428 }
429 }
430}
431
432void KateLayoutCache::wrapLine(KTextEditor::Document *, const KTextEditor::Cursor position)
433{
434 m_lineLayouts.slotEditDone(position.line(), position.line() + 1, 1, m_textLayouts);
435}
436
437void KateLayoutCache::unwrapLine(KTextEditor::Document *, int line)
438{
439 m_lineLayouts.slotEditDone(line - 1, line, -1, m_textLayouts);
440}
441
442void KateLayoutCache::insertText(KTextEditor::Document *, const KTextEditor::Cursor position, const QString &)
443{
444 m_lineLayouts.slotEditDone(position.line(), position.line(), 0, m_textLayouts);
445}
446
447void KateLayoutCache::removeText(KTextEditor::Document *, KTextEditor::Range range, const QString &)
448{
449 m_lineLayouts.slotEditDone(range.start().line(), range.start().line(), 0, m_textLayouts);
450}
451
452void KateLayoutCache::clear()
453{
454 m_textLayouts.clear();
455 m_lineLayouts.clear();
456 m_startPos = KTextEditor::Cursor(-1, -1);
457}
458
459void KateLayoutCache::setViewWidth(int width)
460{
461 m_viewWidth = width;
462 m_lineLayouts.clear();
463 m_textLayouts.clear();
464 m_startPos = KTextEditor::Cursor(-1, -1);
465}
466
467bool KateLayoutCache::wrap() const
468{
469 return m_wrap;
470}
471
472void KateLayoutCache::setWrap(bool wrap)
473{
474 m_wrap = wrap;
475 clear();
476}
477
478void KateLayoutCache::relayoutLines(int startRealLine, int endRealLine)
479{
480 if (startRealLine > endRealLine) {
481 qCWarning(LOG_KTE) << "start" << startRealLine << "before end" << endRealLine;
482 }
483
484 m_lineLayouts.relayoutLines(startRealLine, endRealLine);
485}
486
487bool KateLayoutCache::acceptDirtyLayouts() const
488{
489 return m_acceptDirtyLayouts;
490}
491
492void KateLayoutCache::setAcceptDirtyLayouts(bool accept)
493{
494 m_acceptDirtyLayouts = accept;
495}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
void setLine(int line) noexcept
Set the cursor line to line.
Definition cursor.h:183
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
int lines() const override
Get the count of lines of the document.
int lineLength(int line) const override
Get the length of a given line in characters.
A KParts derived class representing a text document.
Definition document.h:284
void lineUnwrapped(KTextEditor::Document *document, int line)
A line got unwrapped.
void lineWrapped(KTextEditor::Document *document, KTextEditor::Cursor position)
A line got wrapped.
void textInserted(KTextEditor::Document *document, KTextEditor::Cursor position, const QString &text)
Text got inserted.
void textRemoved(KTextEditor::Document *document, KTextEditor::Range range, const QString &text)
Text got removed.
An object representing a section of text, from one Cursor to another.
constexpr Cursor start() const noexcept
Get the start position of this range.
KateTextLayout & viewLine(int viewLine)
Returns the layout of the corresponding line in the view.
KateLineLayout * line(int realLine, int virtualLine=-1)
Returns the KateLineLayout for the specified line.
KateTextLayout textLayout(const KTextEditor::Cursor realCursor)
Returns the layout describing the text line which is occupied by realCursor.
int displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible=false)
Find the view line of the cursor, relative to the display (0 = top line of view, 1 = second line,...
Handles all of the work of rendering the text (used for the views and printing)
Kate::TextFolding & folding() const
Returns the folding info to which this renderer is bound.
KTextEditor::ViewPrivate * view() const
Returns the view to which this renderer is bound.
KTextEditor::DocumentPrivate * doc() const
Returns the document to which this renderer is bound.
void layoutLine(KateLineLayout *line, int maxwidth=-1, bool cacheLayout=false) const
Text width & height calculation functions...
This class represents one visible line of text; with dynamic wrapping, many KateTextLayouts can be ne...
int endCol(bool indicateEOL=false) const
Return the end column of this text line.
int viewLine() const
Return the index of this visual line inside the document line (KateLineLayout).
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
Q_SCRIPTABLE Q_NOREPLY void start()
bool isValid(QStringView ifopt)
const QList< QKeySequence > & end()
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.