OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / texteditor / basetextdocumentlayout.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "basetextdocumentlayout.h"
35
36 using namespace TextEditor;
37
38 CodeFormatterData::~CodeFormatterData()
39 {
40 }
41
42 TextBlockUserData::~TextBlockUserData()
43 {
44     TextMarks marks = m_marks;
45     m_marks.clear();
46     foreach (ITextMark *mrk, marks) {
47         mrk->removedFromEditor();
48     }
49
50     if (m_codeFormatterData)
51         delete m_codeFormatterData;
52 }
53
54 int TextBlockUserData::braceDepthDelta() const
55 {
56     int delta = 0;
57     for (int i = 0; i < m_parentheses.size(); ++i) {
58         switch (m_parentheses.at(i).chr.unicode()) {
59         case '{': case '+': case '[': ++delta; break;
60         case '}': case '-': case ']': --delta; break;
61         default: break;
62         }
63     }
64     return delta;
65 }
66
67 TextBlockUserData::MatchType TextBlockUserData::checkOpenParenthesis(QTextCursor *cursor, QChar c)
68 {
69     QTextBlock block = cursor->block();
70     if (!BaseTextDocumentLayout::hasParentheses(block) || BaseTextDocumentLayout::ifdefedOut(block))
71         return NoMatch;
72
73     Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
74     Parenthesis openParen, closedParen;
75     QTextBlock closedParenParag = block;
76
77     const int cursorPos = cursor->position() - closedParenParag.position();
78     int i = 0;
79     int ignore = 0;
80     bool foundOpen = false;
81     for (;;) {
82         if (!foundOpen) {
83             if (i >= parenList.count())
84                 return NoMatch;
85             openParen = parenList.at(i);
86             if (openParen.pos != cursorPos) {
87                 ++i;
88                 continue;
89             } else {
90                 foundOpen = true;
91                 ++i;
92             }
93         }
94
95         if (i >= parenList.count()) {
96             for (;;) {
97                 closedParenParag = closedParenParag.next();
98                 if (!closedParenParag.isValid())
99                     return NoMatch;
100                 if (BaseTextDocumentLayout::hasParentheses(closedParenParag)
101                     && !BaseTextDocumentLayout::ifdefedOut(closedParenParag)) {
102                     parenList = BaseTextDocumentLayout::parentheses(closedParenParag);
103                     break;
104                 }
105             }
106             i = 0;
107         }
108
109         closedParen = parenList.at(i);
110         if (closedParen.type == Parenthesis::Opened) {
111             ignore++;
112             ++i;
113             continue;
114         } else {
115             if (ignore > 0) {
116                 ignore--;
117                 ++i;
118                 continue;
119             }
120
121             cursor->clearSelection();
122             cursor->setPosition(closedParenParag.position() + closedParen.pos + 1, QTextCursor::KeepAnchor);
123
124             if ((c == QLatin1Char('{') && closedParen.chr != QLatin1Char('}'))
125                 || (c == QLatin1Char('(') && closedParen.chr != QLatin1Char(')'))
126                 || (c == QLatin1Char('[') && closedParen.chr != QLatin1Char(']'))
127                 || (c == QLatin1Char('+') && closedParen.chr != QLatin1Char('-'))
128                )
129                 return Mismatch;
130
131             return Match;
132         }
133     }
134 }
135
136 TextBlockUserData::MatchType TextBlockUserData::checkClosedParenthesis(QTextCursor *cursor, QChar c)
137 {
138     QTextBlock block = cursor->block();
139     if (!BaseTextDocumentLayout::hasParentheses(block) || BaseTextDocumentLayout::ifdefedOut(block))
140         return NoMatch;
141
142     Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
143     Parenthesis openParen, closedParen;
144     QTextBlock openParenParag = block;
145
146     const int cursorPos = cursor->position() - openParenParag.position();
147     int i = parenList.count() - 1;
148     int ignore = 0;
149     bool foundClosed = false;
150     for (;;) {
151         if (!foundClosed) {
152             if (i < 0)
153                 return NoMatch;
154             closedParen = parenList.at(i);
155             if (closedParen.pos != cursorPos - 1) {
156                 --i;
157                 continue;
158             } else {
159                 foundClosed = true;
160                 --i;
161             }
162         }
163
164         if (i < 0) {
165             for (;;) {
166                 openParenParag = openParenParag.previous();
167                 if (!openParenParag.isValid())
168                     return NoMatch;
169
170                 if (BaseTextDocumentLayout::hasParentheses(openParenParag)
171                     && !BaseTextDocumentLayout::ifdefedOut(openParenParag)) {
172                     parenList = BaseTextDocumentLayout::parentheses(openParenParag);
173                     break;
174                 }
175             }
176             i = parenList.count() - 1;
177         }
178
179         openParen = parenList.at(i);
180         if (openParen.type == Parenthesis::Closed) {
181             ignore++;
182             --i;
183             continue;
184         } else {
185             if (ignore > 0) {
186                 ignore--;
187                 --i;
188                 continue;
189             }
190
191             cursor->clearSelection();
192             cursor->setPosition(openParenParag.position() + openParen.pos, QTextCursor::KeepAnchor);
193
194             if ((c == '}' && openParen.chr != '{')    ||
195                  (c == ')' && openParen.chr != '(')   ||
196                  (c == ']' && openParen.chr != '[')   ||
197                  (c == '-' && openParen.chr != '+'))
198                 return Mismatch;
199
200             return Match;
201         }
202     }
203 }
204
205 bool TextBlockUserData::findPreviousOpenParenthesis(QTextCursor *cursor, bool select)
206 {
207     QTextBlock block = cursor->block();
208     int position = cursor->position();
209     int ignore = 0;
210     while (block.isValid()) {
211         Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
212         if (!parenList.isEmpty() && !BaseTextDocumentLayout::ifdefedOut(block)) {
213             for (int i = parenList.count()-1; i >= 0; --i) {
214                 Parenthesis paren = parenList.at(i);
215                 if (block == cursor->block() &&
216                     (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0)))
217                         continue;
218                 if (paren.type == Parenthesis::Closed) {
219                     ++ignore;
220                 } else if (ignore > 0) {
221                     --ignore;
222                 } else {
223                     cursor->setPosition(block.position() + paren.pos, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
224                     return true;
225                 }
226             }
227         }
228         block = block.previous();
229     }
230     return false;
231 }
232
233 bool TextBlockUserData::findPreviousBlockOpenParenthesis(QTextCursor *cursor, bool checkStartPosition)
234 {
235     QTextBlock block = cursor->block();
236     int position = cursor->position();
237     int ignore = 0;
238     while (block.isValid()) {
239         Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
240         if (!parenList.isEmpty() && !BaseTextDocumentLayout::ifdefedOut(block)) {
241             for (int i = parenList.count()-1; i >= 0; --i) {
242                 Parenthesis paren = parenList.at(i);
243                 if (paren.chr != QLatin1Char('{') && paren.chr != QLatin1Char('}')
244                     && paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-')
245                     && paren.chr != QLatin1Char('[') && paren.chr != QLatin1Char(']'))
246                     continue;
247                 if (block == cursor->block()) {
248                     if (position - block.position() <= paren.pos + (paren.type == Parenthesis::Closed ? 1 : 0))
249                         continue;
250                     if (checkStartPosition && paren.type == Parenthesis::Opened && paren.pos== cursor->position()) {
251                         return true;
252                     }
253                 }
254                 if (paren.type == Parenthesis::Closed) {
255                     ++ignore;
256                 } else if (ignore > 0) {
257                     --ignore;
258                 } else {
259                     cursor->setPosition(block.position() + paren.pos);
260                     return true;
261                 }
262             }
263         }
264         block = block.previous();
265     }
266     return false;
267 }
268
269 bool TextBlockUserData::findNextClosingParenthesis(QTextCursor *cursor, bool select)
270 {
271     QTextBlock block = cursor->block();
272     int position = cursor->position();
273     int ignore = 0;
274     while (block.isValid()) {
275         Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
276         if (!parenList.isEmpty() && !BaseTextDocumentLayout::ifdefedOut(block)) {
277             for (int i = 0; i < parenList.count(); ++i) {
278                 Parenthesis paren = parenList.at(i);
279                 if (block == cursor->block() &&
280                     (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
281                     continue;
282                 if (paren.type == Parenthesis::Opened) {
283                     ++ignore;
284                 } else if (ignore > 0) {
285                     --ignore;
286                 } else {
287                     cursor->setPosition(block.position() + paren.pos+1, select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
288                     return true;
289                 }
290             }
291         }
292         block = block.next();
293     }
294     return false;
295 }
296
297 bool TextBlockUserData::findNextBlockClosingParenthesis(QTextCursor *cursor)
298 {
299     QTextBlock block = cursor->block();
300     int position = cursor->position();
301     int ignore = 0;
302     while (block.isValid()) {
303         Parentheses parenList = BaseTextDocumentLayout::parentheses(block);
304         if (!parenList.isEmpty() && !BaseTextDocumentLayout::ifdefedOut(block)) {
305             for (int i = 0; i < parenList.count(); ++i) {
306                 Parenthesis paren = parenList.at(i);
307                 if (paren.chr != QLatin1Char('{') && paren.chr != QLatin1Char('}')
308                     && paren.chr != QLatin1Char('+') && paren.chr != QLatin1Char('-')
309                     && paren.chr != QLatin1Char('[') && paren.chr != QLatin1Char(']'))
310                     continue;
311                 if (block == cursor->block() &&
312                     (position - block.position() > paren.pos - (paren.type == Parenthesis::Opened ? 1 : 0)))
313                     continue;
314                 if (paren.type == Parenthesis::Opened) {
315                     ++ignore;
316                 } else if (ignore > 0) {
317                     --ignore;
318                 } else {
319                     cursor->setPosition(block.position() + paren.pos+1);
320                     return true;
321                 }
322             }
323         }
324         block = block.next();
325     }
326     return false;
327 }
328
329 TextBlockUserData::MatchType TextBlockUserData::matchCursorBackward(QTextCursor *cursor)
330 {
331     cursor->clearSelection();
332     const QTextBlock block = cursor->block();
333
334     if (!BaseTextDocumentLayout::hasParentheses(block) || BaseTextDocumentLayout::ifdefedOut(block))
335         return NoMatch;
336
337     const int relPos = cursor->position() - block.position();
338
339     Parentheses parentheses = BaseTextDocumentLayout::parentheses(block);
340     const Parentheses::const_iterator cend = parentheses.constEnd();
341     for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
342         const Parenthesis &paren = *it;
343         if (paren.pos == relPos - 1
344             && paren.type == Parenthesis::Closed) {
345             return checkClosedParenthesis(cursor, paren.chr);
346         }
347     }
348     return NoMatch;
349 }
350
351 TextBlockUserData::MatchType TextBlockUserData::matchCursorForward(QTextCursor *cursor)
352 {
353     cursor->clearSelection();
354     const QTextBlock block = cursor->block();
355
356     if (!BaseTextDocumentLayout::hasParentheses(block) || BaseTextDocumentLayout::ifdefedOut(block))
357         return NoMatch;
358
359     const int relPos = cursor->position() - block.position();
360
361     Parentheses parentheses = BaseTextDocumentLayout::parentheses(block);
362     const Parentheses::const_iterator cend = parentheses.constEnd();
363     for (Parentheses::const_iterator it = parentheses.constBegin();it != cend; ++it) {
364         const Parenthesis &paren = *it;
365         if (paren.pos == relPos
366             && paren.type == Parenthesis::Opened) {
367             return checkOpenParenthesis(cursor, paren.chr);
368         }
369     }
370     return NoMatch;
371 }
372
373 void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data)
374 {
375     if (m_codeFormatterData)
376         delete m_codeFormatterData;
377
378     m_codeFormatterData = data;
379 }
380
381 BaseTextDocumentLayout::BaseTextDocumentLayout(QTextDocument *doc)
382     :QPlainTextDocumentLayout(doc) {
383     lastSaveRevision = 0;
384     hasMarks = 0;
385     m_requiredWidth = 0;
386 }
387
388 BaseTextDocumentLayout::~BaseTextDocumentLayout()
389 {
390 }
391
392 void BaseTextDocumentLayout::setParentheses(const QTextBlock &block, const Parentheses &parentheses)
393 {
394     if (parentheses.isEmpty()) {
395         if (TextBlockUserData *userData = testUserData(block))
396             userData->clearParentheses();
397     } else {
398         userData(block)->setParentheses(parentheses);
399     }
400 }
401
402 Parentheses BaseTextDocumentLayout::parentheses(const QTextBlock &block)
403 {
404     if (TextBlockUserData *userData = testUserData(block))
405         return userData->parentheses();
406     return Parentheses();
407 }
408
409 bool BaseTextDocumentLayout::hasParentheses(const QTextBlock &block)
410 {
411     if (TextBlockUserData *userData = testUserData(block))
412         return userData->hasParentheses();
413     return false;
414 }
415
416 bool BaseTextDocumentLayout::setIfdefedOut(const QTextBlock &block)
417 {
418     return userData(block)->setIfdefedOut();
419 }
420
421 bool BaseTextDocumentLayout::clearIfdefedOut(const QTextBlock &block)
422 {
423     if (TextBlockUserData *userData = testUserData(block))
424         return userData->clearIfdefedOut();
425     return false;
426 }
427
428 bool BaseTextDocumentLayout::ifdefedOut(const QTextBlock &block)
429 {
430     if (TextBlockUserData *userData = testUserData(block))
431         return userData->ifdefedOut();
432     return false;
433 }
434
435 int BaseTextDocumentLayout::braceDepthDelta(const QTextBlock &block)
436 {
437     if (TextBlockUserData *userData = testUserData(block))
438         return userData->braceDepthDelta();
439     return 0;
440 }
441
442 int BaseTextDocumentLayout::braceDepth(const QTextBlock &block)
443 {
444     int state = block.userState();
445     if (state == -1)
446         return 0;
447     return state >> 8;
448 }
449
450 void BaseTextDocumentLayout::setBraceDepth(QTextBlock &block, int depth)
451 {
452     int state = block.userState();
453     if (state == -1)
454         state = 0;
455     state = state & 0xff;
456     block.setUserState((depth << 8) | state);
457 }
458
459 void BaseTextDocumentLayout::changeBraceDepth(QTextBlock &block, int delta)
460 {
461     if (delta)
462         setBraceDepth(block, braceDepth(block) + delta);
463 }
464
465 void BaseTextDocumentLayout::setLexerState(const QTextBlock &block, int state)
466 {
467     if (state == 0) {
468         if (TextBlockUserData *userData = testUserData(block))
469             userData->setLexerState(0);
470     } else {
471         userData(block)->setLexerState(qMax(0,state));
472     }
473 }
474
475 int BaseTextDocumentLayout::lexerState(const QTextBlock &block)
476 {
477     if (TextBlockUserData *userData = testUserData(block))
478         return userData->lexerState();
479     return 0;
480 }
481
482 void BaseTextDocumentLayout::setFoldingIndent(const QTextBlock &block, int indent)
483 {
484     if (indent == 0) {
485         if (TextBlockUserData *userData = testUserData(block))
486             userData->setFoldingIndent(0);
487     } else {
488         userData(block)->setFoldingIndent(qMax(0,indent));
489     }
490 }
491
492 int BaseTextDocumentLayout::foldingIndent(const QTextBlock &block)
493 {
494     if (TextBlockUserData *userData = testUserData(block))
495         return userData->foldingIndent();
496     return 0;
497 }
498
499 void BaseTextDocumentLayout::changeFoldingIndent(QTextBlock &block, int delta)
500 {
501     if (delta)
502         setFoldingIndent(block, foldingIndent(block) + delta);
503 }
504
505 bool BaseTextDocumentLayout::canFold(const QTextBlock &block)
506 {
507     return (block.next().isValid() && foldingIndent(block.next()) > foldingIndent(block));
508 }
509
510 bool BaseTextDocumentLayout::isFolded(const QTextBlock &block)
511 {
512     if (TextBlockUserData *userData = testUserData(block))
513         return userData->folded();
514     return false;
515 }
516
517 void BaseTextDocumentLayout::setFolded(const QTextBlock &block, bool folded)
518 {
519     if (folded)
520         userData(block)->setFolded(true);
521     else {
522         if (TextBlockUserData *userData = testUserData(block))
523             return userData->setFolded(false);
524     }
525 }
526
527 void BaseTextDocumentLayout::doFoldOrUnfold(const QTextBlock& block, bool unfold)
528 {
529     if (!canFold(block))
530         return;
531     QTextBlock b = block.next();
532
533     int indent = foldingIndent(block);
534     while (b.isValid() && foldingIndent(b) > indent && (unfold || b.next().isValid())) {
535         b.setVisible(unfold);
536         b.setLineCount(unfold? qMax(1, b.layout()->lineCount()) : 0);
537         if (unfold) { // do not unfold folded sub-blocks
538             if (isFolded(b) && b.next().isValid()) {
539                 int jndent = foldingIndent(b);
540                 b = b.next();
541                 while (b.isValid() && foldingIndent(b) > jndent)
542                     b = b.next();
543                 continue;
544             }
545         }
546         b = b.next();
547     }
548     setFolded(block, !unfold);
549 }
550
551 void BaseTextDocumentLayout::setRequiredWidth(int width)
552 {
553     int oldw = m_requiredWidth;
554     m_requiredWidth = width;
555     int dw = QPlainTextDocumentLayout::documentSize().width();
556     if (oldw > dw || width > dw)
557         emitDocumentSizeChanged();
558 }
559
560
561 QSizeF BaseTextDocumentLayout::documentSize() const
562 {
563     QSizeF size = QPlainTextDocumentLayout::documentSize();
564     size.setWidth(qMax((qreal)m_requiredWidth, size.width()));
565     return size;
566 }