1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "textdocumentlayout.h"
27 #include "textdocument.h"
28 #include <utils/qtcassert.h>
29 #include <QDebug>
30 
31 namespace TextEditor {
32 
33 CodeFormatterData::~CodeFormatterData() = default;
34 
~TextBlockUserData()35 TextBlockUserData::~TextBlockUserData()
36 {
37     for (TextMark *mrk : qAsConst(m_marks)) {
38         mrk->baseTextDocument()->removeMarkFromMarksCache(mrk);
39         mrk->setBaseTextDocument(nullptr);
40         mrk->removedFromEditor();
41     }
42 
43     delete m_codeFormatterData;
44 }
45 
braceDepthDelta() const46 int TextBlockUserData::braceDepthDelta() const
47 {
48     int delta = 0;
49     for (auto &parenthesis : m_parentheses) {
50         switch (parenthesis.chr.unicode()) {
51         case '{': case '+': case '[': ++delta; break;
52         case '}': case '-': case ']': --delta; break;
53         default: break;
54         }
55     }
56     return delta;
57 }
58 
checkOpenParenthesis(QTextCursor * cursor,QChar c)59 TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c)
60 {
61     QTextBlock block = cursor->block();
62     if (!TextDocumentLayout::hasParentheses(block) || TextDocumentLayout::ifdefedOut(block))
63         return NoMatch;
64 
65     Parentheses parenList = TextDocumentLayout::parentheses(block);
66     Parenthesis openParen, closedParen;
67     QTextBlock closedParenParag = block;
68 
69     const int cursorPos = cursor->position() - closedParenParag.position();
70     int i = 0;
71     int ignore = 0;
72     bool foundOpen = false;
73     for (;;) {
74         if (!foundOpen) {
75             if (i >= parenList.count())
76                 return NoMatch;
77             openParen = parenList.at(i);
78             if (openParen.pos != cursorPos) {
79                 ++i;
80                 continue;
81             } else {
82                 foundOpen = true;
83                 ++i;
84             }
85         }
86 
87         if (i >= parenList.count()) {
88             for (;;) {
89                 closedParenParag = closedParenParag.next();
90                 if (!closedParenParag.isValid())
91                     return NoMatch;
92                 if (TextDocumentLayout::hasParentheses(closedParenParag)
93                     && !TextDocumentLayout::ifdefedOut(closedParenParag)) {
94                     parenList = TextDocumentLayout::parentheses(closedParenParag);
95                     break;
96                 }
97             }
98             i = 0;
99         }
100 
101         closedParen = parenList.at(i);
102         if (closedParen.type == Parenthesis::Opened) {
103             ignore++;
104             ++i;
105             continue;
106         } else {
107             if (ignore > 0) {
108                 ignore--;
109                 ++i;
110                 continue;
111             }
112 
113             cursor->clearSelection();
114             cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
115 
116             if ((c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}'))
117                 || (c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')'))
118                 || (c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']'))
119                 || (c == QLatin1Char('+') && closedParen.chr != QLatin1Char('-'))
120                )
121                 return Mismatch;
122 
123             return Match;
124         }
125     }
126 }
127 
checkClosedParenthesis(QTextCursor * cursor,QChar c)128 TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c)
129 {
130     QTextBlock block = cursor->block();
131     if (!TextDocumentLayout::hasParentheses(block) || TextDocumentLayout::ifdefedOut(block))
132         return NoMatch;
133 
134     Parentheses parenList = TextDocumentLayout::parentheses(block);
135     Parenthesis openParen, closedParen;
136     QTextBlock openParenParag = block;
137 
138     const int cursorPos = cursor->position() - openParenParag.position();
139     int i = parenList.count() - 1;
140     int ignore = 0;
141     bool foundClosed = false;
142     for (;;) {
143         if (!foundClosed) {
144             if (i < 0)
145                 return NoMatch;
146             closedParen = parenList.at(i);
147             if (closedParen.pos != cursorPos - 1) {
148                 --i;
149                 continue;
150             } else {
151                 foundClosed = true;
152                 --i;
153             }
154         }
155 
156         if (i < 0) {
157             for (;;) {
158                 openParenParag = openParenParag.previous();
159                 if (!openParenParag.isValid())
160                     return NoMatch;
161 
162                 if (TextDocumentLayout::hasParentheses(openParenParag)
163                     && !TextDocumentLayout::ifdefedOut(openParenParag)) {
164                     parenList = TextDocumentLayout::parentheses(openParenParag);
165                     break;
166                 }
167             }
168             i = parenList.count() - 1;
169         }
170 
171         openParen = parenList.at(i);
172         if (openParen.type == Parenthesis::Closed) {
173             ignore++;
174             --i;
175             continue;
176         } else {
177             if (ignore > 0) {
178                 ignore--;
179                 --i;
180                 continue;
181             }
182 
183             cursor->clearSelection();
184             cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
185 
186             if ((c == QLatin1Char('}') && openParen.chr != QLatin1Char('{'))    ||
187                  (c == QLatin1Char(')') && openParen.chr != QLatin1Char('('))   ||
188                  (c == QLatin1Char(']') && openParen.chr != QLatin1Char('['))   ||
189                  (c == QLatin1Char('-') && openParen.chr != QLatin1Char('+')))
190                 return Mismatch;
191 
192             return Match;
193         }
194     }
195 }
196 
findPreviousOpenParenthesis(QTextCursor * cursor,bool select,bool onlyInCurrentBlock)197 bool TextBlockUserData::findPreviousOpenParenthesis(QTextCursor *cursor, bool select, bool onlyInCurrentBlock)
198 {
199     QTextBlock block = cursor->block();
200     int position = cursor->position();
201     int ignore = 0;
202     while (block.isValid()) {
203         Parentheses parenList = TextDocumentLayout::parentheses(block);
204         if (!parenList.isEmpty() && !TextDocumentLayout::ifdefedOut(block)) {
205             for (int i = parenList.count()-1; i >= 0; --i) {
206                 Parenthesis paren = parenList.at(i);
207                 if (block == cursor->block() &&
208                     (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0)))
209                         continue;
210                 if (paren.type == Parenthesis::Closed) {
211                     ++ignore;
212                 } else if (ignore > 0) {
213                     --ignore;
214                 } else {
215                     cursor->setPosition(block.position() + paren.pos, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
216                     return true;
217                 }
218             }
219         }
220         if (onlyInCurrentBlock)
221             return false;
222         block = block.previous();
223     }
224     return false;
225 }
226 
findPreviousBlockOpenParenthesis(QTextCursor * cursor,bool checkStartPosition)227 bool TextBlockUserData::findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition)
228 {
229     QTextBlock block = cursor->block();
230     int position = cursor->position();
231     int ignore = 0;
232     while (block.isValid()) {
233         Parentheses parenList = TextDocumentLayout::parentheses(block);
234         if (!parenList.isEmpty() && !TextDocumentLayout::ifdefedOut(block)) {
235             for (int i = parenList.count()-1; i >= 0; --i) {
236                 Parenthesis paren = parenList.at(i);
237                 if (paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-'))
238                     continue;
239                 if (block == cursor->block()) {
240                     if (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0))
241                         continue;
242                     if (checkStartPosition && paren.type == Parenthesis::Opened && paren.pos== cursor->position())
243                         return true;
244                 }
245                 if (paren.type == Parenthesis::Closed) {
246                     ++ignore;
247                 } else if (ignore > 0) {
248                     --ignore;
249                 } else {
250                     cursor->setPosition(block.position() + paren.pos);
251                     return true;
252                 }
253             }
254         }
255         block = block.previous();
256     }
257     return false;
258 }
259 
findNextClosingParenthesis(QTextCursor * cursor,bool select)260 bool TextBlockUserData::findNextClosingParenthesis(QTextCursor *cursor, bool select)
261 {
262     QTextBlock block = cursor->block();
263     int position = cursor->position();
264     int ignore = 0;
265     while (block.isValid()) {
266         Parentheses parenList = TextDocumentLayout::parentheses(block);
267         if (!parenList.isEmpty() && !TextDocumentLayout::ifdefedOut(block)) {
268             for (int i = 0; i < parenList.count(); ++i) {
269                 Parenthesis paren = parenList.at(i);
270                 if (block == cursor->block() &&
271                     (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
272                     continue;
273                 if (paren.type == Parenthesis::Opened) {
274                     ++ignore;
275                 } else if (ignore > 0) {
276                     --ignore;
277                 } else {
278                     cursor->setPosition(block.position() + paren.pos+1, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
279                     return true;
280                 }
281             }
282         }
283         block = block.next();
284     }
285     return false;
286 }
287 
findNextBlockClosingParenthesis(QTextCursor * cursor)288 bool TextBlockUserData::findNextBlockClosingParenthesis(QTextCursor *cursor)
289 {
290     QTextBlock block = cursor->block();
291     int position = cursor->position();
292     int ignore = 0;
293     while (block.isValid()) {
294         Parentheses parenList = TextDocumentLayout::parentheses(block);
295         if (!parenList.isEmpty() && !TextDocumentLayout::ifdefedOut(block)) {
296             for (int i = 0; i < parenList.count(); ++i) {
297                 Parenthesis paren = parenList.at(i);
298                 if (paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-'))
299                     continue;
300                 if (block == cursor->block() &&
301                     (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
302                     continue;
303                 if (paren.type == Parenthesis::Opened) {
304                     ++ignore;
305                 } else if (ignore > 0) {
306                     --ignore;
307                 } else {
308                     cursor->setPosition(block.position() + paren.pos+1);
309                     return true;
310                 }
311             }
312         }
313         block = block.next();
314     }
315     return false;
316 }
317 
matchCursorBackward(QTextCursor * cursor)318 TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor)
319 {
320     cursor->clearSelection();
321     const QTextBlock block = cursor->block();
322 
323     if (!TextDocumentLayout::hasParentheses(block) || TextDocumentLayout::ifdefedOut(block))
324         return NoMatch;
325 
326     const int relPos = cursor->position() - block.position();
327 
328     Parentheses parentheses = TextDocumentLayout::parentheses(block);
329     const Parentheses::const_iterator cend = parentheses.constEnd();
330     for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
331         const Parenthesis &paren = *it;
332         if (paren.pos == relPos - 1 && paren.type == Parenthesis::Closed)
333             return checkClosedParenthesis(cursor, paren.chr);
334     }
335     return NoMatch;
336 }
337 
matchCursorForward(QTextCursor * cursor)338 TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor)
339 {
340     cursor->clearSelection();
341     const QTextBlock block = cursor->block();
342 
343     if (!TextDocumentLayout::hasParentheses(block) || TextDocumentLayout::ifdefedOut(block))
344         return NoMatch;
345 
346     const int relPos = cursor->position() - block.position();
347 
348     Parentheses parentheses = TextDocumentLayout::parentheses(block);
349     const Parentheses::const_iterator cend = parentheses.constEnd();
350     for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
351         const Parenthesis &paren = *it;
352         if (paren.pos == relPos
353             && paren.type == Parenthesis::Opened) {
354             return checkOpenParenthesis(cursor, paren.chr);
355         }
356     }
357     return NoMatch;
358 }
359 
setCodeFormatterData(CodeFormatterData * data)360 void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data)
361 {
362     if (m_codeFormatterData)
363         delete m_codeFormatterData;
364 
365     m_codeFormatterData = data;
366 }
367 
addMark(TextMark * mark)368 void TextBlockUserData::addMark(TextMark *mark)
369 {
370     int i = 0;
371     for ( ; i < m_marks.size(); ++i) {
372         if (mark->priority() < m_marks.at(i)->priority())
373             break;
374     }
375     m_marks.insert(i, mark);
376 }
377 
378 
TextDocumentLayout(QTextDocument * doc)379 TextDocumentLayout::TextDocumentLayout(QTextDocument *doc)
380     : QPlainTextDocumentLayout(doc)
381 {}
382 
~TextDocumentLayout()383 TextDocumentLayout::~TextDocumentLayout()
384 {
385     documentClosing();
386 }
387 
setParentheses(const QTextBlock & block,const Parentheses & parentheses)388 void TextDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses)
389 {
390     if (parentheses.isEmpty()) {
391         if (TextBlockUserData *userData = textUserData(block))
392             userData->clearParentheses();
393     } else {
394         userData(block)->setParentheses(parentheses);
395     }
396 }
397 
parentheses(const QTextBlock & block)398 Parentheses TextDocumentLayout::parentheses(const QTextBlock &block)
399 {
400     if (TextBlockUserData *userData = textUserData(block))
401         return userData->parentheses();
402     return Parentheses();
403 }
404 
hasParentheses(const QTextBlock & block)405 bool TextDocumentLayout::hasParentheses(const QTextBlock &block)
406 {
407     if (TextBlockUserData *userData = textUserData(block))
408         return userData->hasParentheses();
409     return false;
410 }
411 
setIfdefedOut(const QTextBlock & block)412 bool TextDocumentLayout::setIfdefedOut(const QTextBlock &block)
413 {
414     return userData(block)->setIfdefedOut();
415 }
416 
clearIfdefedOut(const QTextBlock & block)417 bool TextDocumentLayout::clearIfdefedOut(const QTextBlock &block)
418 {
419     if (TextBlockUserData *userData = textUserData(block))
420         return userData->clearIfdefedOut();
421     return false;
422 }
423 
ifdefedOut(const QTextBlock & block)424 bool TextDocumentLayout::ifdefedOut(const QTextBlock &block)
425 {
426     if (TextBlockUserData *userData = textUserData(block))
427         return userData->ifdefedOut();
428     return false;
429 }
430 
braceDepthDelta(const QTextBlock & block)431 int TextDocumentLayout::braceDepthDelta(const QTextBlock &block)
432 {
433     if (TextBlockUserData *userData = textUserData(block))
434         return userData->braceDepthDelta();
435     return 0;
436 }
437 
braceDepth(const QTextBlock & block)438 int TextDocumentLayout::braceDepth(const QTextBlock &block)
439 {
440     int state = block.userState();
441     if (state == -1)
442         return 0;
443     return state >> 8;
444 }
445 
setBraceDepth(QTextBlock & block,int depth)446 void TextDocumentLayout::setBraceDepth(QTextBlock &block, int depth)
447 {
448     int state = block.userState();
449     if (state == -1)
450         state = 0;
451     state = state & 0xff;
452     block.setUserState((depth << 8) | state);
453 }
454 
changeBraceDepth(QTextBlock & block,int delta)455 void TextDocumentLayout::changeBraceDepth(QTextBlock &block, int delta)
456 {
457     if (delta)
458         setBraceDepth(block, braceDepth(block) + delta);
459 }
460 
setLexerState(const QTextBlock & block,int state)461 void TextDocumentLayout::setLexerState(const QTextBlock &block, int state)
462 {
463     if (state == 0) {
464         if (TextBlockUserData *userData = textUserData(block))
465             userData->setLexerState(0);
466     } else {
467         userData(block)->setLexerState(qMax(0,state));
468     }
469 }
470 
lexerState(const QTextBlock & block)471 int TextDocumentLayout::lexerState(const QTextBlock &block)
472 {
473     if (TextBlockUserData *userData = textUserData(block))
474         return userData->lexerState();
475     return 0;
476 }
477 
setFoldingIndent(const QTextBlock & block,int indent)478 void TextDocumentLayout::setFoldingIndent(const QTextBlock &block, int indent)
479 {
480     if (indent == 0) {
481         if (TextBlockUserData *userData = textUserData(block))
482             userData->setFoldingIndent(0);
483     } else {
484         userData(block)->setFoldingIndent(indent);
485     }
486 }
487 
foldingIndent(const QTextBlock & block)488 int TextDocumentLayout::foldingIndent(const QTextBlock &block)
489 {
490     if (TextBlockUserData *userData = textUserData(block))
491         return userData->foldingIndent();
492     return 0;
493 }
494 
changeFoldingIndent(QTextBlock & block,int delta)495 void TextDocumentLayout::changeFoldingIndent(QTextBlock &block, int delta)
496 {
497     if (delta)
498         setFoldingIndent(block, foldingIndent(block) + delta);
499 }
500 
canFold(const QTextBlock & block)501 bool TextDocumentLayout::canFold(const QTextBlock &block)
502 {
503     return (block.next().isValid() && foldingIndent(block.next()) > foldingIndent(block));
504 }
505 
isFolded(const QTextBlock & block)506 bool TextDocumentLayout::isFolded(const QTextBlock &block)
507 {
508     if (TextBlockUserData *userData = textUserData(block))
509         return userData->folded();
510     return false;
511 }
512 
setFolded(const QTextBlock & block,bool folded)513 void TextDocumentLayout::setFolded(const QTextBlock &block, bool folded)
514 {
515     if (folded)
516         userData(block)->setFolded(true);
517     else if (TextBlockUserData *userData = textUserData(block))
518         userData->setFolded(false);
519     else
520         return;
521 
522     if (auto layout = qobject_cast<TextDocumentLayout *>(block.document()->documentLayout()))
523         emit layout->foldChanged(block.blockNumber(), folded);
524 }
525 
requestExtraAreaUpdate()526 void TextDocumentLayout::requestExtraAreaUpdate()
527 {
528     emit updateExtraArea();
529 }
530 
doFoldOrUnfold(const QTextBlock & block,bool unfold)531 void TextDocumentLayout::doFoldOrUnfold(const QTextBlock& block, bool unfold)
532 {
533     if (!canFold(block))
534         return;
535     QTextBlock b = block.next();
536 
537     int indent = foldingIndent(block);
538     while (b.isValid() && foldingIndent(b) > indent && (unfold || b.next().isValid())) {
539         b.setVisible(unfold);
540         b.setLineCount(unfold? qMax(1, b.layout()->lineCount()) : 0);
541         if (unfold) { // do not unfold folded sub-blocks
542             if (isFolded(b) && b.next().isValid()) {
543                 int jndent = foldingIndent(b);
544                 b = b.next();
545                 while (b.isValid() && foldingIndent(b) > jndent)
546                     b = b.next();
547                 continue;
548             }
549         }
550         b = b.next();
551     }
552     setFolded(block, !unfold);
553 }
554 
setRequiredWidth(int width)555 void TextDocumentLayout::setRequiredWidth(int width)
556 {
557     int oldw = m_requiredWidth;
558     m_requiredWidth = width;
559     int dw = int(QPlainTextDocumentLayout::documentSize().width());
560     if (oldw > dw || width > dw)
561         emitDocumentSizeChanged();
562 }
563 
564 
documentSize() const565 QSizeF TextDocumentLayout::documentSize() const
566 {
567     QSizeF size = QPlainTextDocumentLayout::documentSize();
568     size.setWidth(qMax(qreal(m_requiredWidth), size.width()));
569     return size;
570 }
571 
documentClosing()572 TextMarks TextDocumentLayout::documentClosing()
573 {
574     TextMarks marks;
575     for (QTextBlock block = document()->begin(); block.isValid(); block = block.next()) {
576         if (auto data = static_cast<TextBlockUserData *>(block.userData()))
577             marks.append(data->documentClosing());
578     }
579     return marks;
580 }
581 
documentReloaded(TextMarks marks,TextDocument * baseTextDocument)582 void TextDocumentLayout::documentReloaded(TextMarks marks, TextDocument *baseTextDocument)
583 {
584     for (TextMark *mark : qAsConst(marks)) {
585         int blockNumber = mark->lineNumber() - 1;
586         QTextBlock block = document()->findBlockByNumber(blockNumber);
587         if (block.isValid()) {
588             TextBlockUserData *userData = TextDocumentLayout::userData(block);
589             userData->addMark(mark);
590             mark->setBaseTextDocument(baseTextDocument);
591             mark->updateBlock(block);
592         } else {
593             baseTextDocument->removeMarkFromMarksCache(mark);
594             mark->setBaseTextDocument(nullptr);
595             mark->removedFromEditor();
596         }
597     }
598     requestUpdate();
599 }
600 
updateMarksLineNumber()601 void TextDocumentLayout::updateMarksLineNumber()
602 {
603     // Note: the breakpointmanger deletes breakpoint marks and readds them
604     // if it doesn't agree with our updating
605     QTextBlock block = document()->begin();
606     int blockNumber = 0;
607     while (block.isValid()) {
608         if (const TextBlockUserData *userData = textUserData(block)) {
609             for (TextMark *mrk : userData->marks())
610                 mrk->updateLineNumber(blockNumber + 1);
611         }
612         block = block.next();
613         ++blockNumber;
614     }
615 }
616 
updateMarksBlock(const QTextBlock & block)617 void TextDocumentLayout::updateMarksBlock(const QTextBlock &block)
618 {
619     if (const TextBlockUserData *userData = textUserData(block)) {
620         for (TextMark *mrk : userData->marks())
621             mrk->updateBlock(block);
622     }
623 }
624 
blockBoundingRect(const QTextBlock & block) const625 QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
626 {
627     QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block);
628     if (TextBlockUserData *userData = textUserData(block))
629         boundingRect.adjust(0, 0, 0, userData->additionalAnnotationHeight());
630     return boundingRect;
631 }
632 
setup(TextDocumentLayout * layout)633 void TextDocumentLayout::FoldValidator::setup(TextDocumentLayout *layout)
634 {
635     m_layout = layout;
636 }
637 
reset()638 void TextDocumentLayout::FoldValidator::reset()
639 {
640     m_insideFold = 0;
641     m_requestDocUpdate = false;
642 }
643 
process(QTextBlock block)644 void TextDocumentLayout::FoldValidator::process(QTextBlock block)
645 {
646     if (!m_layout)
647         return;
648 
649     const QTextBlock &previous = block.previous();
650     if (!previous.isValid())
651         return;
652 
653     const bool preIsFolded = isFolded(previous);
654     const bool preCanFold = canFold(previous);
655     const bool isVisible = block.isVisible();
656 
657     if (preIsFolded && !preCanFold)
658         setFolded(previous, false);
659     else if (!preIsFolded && preCanFold && previous.isVisible() && !isVisible)
660         setFolded(previous, true);
661 
662     if (isFolded(previous) && !m_insideFold)
663         m_insideFold = foldingIndent(block);
664 
665     bool shouldBeVisible = m_insideFold == 0;
666     if (!shouldBeVisible) {
667         shouldBeVisible = foldingIndent(block) < m_insideFold;
668         if (shouldBeVisible)
669             m_insideFold = 0;
670     }
671 
672     if (shouldBeVisible != isVisible) {
673         block.setVisible(shouldBeVisible);
674         block.setLineCount(block.isVisible() ? qMax(1, block.layout()->lineCount()) : 0);
675         m_requestDocUpdate = true;
676     }
677 }
678 
finalize()679 void TextDocumentLayout::FoldValidator::finalize()
680 {
681     if (m_requestDocUpdate && m_layout) {
682         m_layout->requestUpdate();
683         m_layout->emitDocumentSizeChanged();
684     }
685 }
686 
687 } // namespace TextEditor
688