KTextEditor

katelayoutcache.cpp
1 /*
2  SPDX-FileCopyrightText: 2005 Hamish Rodda <[email protected]>
3  SPDX-FileCopyrightText: 2008-2018 Dominik Haumann <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "katelayoutcache.h"
9 
10 #include <QtAlgorithms>
11 
12 #include "katebuffer.h"
13 #include "katedocument.h"
14 #include "katepartdebug.h"
15 #include "katerenderer.h"
16 #include "kateview.h"
17 
18 namespace
19 {
20 bool enableLayoutCache = false;
21 
22 bool lessThan(const KateLineLayoutMap::LineLayoutPair &lhs, const KateLineLayoutMap::LineLayoutPair &rhs)
23 {
24  return lhs.first < rhs.first;
25 }
26 
27 }
28 
29 // BEGIN KateLineLayoutMap
30 
31 void KateLineLayoutMap::clear()
32 {
33  m_lineLayouts.clear();
34 }
35 
36 bool KateLineLayoutMap::contains(int i) const
37 {
38  return std::binary_search(m_lineLayouts.cbegin(), m_lineLayouts.cend(), LineLayoutPair(i, KateLineLayoutPtr()), lessThan);
39 }
40 
41 void KateLineLayoutMap::insert(int realLine, const KateLineLayoutPtr &lineLayoutPtr)
42 {
43  LineLayoutMap::iterator it = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, KateLineLayoutPtr()), lessThan);
44  if (it != m_lineLayouts.end() && (*it) == LineLayoutPair(realLine, KateLineLayoutPtr())) {
45  (*it).second = lineLayoutPtr;
46  } else {
47  it = std::upper_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, KateLineLayoutPtr()), lessThan);
48  m_lineLayouts.insert(it, LineLayoutPair(realLine, lineLayoutPtr));
49  }
50 }
51 
52 void KateLineLayoutMap::viewWidthIncreased()
53 {
54  LineLayoutMap::iterator it = m_lineLayouts.begin();
55  for (; it != m_lineLayouts.end(); ++it) {
56  if ((*it).second->isValid() && (*it).second->viewLineCount() > 1) {
57  (*it).second->invalidateLayout();
58  }
59  }
60 }
61 
62 void KateLineLayoutMap::viewWidthDecreased(int newWidth)
63 {
64  LineLayoutMap::iterator it = m_lineLayouts.begin();
65  for (; it != m_lineLayouts.end(); ++it) {
66  if ((*it).second->isValid() && ((*it).second->viewLineCount() > 1 || (*it).second->width() > newWidth)) {
67  (*it).second->invalidateLayout();
68  }
69  }
70 }
71 
72 void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine)
73 {
74  LineLayoutMap::iterator start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(startRealLine, KateLineLayoutPtr()), lessThan);
75  LineLayoutMap::iterator end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(endRealLine, KateLineLayoutPtr()), lessThan);
76 
77  while (start != end) {
78  (*start).second->setLayoutDirty();
79  ++start;
80  }
81 }
82 
83 void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount)
84 {
85  LineLayoutMap::iterator start = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(fromLine, KateLineLayoutPtr()), lessThan);
86  LineLayoutMap::iterator end = std::upper_bound(start, m_lineLayouts.end(), LineLayoutPair(toLine, KateLineLayoutPtr()), lessThan);
87  LineLayoutMap::iterator it;
88 
89  if (shiftAmount != 0) {
90  for (it = end; it != m_lineLayouts.end(); ++it) {
91  (*it).first += shiftAmount;
92  (*it).second->setLine((*it).second->line() + shiftAmount);
93  }
94 
95  for (it = start; it != end; ++it) {
96  (*it).second->clear();
97  }
98 
99  m_lineLayouts.erase(start, end);
100  } else {
101  for (it = start; it != end; ++it) {
102  (*it).second->setLayoutDirty();
103  }
104  }
105 }
106 
107 KateLineLayoutPtr &KateLineLayoutMap::operator[](int i)
108 {
109  const LineLayoutMap::iterator it = std::lower_bound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(i, KateLineLayoutPtr()), lessThan);
110  Q_ASSERT(it != m_lineLayouts.end());
111  return (*it).second;
112 }
113 // END KateLineLayoutMap
114 
115 KateLayoutCache::KateLayoutCache(KateRenderer *renderer, QObject *parent)
116  : QObject(parent)
117  , m_renderer(renderer)
118  , m_startPos(-1, -1)
119  , m_viewWidth(0)
120  , m_wrap(false)
121  , m_acceptDirtyLayouts(false)
122 {
123  Q_ASSERT(m_renderer);
124 
125  // connect to all possible editing primitives
126  connect(&m_renderer->doc()->buffer(), &KateBuffer::lineWrapped, this, &KateLayoutCache::wrapLine);
127  connect(&m_renderer->doc()->buffer(), &KateBuffer::lineUnwrapped, this, &KateLayoutCache::unwrapLine);
128  connect(&m_renderer->doc()->buffer(), &KateBuffer::textInserted, this, &KateLayoutCache::insertText);
129  connect(&m_renderer->doc()->buffer(), &KateBuffer::textRemoved, this, &KateLayoutCache::removeText);
130 }
131 
132 void KateLayoutCache::updateViewCache(const KTextEditor::Cursor startPos, int newViewLineCount, int viewLinesScrolled)
133 {
134  // qCDebug(LOG_KTE) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled;
135 
136  int oldViewLineCount = m_textLayouts.size();
137  if (newViewLineCount == -1) {
138  newViewLineCount = oldViewLineCount;
139  }
140 
141  enableLayoutCache = true;
142 
143  int realLine;
144  if (newViewLineCount == -1) {
145  realLine = m_renderer->folding().visibleLineToLine(m_renderer->folding().lineToVisibleLine(startPos.line()));
146  } else {
147  realLine = m_renderer->folding().visibleLineToLine(startPos.line());
148  }
149  int _viewLine = 0;
150 
151  if (wrap()) {
152  // TODO check these assumptions are ok... probably they don't give much speedup anyway?
153  if (startPos == m_startPos && !m_textLayouts.empty()) {
154  _viewLine = m_textLayouts.front().viewLine();
155 
156  } else if (viewLinesScrolled > 0 && (size_t)viewLinesScrolled < m_textLayouts.size()) {
157  _viewLine = m_textLayouts[viewLinesScrolled].viewLine();
158 
159  } else {
160  KateLineLayoutPtr l = line(realLine);
161  if (l) {
162  Q_ASSERT(l->isValid());
163  Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor());
164 
165  bool found = false;
166  for (; _viewLine < l->viewLineCount(); ++_viewLine) {
167  const KateTextLayout &t = l->viewLine(_viewLine);
168  if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) {
169  found = true;
170  break;
171  }
172  }
173 
174  // FIXME FIXME need to calculate past-end-of-line position here...
175  Q_ASSERT(found);
176  Q_UNUSED(found);
177  }
178  }
179  }
180 
181  m_startPos = startPos;
182 
183  // Move the text layouts if we've just scrolled...
184  if (viewLinesScrolled != 0) {
185  // loop backwards if we've just scrolled up...
186  bool forwards = viewLinesScrolled >= 0 ? true : false;
187  for (int z = forwards ? 0 : m_textLayouts.size() - 1; forwards ? ((size_t)z < m_textLayouts.size()) : (z >= 0); forwards ? z++ : z--) {
188  int oldZ = z + viewLinesScrolled;
189  if (oldZ >= 0 && (size_t)oldZ < m_textLayouts.size()) {
190  m_textLayouts[z] = m_textLayouts[oldZ];
191  }
192  }
193  }
194 
195  // Resize functionality
196  if (newViewLineCount > oldViewLineCount) {
197  m_textLayouts.reserve(newViewLineCount);
198 
199  } else if (newViewLineCount < oldViewLineCount) {
200  /* FIXME reintroduce... check we're not missing any
201  int lastLine = m_textLayouts[newSize - 1].line();
202  for (int i = oldSize; i < newSize; i++) {
203  const KateTextLayout& layout = m_textLayouts[i];
204  if (layout.line() > lastLine && !layout.viewLine())
205  layout.kateLineLayout()->layout()->setCacheEnabled(false);
206  }*/
207  m_textLayouts.resize(newViewLineCount);
208  }
209 
210  KateLineLayoutPtr l = line(realLine);
211  for (int i = 0; i < newViewLineCount; ++i) {
212  if (!l) {
213  if ((size_t)i < m_textLayouts.size()) {
214  if (m_textLayouts[i].isValid()) {
215  m_textLayouts[i] = KateTextLayout::invalid();
216  }
217  } else {
218  m_textLayouts.push_back(KateTextLayout::invalid());
219  }
220  continue;
221  }
222 
223  Q_ASSERT(l->isValid());
224  Q_ASSERT(_viewLine < l->viewLineCount());
225 
226  if ((size_t)i < m_textLayouts.size()) {
227  bool dirty = false;
228  if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine
229  || (!m_textLayouts[i].isValid() && l->viewLine(_viewLine).isValid())) {
230  dirty = true;
231  }
232  m_textLayouts[i] = l->viewLine(_viewLine);
233  if (dirty) {
234  m_textLayouts[i].setDirty(true);
235  }
236 
237  } else {
238  m_textLayouts.push_back(l->viewLine(_viewLine));
239  }
240 
241  // qCDebug(LOG_KTE) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() <<
242  // ")"; m_textLayouts[i].debugOutput();
243 
244  _viewLine++;
245 
246  if (_viewLine > l->viewLineCount() - 1) {
247  int virtualLine = l->virtualLine() + 1;
248  realLine = m_renderer->folding().visibleLineToLine(virtualLine);
249  _viewLine = 0;
250  if (realLine < m_renderer->doc()->lines()) {
251  l = line(realLine, virtualLine);
252  } else {
253  l = nullptr;
254  }
255  }
256  }
257 
258  enableLayoutCache = false;
259 }
260 
261 KateLineLayoutPtr KateLayoutCache::line(int realLine, int virtualLine)
262 {
263  if (m_lineLayouts.contains(realLine)) {
264  KateLineLayoutPtr l = m_lineLayouts[realLine];
265 
266  // ensure line is OK
267  Q_ASSERT(l->line() == realLine);
268  Q_ASSERT(realLine < m_renderer->doc()->buffer().lines());
269 
270  if (virtualLine != -1) {
271  l->setVirtualLine(virtualLine);
272  }
273 
274  if (!l->isValid()) {
275  l->setUsePlainTextLine(acceptDirtyLayouts());
276  l->textLine(!acceptDirtyLayouts());
277  m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
278  } else if (l->isLayoutDirty() && !acceptDirtyLayouts()) {
279  // reset textline
280  l->setUsePlainTextLine(false);
281  l->textLine(true);
282  m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
283  }
284 
285  Q_ASSERT(l->isValid() && (!l->isLayoutDirty() || acceptDirtyLayouts()));
286 
287  return l;
288  }
289 
290  if (realLine < 0 || realLine >= m_renderer->doc()->lines()) {
291  return KateLineLayoutPtr();
292  }
293 
294  KateLineLayoutPtr l(new KateLineLayout(*m_renderer));
295  l->setLine(realLine, virtualLine);
296 
297  // Mark it dirty, because it may not have the syntax highlighting applied
298  // mark this here, to allow layoutLine to use plainLines...
299  if (acceptDirtyLayouts()) {
300  l->setUsePlainTextLine(true);
301  }
302 
303  m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache);
304  Q_ASSERT(l->isValid());
305 
306  if (acceptDirtyLayouts()) {
307  l->setLayoutDirty(true);
308  }
309 
310  m_lineLayouts.insert(realLine, l);
311  return l;
312 }
313 
315 {
316  return line(realCursor.line());
317 }
318 
320 {
321  return line(realCursor.line())->viewLine(viewLine(realCursor));
322 }
323 
324 KateTextLayout KateLayoutCache::textLayout(uint realLine, int _viewLine)
325 {
326  return line(realLine)->viewLine(_viewLine);
327 }
328 
330 {
331  Q_ASSERT(_viewLine >= 0 && (size_t)_viewLine < m_textLayouts.size());
332  return m_textLayouts[_viewLine];
333 }
334 
335 int KateLayoutCache::viewCacheLineCount() const
336 {
337  return m_textLayouts.size();
338 }
339 
340 KTextEditor::Cursor KateLayoutCache::viewCacheStart() const
341 {
342  return !m_textLayouts.empty() ? m_textLayouts.front().start() : KTextEditor::Cursor();
343 }
344 
345 KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const
346 {
347  return !m_textLayouts.empty() ? m_textLayouts.back().end() : KTextEditor::Cursor();
348 }
349 
350 int KateLayoutCache::viewWidth() const
351 {
352  return m_viewWidth;
353 }
354 
355 /**
356  * This returns the view line upon which realCursor is situated.
357  * The view line is the number of lines in the view from the first line
358  * The supplied cursor should be in real lines.
359  */
361 {
362  /**
363  * Make sure cursor column and line is valid
364  */
365  if (realCursor.column() < 0 || realCursor.line() < 0 || realCursor.line() > m_renderer->doc()->lines()) {
366  return 0;
367  }
368 
369  KateLineLayoutPtr thisLine = line(realCursor.line());
370 
371  for (int i = 0; i < thisLine->viewLineCount(); ++i) {
372  const KateTextLayout &l = thisLine->viewLine(i);
373  if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) {
374  return i;
375  }
376  }
377 
378  return thisLine->viewLineCount() - 1;
379 }
380 
381 int KateLayoutCache::displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible)
382 {
383  if (!virtualCursor.isValid()) {
384  return -1;
385  }
386 
387  KTextEditor::Cursor work = viewCacheStart();
388 
389  // only try this with valid lines!
390  if (work.isValid()) {
391  work.setLine(m_renderer->folding().lineToVisibleLine(work.line()));
392  }
393 
394  if (!work.isValid()) {
395  return virtualCursor.line();
396  }
397 
398  int limit = m_textLayouts.size();
399 
400  // Efficient non-word-wrapped path
401  if (!m_renderer->view()->dynWordWrap()) {
402  int ret = virtualCursor.line() - work.line();
403  if (limitToVisible && (ret < 0)) {
404  return -1;
405  } else if (limitToVisible && (ret > limit)) {
406  return -2;
407  } else {
408  return ret;
409  }
410  }
411 
412  if (work == virtualCursor) {
413  return 0;
414  }
415 
416  int ret = -(int)viewLine(viewCacheStart());
417  bool forwards = (work < virtualCursor);
418 
419  // FIXME switch to using ranges? faster?
420  if (forwards) {
421  while (work.line() != virtualCursor.line()) {
422  ret += viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
423  work.setLine(work.line() + 1);
424  if (limitToVisible && ret > limit) {
425  return -2;
426  }
427  }
428  } else {
429  while (work.line() != virtualCursor.line()) {
430  work.setLine(work.line() - 1);
431  ret -= viewLineCount(m_renderer->folding().visibleLineToLine(work.line()));
432  if (limitToVisible && ret < 0) {
433  return -1;
434  }
435  }
436  }
437 
438  // final difference
439  KTextEditor::Cursor realCursor = virtualCursor;
440  realCursor.setLine(m_renderer->folding().visibleLineToLine(realCursor.line()));
441  if (realCursor.column() == -1) {
442  realCursor.setColumn(m_renderer->doc()->lineLength(realCursor.line()));
443  }
444  ret += viewLine(realCursor);
445 
446  if (limitToVisible && (ret < 0 || ret > limit)) {
447  return -1;
448  }
449 
450  return ret;
451 }
452 
453 int KateLayoutCache::lastViewLine(int realLine)
454 {
455  if (!m_renderer->view()->dynWordWrap()) {
456  return 0;
457  }
458 
459  KateLineLayoutPtr l = line(realLine);
460  Q_ASSERT(l);
461  return l->viewLineCount() - 1;
462 }
463 
464 int KateLayoutCache::viewLineCount(int realLine)
465 {
466  return lastViewLine(realLine) + 1;
467 }
468 
469 void KateLayoutCache::viewCacheDebugOutput() const
470 {
471  qCDebug(LOG_KTE) << "Printing values for " << m_textLayouts.size() << " lines:";
472  for (const KateTextLayout &t : std::as_const(m_textLayouts)) {
473  if (t.isValid()) {
474  t.debugOutput();
475  } else {
476  qCDebug(LOG_KTE) << "Line Invalid.";
477  }
478  }
479 }
480 
481 void KateLayoutCache::wrapLine(const KTextEditor::Cursor position)
482 {
483  m_lineLayouts.slotEditDone(position.line(), position.line() + 1, 1);
484 }
485 
486 void KateLayoutCache::unwrapLine(int line)
487 {
488  m_lineLayouts.slotEditDone(line - 1, line, -1);
489 }
490 
491 void KateLayoutCache::insertText(const KTextEditor::Cursor position, const QString &)
492 {
493  m_lineLayouts.slotEditDone(position.line(), position.line(), 0);
494 }
495 
496 void KateLayoutCache::removeText(KTextEditor::Range range)
497 {
498  m_lineLayouts.slotEditDone(range.start().line(), range.start().line(), 0);
499 }
500 
501 void KateLayoutCache::clear()
502 {
503  m_textLayouts.clear();
504  m_lineLayouts.clear();
505  m_startPos = KTextEditor::Cursor(-1, -1);
506 }
507 
508 void KateLayoutCache::setViewWidth(int width)
509 {
510  bool wider = width > m_viewWidth;
511 
512  m_viewWidth = width;
513 
514  m_lineLayouts.clear();
515  m_startPos = KTextEditor::Cursor(-1, -1);
516 
517  // Only get rid of layouts that we have to
518  if (wider) {
519  m_lineLayouts.viewWidthIncreased();
520  } else {
521  m_lineLayouts.viewWidthDecreased(width);
522  }
523 }
524 
525 bool KateLayoutCache::wrap() const
526 {
527  return m_wrap;
528 }
529 
530 void KateLayoutCache::setWrap(bool wrap)
531 {
532  m_wrap = wrap;
533  clear();
534 }
535 
536 void KateLayoutCache::relayoutLines(int startRealLine, int endRealLine)
537 {
538  if (startRealLine > endRealLine) {
539  qCWarning(LOG_KTE) << "start" << startRealLine << "before end" << endRealLine;
540  }
541 
542  m_lineLayouts.relayoutLines(startRealLine, endRealLine);
543 }
544 
545 bool KateLayoutCache::acceptDirtyLayouts() const
546 {
547  return m_acceptDirtyLayouts;
548 }
549 
550 void KateLayoutCache::setAcceptDirtyLayouts(bool accept)
551 {
552  m_acceptDirtyLayouts = accept;
553 }
constexpr int column() const Q_DECL_NOEXCEPT
Retrieve the column on which this cursor is situated.
Definition: cursor.h:215
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,...
Q_SCRIPTABLE Q_NOREPLY void start()
void lineUnwrapped(int line)
A line got unwrapped.
An object representing a section of text, from one Cursor to another.
KateLineLayoutPtr line(int realLine, int virtualLine=-1)
Returns the KateLineLayout for the specified line.
This class represents one visible line of text; with dynamic wrapping, many KateTextLayouts can be ne...
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int endCol(bool indicateEOL=false) const
Return the end column of this text line.
Kate::TextFolding & folding() const
Returns the folding info to which this renderer is bound.
Definition: katerenderer.h:92
The Cursor represents a position in a Document.
Definition: cursor.h:71
void setLine(int line) Q_DECL_NOEXCEPT
Set the cursor line to line.
Definition: cursor.h:206
void setColumn(int column) Q_DECL_NOEXCEPT
Set the cursor column to column.
Definition: cursor.h:224
void textRemoved(KTextEditor::Range range, const QString &text)
Text got removed.
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
KTextEditor::ViewPrivate * view() const
Returns the view to which this renderer is bound.
Definition: katerenderer.h:100
constexpr Cursor start() const Q_DECL_NOEXCEPT
Get the start position of this range.
void textInserted(const KTextEditor::Cursor position, const QString &text)
Text got inserted.
bool isValid(QStringView ifopt)
Handles all of the work of rendering the text (used for the views and printing)
Definition: katerenderer.h:48
void lineWrapped(const KTextEditor::Cursor position)
A line got wrapped.
KateTextLayout textLayout(const KTextEditor::Cursor realCursor)
Returns the layout describing the text line which is occupied by realCursor.
constexpr int line() const Q_DECL_NOEXCEPT
Retrieve the line on which this cursor is situated.
Definition: cursor.h:197
constexpr bool isValid() const Q_DECL_NOEXCEPT
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition: cursor.h:99
KTextEditor::DocumentPrivate * doc() const
Returns the document to which this renderer is bound.
Definition: katerenderer.h:83
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
KateTextLayout & viewLine(int viewLine)
Returns the layout of the corresponding line in the view.
const QList< QKeySequence > & end()
void layoutLine(KateLineLayoutPtr line, int maxwidth=-1, bool cacheLayout=false) const
Text width & height calculation functions...
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 03:48:54 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.