source: trunk/src/gui/text/qsyntaxhighlighter.cpp@ 890

Last change on this file since 890 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 21.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qsyntaxhighlighter.h"
43
44#ifndef QT_NO_SYNTAXHIGHLIGHTER
45#include <private/qobject_p.h>
46#include <qtextdocument.h>
47#include <private/qtextdocument_p.h>
48#include <qtextlayout.h>
49#include <qpointer.h>
50#include <qtextobject.h>
51#include <qtextcursor.h>
52#include <qdebug.h>
53#include <qtextedit.h>
54#include <qtimer.h>
55
56QT_BEGIN_NAMESPACE
57
58class QSyntaxHighlighterPrivate : public QObjectPrivate
59{
60 Q_DECLARE_PUBLIC(QSyntaxHighlighter)
61public:
62 inline QSyntaxHighlighterPrivate()
63 : rehighlightPending(false), inReformatBlocks(false)
64 {}
65
66 QPointer<QTextDocument> doc;
67
68 void _q_reformatBlocks(int from, int charsRemoved, int charsAdded);
69 void reformatBlocks(int from, int charsRemoved, int charsAdded);
70 void reformatBlock(const QTextBlock &block);
71
72 inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) {
73 inReformatBlocks = true;
74 cursor.beginEditBlock();
75 int from = cursor.position();
76 cursor.movePosition(operation);
77 reformatBlocks(from, 0, cursor.position() - from);
78 cursor.endEditBlock();
79 inReformatBlocks = false;
80 }
81
82 inline void _q_delayedRehighlight() {
83 if (!rehighlightPending)
84 return;
85 rehighlightPending = false;
86 q_func()->rehighlight();
87 }
88
89 void applyFormatChanges();
90 QVector<QTextCharFormat> formatChanges;
91 QTextBlock currentBlock;
92 bool rehighlightPending;
93 bool inReformatBlocks;
94};
95
96void QSyntaxHighlighterPrivate::applyFormatChanges()
97{
98 bool formatsChanged = false;
99
100 QTextLayout *layout = currentBlock.layout();
101
102 QList<QTextLayout::FormatRange> ranges = layout->additionalFormats();
103
104 const int preeditAreaStart = layout->preeditAreaPosition();
105 const int preeditAreaLength = layout->preeditAreaText().length();
106
107 if (preeditAreaLength != 0) {
108 QList<QTextLayout::FormatRange>::Iterator it = ranges.begin();
109 while (it != ranges.end()) {
110 if (it->start >= preeditAreaStart
111 && it->start + it->length <= preeditAreaStart + preeditAreaLength) {
112 ++it;
113 } else {
114 it = ranges.erase(it);
115 formatsChanged = true;
116 }
117 }
118 } else if (!ranges.isEmpty()) {
119 ranges.clear();
120 formatsChanged = true;
121 }
122
123 QTextCharFormat emptyFormat;
124
125 QTextLayout::FormatRange r;
126 r.start = -1;
127
128 int i = 0;
129 while (i < formatChanges.count()) {
130
131 while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat)
132 ++i;
133
134 if (i >= formatChanges.count())
135 break;
136
137 r.start = i;
138 r.format = formatChanges.at(i);
139
140 while (i < formatChanges.count() && formatChanges.at(i) == r.format)
141 ++i;
142
143 if (i >= formatChanges.count())
144 break;
145
146 r.length = i - r.start;
147
148 if (preeditAreaLength != 0) {
149 if (r.start >= preeditAreaStart)
150 r.start += preeditAreaLength;
151 else if (r.start + r.length >= preeditAreaStart)
152 r.length += preeditAreaLength;
153 }
154
155 ranges << r;
156 formatsChanged = true;
157 r.start = -1;
158 }
159
160 if (r.start != -1) {
161 r.length = formatChanges.count() - r.start;
162
163 if (preeditAreaLength != 0) {
164 if (r.start >= preeditAreaStart)
165 r.start += preeditAreaLength;
166 else if (r.start + r.length >= preeditAreaStart)
167 r.length += preeditAreaLength;
168 }
169
170 ranges << r;
171 formatsChanged = true;
172 }
173
174 if (formatsChanged) {
175 layout->setAdditionalFormats(ranges);
176 doc->markContentsDirty(currentBlock.position(), currentBlock.length());
177 }
178}
179
180void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
181{
182 if (!inReformatBlocks)
183 reformatBlocks(from, charsRemoved, charsAdded);
184}
185
186void QSyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded)
187{
188 rehighlightPending = false;
189
190 QTextBlock block = doc->findBlock(from);
191 if (!block.isValid())
192 return;
193
194 int endPosition;
195 QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0));
196 if (lastBlock.isValid())
197 endPosition = lastBlock.position() + lastBlock.length();
198 else
199 endPosition = doc->docHandle()->length();
200
201 bool forceHighlightOfNextBlock = false;
202
203 while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
204 const int stateBeforeHighlight = block.userState();
205
206 reformatBlock(block);
207
208 forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight);
209
210 block = block.next();
211 }
212
213 formatChanges.clear();
214}
215
216void QSyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block)
217{
218 Q_Q(QSyntaxHighlighter);
219
220 Q_ASSERT_X(!currentBlock.isValid(), "QSyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively");
221
222 currentBlock = block;
223
224 formatChanges.fill(QTextCharFormat(), block.length() - 1);
225 q->highlightBlock(block.text());
226 applyFormatChanges();
227
228 currentBlock = QTextBlock();
229}
230
231/*!
232 \class QSyntaxHighlighter
233 \reentrant
234
235 \brief The QSyntaxHighlighter class allows you to define syntax
236 highlighting rules, and in addition you can use the class to query
237 a document's current formatting or user data.
238
239 \since 4.1
240
241 \ingroup richtext-processing
242
243 The QSyntaxHighlighter class is a base class for implementing
244 QTextEdit syntax highlighters. A syntax highligher automatically
245 highlights parts of the text in a QTextEdit, or more generally in
246 a QTextDocument. Syntax highlighters are often used when the user
247 is entering text in a specific format (for example source code)
248 and help the user to read the text and identify syntax errors.
249
250 To provide your own syntax highlighting, you must subclass
251 QSyntaxHighlighter and reimplement highlightBlock().
252
253 When you create an instance of your QSyntaxHighlighter subclass,
254 pass it the QTextEdit or QTextDocument that you want the syntax
255 highlighting to be applied to. For example:
256
257 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 0
258
259 After this your highlightBlock() function will be called
260 automatically whenever necessary. Use your highlightBlock()
261 function to apply formatting (e.g. setting the font and color) to
262 the text that is passed to it. QSyntaxHighlighter provides the
263 setFormat() function which applies a given QTextCharFormat on
264 the current text block. For example:
265
266 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 1
267
268 Some syntaxes can have constructs that span several text
269 blocks. For example, a C++ syntax highlighter should be able to
270 cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
271 these cases it is necessary to know the end state of the previous
272 text block (e.g. "in comment").
273
274 Inside your highlightBlock() implementation you can query the end
275 state of the previous text block using the previousBlockState()
276 function. After parsing the block you can save the last state
277 using setCurrentBlockState().
278
279 The currentBlockState() and previousBlockState() functions return
280 an int value. If no state is set, the returned value is -1. You
281 can designate any other value to identify any given state using
282 the setCurrentBlockState() function. Once the state is set the
283 QTextBlock keeps that value until it is set set again or until the
284 corresponding paragraph of text is deleted.
285
286 For example, if you're writing a simple C++ syntax highlighter,
287 you might designate 1 to signify "in comment":
288
289 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 2
290
291 In the example above, we first set the current block state to
292 0. Then, if the previous block ended within a comment, we higlight
293 from the beginning of the current block (\c {startIndex =
294 0}). Otherwise, we search for the given start expression. If the
295 specified end expression cannot be found in the text block, we
296 change the current block state by calling setCurrentBlockState(),
297 and make sure that the rest of the block is higlighted.
298
299 In addition you can query the current formatting and user data
300 using the format() and currentBlockUserData() functions
301 respectively. You can also attach user data to the current text
302 block using the setCurrentBlockUserData() function.
303 QTextBlockUserData can be used to store custom settings. In the
304 case of syntax highlighting, it is in particular interesting as
305 cache storage for information that you may figure out while
306 parsing the paragraph's text. For an example, see the
307 setCurrentBlockUserData() documentation.
308
309 \sa QTextEdit, {Syntax Highlighter Example}
310*/
311
312/*!
313 Constructs a QSyntaxHighlighter with the given \a parent.
314*/
315QSyntaxHighlighter::QSyntaxHighlighter(QObject *parent)
316 : QObject(*new QSyntaxHighlighterPrivate, parent)
317{
318}
319
320/*!
321 Constructs a QSyntaxHighlighter and installs it on \a parent.
322 The specified QTextDocument also becomes the owner of the
323 QSyntaxHighlighter.
324*/
325QSyntaxHighlighter::QSyntaxHighlighter(QTextDocument *parent)
326 : QObject(*new QSyntaxHighlighterPrivate, parent)
327{
328 setDocument(parent);
329}
330
331/*!
332 Constructs a QSyntaxHighlighter and installs it on \a parent 's
333 QTextDocument. The specified QTextEdit also becomes the owner of
334 the QSyntaxHighlighter.
335*/
336QSyntaxHighlighter::QSyntaxHighlighter(QTextEdit *parent)
337 : QObject(*new QSyntaxHighlighterPrivate, parent)
338{
339 setDocument(parent->document());
340}
341
342/*!
343 Destructor. Uninstalls this syntax highlighter from the text document.
344*/
345QSyntaxHighlighter::~QSyntaxHighlighter()
346{
347 setDocument(0);
348}
349
350/*!
351 Installs the syntax highlighter on the given QTextDocument \a doc.
352 A QSyntaxHighlighter can only be used with one document at a time.
353*/
354void QSyntaxHighlighter::setDocument(QTextDocument *doc)
355{
356 Q_D(QSyntaxHighlighter);
357 if (d->doc) {
358 disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
359 this, SLOT(_q_reformatBlocks(int,int,int)));
360
361 QTextCursor cursor(d->doc);
362 cursor.beginEditBlock();
363 for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next())
364 blk.layout()->clearAdditionalFormats();
365 cursor.endEditBlock();
366 }
367 d->doc = doc;
368 if (d->doc) {
369 connect(d->doc, SIGNAL(contentsChange(int,int,int)),
370 this, SLOT(_q_reformatBlocks(int,int,int)));
371 d->rehighlightPending = true;
372 QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
373 }
374}
375
376/*!
377 Returns the QTextDocument on which this syntax highlighter is
378 installed.
379*/
380QTextDocument *QSyntaxHighlighter::document() const
381{
382 Q_D(const QSyntaxHighlighter);
383 return d->doc;
384}
385
386/*!
387 \since 4.2
388
389 Reapplies the highlighting to the whole document.
390
391 \sa rehighlightBlock()
392*/
393void QSyntaxHighlighter::rehighlight()
394{
395 Q_D(QSyntaxHighlighter);
396 if (!d->doc)
397 return;
398
399 QTextCursor cursor(d->doc);
400 d->rehighlight(cursor, QTextCursor::End);
401}
402
403/*!
404 \since 4.6
405
406 Reapplies the highlighting to the given QTextBlock \a block.
407
408 \sa rehighlight()
409*/
410void QSyntaxHighlighter::rehighlightBlock(const QTextBlock &block)
411{
412 Q_D(QSyntaxHighlighter);
413 if (!d->doc || !block.isValid() || block.document() != d->doc)
414 return;
415
416 const bool rehighlightPending = d->rehighlightPending;
417
418 QTextCursor cursor(block);
419 d->rehighlight(cursor, QTextCursor::EndOfBlock);
420
421 if (rehighlightPending)
422 d->rehighlightPending = rehighlightPending;
423}
424
425/*!
426 \fn void QSyntaxHighlighter::highlightBlock(const QString &text)
427
428 Highlights the given text block. This function is called when
429 necessary by the rich text engine, i.e. on text blocks which have
430 changed.
431
432 To provide your own syntax highlighting, you must subclass
433 QSyntaxHighlighter and reimplement highlightBlock(). In your
434 reimplementation you should parse the block's \a text and call
435 setFormat() as often as necessary to apply any font and color
436 changes that you require. For example:
437
438 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 3
439
440 Some syntaxes can have constructs that span several text
441 blocks. For example, a C++ syntax highlighter should be able to
442 cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
443 these cases it is necessary to know the end state of the previous
444 text block (e.g. "in comment").
445
446 Inside your highlightBlock() implementation you can query the end
447 state of the previous text block using the previousBlockState()
448 function. After parsing the block you can save the last state
449 using setCurrentBlockState().
450
451 The currentBlockState() and previousBlockState() functions return
452 an int value. If no state is set, the returned value is -1. You
453 can designate any other value to identify any given state using
454 the setCurrentBlockState() function. Once the state is set the
455 QTextBlock keeps that value until it is set set again or until the
456 corresponding paragraph of text gets deleted.
457
458 For example, if you're writing a simple C++ syntax highlighter,
459 you might designate 1 to signify "in comment". For a text block
460 that ended in the middle of a comment you'd set 1 using
461 setCurrentBlockState, and for other paragraphs you'd set 0.
462 In your parsing code if the return value of previousBlockState()
463 is 1, you would highlight the text as a C++ comment until you
464 reached the closing \c{*}\c{/}.
465
466 \sa previousBlockState(), setFormat(), setCurrentBlockState()
467*/
468
469/*!
470 This function is applied to the syntax highlighter's current text
471 block (i.e. the text that is passed to the highlightBlock()
472 function).
473
474 The specified \a format is applied to the text from the \a start
475 position for a length of \a count characters (if \a count is 0,
476 nothing is done). The formatting properties set in \a format are
477 merged at display time with the formatting information stored
478 directly in the document, for example as previously set with
479 QTextCursor's functions. Note that the document itself remains
480 unmodified by the format set through this function.
481
482 \sa format(), highlightBlock()
483*/
484void QSyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format)
485{
486 Q_D(QSyntaxHighlighter);
487 if (start < 0 || start >= d->formatChanges.count())
488 return;
489
490 const int end = qMin(start + count, d->formatChanges.count());
491 for (int i = start; i < end; ++i)
492 d->formatChanges[i] = format;
493}
494
495/*!
496 \overload
497
498 The specified \a color is applied to the current text block from
499 the \a start position for a length of \a count characters.
500
501 The other attributes of the current text block, e.g. the font and
502 background color, are reset to default values.
503
504 \sa format(), highlightBlock()
505*/
506void QSyntaxHighlighter::setFormat(int start, int count, const QColor &color)
507{
508 QTextCharFormat format;
509 format.setForeground(color);
510 setFormat(start, count, format);
511}
512
513/*!
514 \overload
515
516 The specified \a font is applied to the current text block from
517 the \a start position for a length of \a count characters.
518
519 The other attributes of the current text block, e.g. the font and
520 background color, are reset to default values.
521
522 \sa format(), highlightBlock()
523*/
524void QSyntaxHighlighter::setFormat(int start, int count, const QFont &font)
525{
526 QTextCharFormat format;
527 format.setFont(font);
528 setFormat(start, count, format);
529}
530
531/*!
532 \fn QTextCharFormat QSyntaxHighlighter::format(int position) const
533
534 Returns the format at \a position inside the syntax highlighter's
535 current text block.
536*/
537QTextCharFormat QSyntaxHighlighter::format(int pos) const
538{
539 Q_D(const QSyntaxHighlighter);
540 if (pos < 0 || pos >= d->formatChanges.count())
541 return QTextCharFormat();
542 return d->formatChanges.at(pos);
543}
544
545/*!
546 Returns the end state of the text block previous to the
547 syntax highlighter's current block. If no value was
548 previously set, the returned value is -1.
549
550 \sa highlightBlock(), setCurrentBlockState()
551*/
552int QSyntaxHighlighter::previousBlockState() const
553{
554 Q_D(const QSyntaxHighlighter);
555 if (!d->currentBlock.isValid())
556 return -1;
557
558 const QTextBlock previous = d->currentBlock.previous();
559 if (!previous.isValid())
560 return -1;
561
562 return previous.userState();
563}
564
565/*!
566 Returns the state of the current text block. If no value is set,
567 the returned value is -1.
568*/
569int QSyntaxHighlighter::currentBlockState() const
570{
571 Q_D(const QSyntaxHighlighter);
572 if (!d->currentBlock.isValid())
573 return -1;
574
575 return d->currentBlock.userState();
576}
577
578/*!
579 Sets the state of the current text block to \a newState.
580
581 \sa highlightBlock()
582*/
583void QSyntaxHighlighter::setCurrentBlockState(int newState)
584{
585 Q_D(QSyntaxHighlighter);
586 if (!d->currentBlock.isValid())
587 return;
588
589 d->currentBlock.setUserState(newState);
590}
591
592/*!
593 Attaches the given \a data to the current text block. The
594 ownership is passed to the underlying text document, i.e. the
595 provided QTextBlockUserData object will be deleted if the
596 corresponding text block gets deleted.
597
598 QTextBlockUserData can be used to store custom settings. In the
599 case of syntax highlighting, it is in particular interesting as
600 cache storage for information that you may figure out while
601 parsing the paragraph's text.
602
603 For example while parsing the text, you can keep track of
604 parenthesis characters that you encounter ('{[(' and the like),
605 and store their relative position and the actual QChar in a simple
606 class derived from QTextBlockUserData:
607
608 \snippet doc/src/snippets/code/src_gui_text_qsyntaxhighlighter.cpp 4
609
610 During cursor navigation in the associated editor, you can ask the
611 current QTextBlock (retrieved using the QTextCursor::block()
612 function) if it has a user data object set and cast it to your \c
613 BlockData object. Then you can check if the current cursor
614 position matches with a previously recorded parenthesis position,
615 and, depending on the type of parenthesis (opening or closing),
616 find the next opening or closing parenthesis on the same level.
617
618 In this way you can do a visual parenthesis matching and highlight
619 from the current cursor position to the matching parenthesis. That
620 makes it easier to spot a missing parenthesis in your code and to
621 find where a corresponding opening/closing parenthesis is when
622 editing parenthesis intensive code.
623
624 \sa QTextBlock::setUserData()
625*/
626void QSyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data)
627{
628 Q_D(QSyntaxHighlighter);
629 if (!d->currentBlock.isValid())
630 return;
631
632 d->currentBlock.setUserData(data);
633}
634
635/*!
636 Returns the QTextBlockUserData object previously attached to the
637 current text block.
638
639 \sa QTextBlock::userData(), setCurrentBlockUserData()
640*/
641QTextBlockUserData *QSyntaxHighlighter::currentBlockUserData() const
642{
643 Q_D(const QSyntaxHighlighter);
644 if (!d->currentBlock.isValid())
645 return 0;
646
647 return d->currentBlock.userData();
648}
649
650/*!
651 \since 4.4
652
653 Returns the current text block.
654*/
655QTextBlock QSyntaxHighlighter::currentBlock() const
656{
657 Q_D(const QSyntaxHighlighter);
658 return d->currentBlock;
659}
660
661QT_END_NAMESPACE
662
663#include "moc_qsyntaxhighlighter.cpp"
664
665#endif // QT_NO_SYNTAXHIGHLIGHTER
Note: See TracBrowser for help on using the repository browser.