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

Last change on this file since 563 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

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