1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtGui module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
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 ** 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 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "syntaxhighlighter.h"
43 #include "basetextdocumentlayout.h"
44 #include <qtextdocument.h>
45 #include <qtextlayout.h>
46 #include <qpointer.h>
47 #include <qtextobject.h>
48 #include <qtextcursor.h>
49 #include <qdebug.h>
50 #include <qtextedit.h>
51 #include <qtimer.h>
52
53 using namespace TextEditor;
54
55 class TextEditor::SyntaxHighlighterPrivate
56 {
57 SyntaxHighlighter *q_ptr;
58 Q_DECLARE_PUBLIC(SyntaxHighlighter)
59 public:
SyntaxHighlighterPrivate()60 inline SyntaxHighlighterPrivate()
61 : q_ptr(0), rehighlightPending(false), inReformatBlocks(false)
62 {}
63
64 QPointer<QTextDocument> doc;
65
66 void _q_reformatBlocks(int from, int charsRemoved, int charsAdded);
67 void reformatBlocks(int from, int charsRemoved, int charsAdded);
68 void reformatBlock(const QTextBlock &block, int from, int charsRemoved, int charsAdded);
69
rehighlight(QTextCursor & cursor,QTextCursor::MoveOperation operation)70 inline void rehighlight(QTextCursor &cursor, QTextCursor::MoveOperation operation) {
71 inReformatBlocks = true;
72 cursor.beginEditBlock();
73 int from = cursor.position();
74 cursor.movePosition(operation);
75 reformatBlocks(from, 0, cursor.position() - from);
76 cursor.endEditBlock();
77 inReformatBlocks = false;
78 }
79
_q_delayedRehighlight()80 inline void _q_delayedRehighlight() {
81 if (!rehighlightPending)
82 return;
83 rehighlightPending = false;
84 q_func()->rehighlight();
85 }
86
87 void applyFormatChanges(int from, int charsRemoved, int charsAdded);
88 QVector<QTextCharFormat> formatChanges;
89 QTextBlock currentBlock;
90 QList<SyntaxToken> tokens;
91 SyntaxComment comment;
92 bool rehighlightPending;
93 bool inReformatBlocks;
94 };
95
adjustRange(QTextLayout::FormatRange & range,int from,int charsRemoved,int charsAdded)96 static bool adjustRange(QTextLayout::FormatRange &range, int from, int charsRemoved, int charsAdded) {
97
98 if (range.start >= from) {
99 range.start += charsAdded - charsRemoved;
100 return true;
101 } else if (range.start + range.length > from) {
102 range.length += charsAdded - charsRemoved;
103 return true;
104 }
105 return false;
106 }
107
applyFormatChanges(int from,int charsRemoved,int charsAdded)108 void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, int charsAdded)
109 {
110 bool formatsChanged = false;
111
112 QTextLayout *layout = currentBlock.layout();
113
114 QList<QTextLayout::FormatRange> ranges = layout->additionalFormats();
115
116 bool doAdjustRange = currentBlock.contains(from);
117
118 QList<QTextLayout::FormatRange> old_ranges;
119
120 if (!ranges.isEmpty()) {
121 QList<QTextLayout::FormatRange>::Iterator it = ranges.begin();
122 while (it != ranges.end()) {
123 if (it->format.property(QTextFormat::UserProperty).toBool()) {
124 if (doAdjustRange)
125 formatsChanged = adjustRange(*it, from - currentBlock.position(), charsRemoved, charsAdded)
126 || formatsChanged;
127 ++it;
128 } else {
129 old_ranges.append(*it);
130 it = ranges.erase(it);
131 }
132 }
133 }
134
135 QTextCharFormat emptyFormat;
136
137 QTextLayout::FormatRange r;
138 r.start = -1;
139
140 QList<QTextLayout::FormatRange> new_ranges;
141 int i = 0;
142 while (i < formatChanges.count()) {
143
144 while (i < formatChanges.count() && formatChanges.at(i) == emptyFormat)
145 ++i;
146
147 if (i >= formatChanges.count())
148 break;
149
150 r.start = i;
151 r.format = formatChanges.at(i);
152
153 while (i < formatChanges.count() && formatChanges.at(i) == r.format)
154 ++i;
155
156 if (i >= formatChanges.count())
157 break;
158
159 r.length = i - r.start;
160
161 new_ranges << r;
162 r.start = -1;
163 }
164
165 if (r.start != -1) {
166 r.length = formatChanges.count() - r.start;
167
168 new_ranges << r;
169 }
170
171 formatsChanged = formatsChanged || (new_ranges.size() != old_ranges.size());
172
173 for (int i = 0; !formatsChanged && i < new_ranges.size(); ++i) {
174 const QTextLayout::FormatRange &o = old_ranges.at(i);
175 const QTextLayout::FormatRange &n = new_ranges.at(i);
176 formatsChanged = (o.start != n.start || o.length != n.length || o.format != n.format);
177 }
178
179 if (formatsChanged) {
180 ranges.append(new_ranges);
181 layout->setAdditionalFormats(ranges);
182 doc->markContentsDirty(currentBlock.position(), currentBlock.length());
183 }
184 }
185
_q_reformatBlocks(int from,int charsRemoved,int charsAdded)186 void SyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
187 {
188 if (!inReformatBlocks)
189 reformatBlocks(from, charsRemoved, charsAdded);
190 }
191
reformatBlocks(int from,int charsRemoved,int charsAdded)192 void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded)
193 {
194 rehighlightPending = false;
195
196 QTextBlock block = doc->findBlock(from);
197 if (!block.isValid())
198 return;
199
200 int endPosition;
201 QTextBlock lastBlock = doc->findBlock(from + charsAdded + (charsRemoved > 0 ? 1 : 0));
202 if (lastBlock.isValid())
203 endPosition = lastBlock.position() + lastBlock.length();
204 else
205 endPosition = doc->lastBlock().position() + doc->lastBlock().length(); //doc->docHandle()->length();
206
207 bool forceHighlightOfNextBlock = false;
208
209 while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
210 const int stateBeforeHighlight = block.userState();
211
212 reformatBlock(block, from, charsRemoved, charsAdded);
213
214 forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight);
215
216 block = block.next();
217 }
218
219 formatChanges.clear();
220 }
221
reformatBlock(const QTextBlock & block,int from,int charsRemoved,int charsAdded)222 void SyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block, int from, int charsRemoved, int charsAdded)
223 {
224 Q_Q(SyntaxHighlighter);
225
226 Q_ASSERT_X(!currentBlock.isValid(), "SyntaxHighlighter::reformatBlock()", "reFormatBlock() called recursively");
227
228 currentBlock = block;
229 tokens.clear();
230 formatChanges.fill(QTextCharFormat(), block.length() - 1);
231 q->highlightBlock(block.text());
232
233 BaseTextDocumentLayout::userData(block)->setTokens(tokens);
234 BaseTextDocumentLayout::setLexerState(block,q->currentBlockState());
235
236 applyFormatChanges(from, charsRemoved, charsAdded);
237
238 currentBlock = QTextBlock();
239 }
240
241 const SyntaxHighlighter::KateFormatMap SyntaxHighlighter::m_kateFormats;
242
KateFormatMap()243 SyntaxHighlighter::KateFormatMap::KateFormatMap()
244 {
245 m_ids.insert(QLatin1String("dsNormal"), SyntaxHighlighter::Normal);
246 m_ids.insert(QLatin1String("dsKeyword"), SyntaxHighlighter::Keyword);
247 m_ids.insert(QLatin1String("dsDataType"), SyntaxHighlighter::DataType);
248 m_ids.insert(QLatin1String("dsDecVal"), SyntaxHighlighter::Decimal);
249 m_ids.insert(QLatin1String("dsBaseN"), SyntaxHighlighter::BaseN);
250 m_ids.insert(QLatin1String("dsFloat"), SyntaxHighlighter::Float);
251 m_ids.insert(QLatin1String("dsChar"), SyntaxHighlighter::Char);
252 m_ids.insert(QLatin1String("dsString"), SyntaxHighlighter::String);
253 m_ids.insert(QLatin1String("dsComment"), SyntaxHighlighter::Comment);
254 m_ids.insert(QLatin1String("dsOthers"), SyntaxHighlighter::Others);
255 m_ids.insert(QLatin1String("dsAlert"), SyntaxHighlighter::Alert);
256 m_ids.insert(QLatin1String("dsFunction"), SyntaxHighlighter::Function);
257 m_ids.insert(QLatin1String("dsRegionMarker"), SyntaxHighlighter::RegionMarker);
258 m_ids.insert(QLatin1String("dsError"), SyntaxHighlighter::Error);
259 m_ids.insert(QLatin1String("dsSymbol"),SyntaxHighlighter::Symbol);
260 m_ids.insert(QLatin1String("dsBuiltinFunc"), SyntaxHighlighter::BuiltinFunc);
261 m_ids.insert(QLatin1String("dsPredeclared"), SyntaxHighlighter::Predeclared);
262 m_ids.insert(QLatin1String("dsFuncDecl"), SyntaxHighlighter::FuncDecl);
263 m_ids.insert(QLatin1String("dsPlaceholder"), SyntaxHighlighter::Placeholder);
264 m_ids.insert(QLatin1String("dsToDo"), SyntaxHighlighter::ToDo);
265 m_ids.insert(QLatin1String("dsPreprocessorFormat"),SyntaxHighlighter::PreprocessorFormat);
266 }
267
configureFormat(TextFormatId id,const QTextCharFormat & format)268 void SyntaxHighlighter::configureFormat(TextFormatId id, const QTextCharFormat &format)
269 {
270 m_creatorFormats[id] = format;
271 }
272
setTabSize(int)273 void SyntaxHighlighter::setTabSize(int /*tabSize*/)
274 {
275 }
276
comment() const277 SyntaxComment SyntaxHighlighter::comment() const
278 {
279 Q_D(const SyntaxHighlighter);
280 return d->comment;
281 }
282
setupComment(const SyntaxComment & comment)283 void SyntaxHighlighter::setupComment(const SyntaxComment &comment)
284 {
285 Q_D(SyntaxHighlighter);
286 d->comment = comment;
287 }
288
289 /*!
290 \class SyntaxHighlighter
291 \reentrant
292
293 \brief The SyntaxHighlighter class allows you to define syntax
294 highlighting rules, and in addition you can use the class to query
295 a document's current formatting or user data.
296
297 \since 4.1
298
299 \ingroup richtext-processing
300
301 The SyntaxHighlighter class is a base class for implementing
302 QTextEdit syntax highlighters. A syntax highligher automatically
303 highlights parts of the text in a QTextEdit, or more generally in
304 a QTextDocument. Syntax highlighters are often used when the user
305 is entering text in a specific format (for example source code)
306 and help the user to read the text and identify syntax errors.
307
308 To provide your own syntax highlighting, you must subclass
309 SyntaxHighlighter and reimplement highlightBlock().
310
311 When you create an instance of your SyntaxHighlighter subclass,
312 pass it the QTextEdit or QTextDocument that you want the syntax
313 highlighting to be applied to. For example:
314
315 \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 0
316
317 After this your highlightBlock() function will be called
318 automatically whenever necessary. Use your highlightBlock()
319 function to apply formatting (e.g. setting the font and color) to
320 the text that is passed to it. SyntaxHighlighter provides the
321 setFormat() function which applies a given QTextCharFormat on
322 the current text block. For example:
323
324 \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 1
325
326 Some syntaxes can have constructs that span several text
327 blocks. For example, a C++ syntax highlighter should be able to
328 cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
329 these cases it is necessary to know the end state of the previous
330 text block (e.g. "in comment").
331
332 Inside your highlightBlock() implementation you can query the end
333 state of the previous text block using the previousBlockState()
334 function. After parsing the block you can save the last state
335 using setCurrentBlockState().
336
337 The currentBlockState() and previousBlockState() functions return
338 an int value. If no state is set, the returned value is -1. You
339 can designate any other value to identify any given state using
340 the setCurrentBlockState() function. Once the state is set the
341 QTextBlock keeps that value until it is set set again or until the
342 corresponding paragraph of text is deleted.
343
344 For example, if you're writing a simple C++ syntax highlighter,
345 you might designate 1 to signify "in comment":
346
347 \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 2
348
349 In the example above, we first set the current block state to
350 0. Then, if the previous block ended within a comment, we higlight
351 from the beginning of the current block (\c {startIndex =
352 0}). Otherwise, we search for the given start expression. If the
353 specified end expression cannot be found in the text block, we
354 change the current block state by calling setCurrentBlockState(),
355 and make sure that the rest of the block is higlighted.
356
357 In addition you can query the current formatting and user data
358 using the format() and currentBlockUserData() functions
359 respectively. You can also attach user data to the current text
360 block using the setCurrentBlockUserData() function.
361 QTextBlockUserData can be used to store custom settings. In the
362 case of syntax highlighting, it is in particular interesting as
363 cache storage for information that you may figure out while
364 parsing the paragraph's text. For an example, see the
365 setCurrentBlockUserData() documentation.
366
367 \sa QTextEdit, {Syntax Highlighter Example}
368 */
369
370 /*!
371 Constructs a SyntaxHighlighter with the given \a parent.
372 */
SyntaxHighlighter(QObject * parent)373 SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
374 : QObject(parent), d_ptr(new SyntaxHighlighterPrivate)
375 {
376 d_ptr->q_ptr = this;
377 }
378
379 /*!
380 Constructs a SyntaxHighlighter and installs it on \a parent.
381 The specified QTextDocument also becomes the owner of the
382 SyntaxHighlighter.
383 */
SyntaxHighlighter(QTextDocument * parent)384 SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent)
385 : QObject(parent), d_ptr(new SyntaxHighlighterPrivate)
386 {
387 d_ptr->q_ptr = this;
388 setDocument(parent);
389 }
390
391 /*!
392 Constructs a SyntaxHighlighter and installs it on \a parent 's
393 QTextDocument. The specified QTextEdit also becomes the owner of
394 the SyntaxHighlighter.
395 */
SyntaxHighlighter(QTextEdit * parent)396 SyntaxHighlighter::SyntaxHighlighter(QTextEdit *parent)
397 : QObject(parent), d_ptr(new SyntaxHighlighterPrivate)
398 {
399 d_ptr->q_ptr = this;
400 setDocument(parent->document());
401 }
402
403 /*!
404 Destructor. Uninstalls this syntax highlighter from the text document.
405 */
~SyntaxHighlighter()406 SyntaxHighlighter::~SyntaxHighlighter()
407 {
408 setDocument(0);
409 }
410
411 /*!
412 Installs the syntax highlighter on the given QTextDocument \a doc.
413 A SyntaxHighlighter can only be used with one document at a time.
414 */
setDocument(QTextDocument * doc)415 void SyntaxHighlighter::setDocument(QTextDocument *doc)
416 {
417 Q_D(SyntaxHighlighter);
418 if (d->doc) {
419 disconnect(d->doc, SIGNAL(contentsChange(int,int,int)),
420 this, SLOT(_q_reformatBlocks(int,int,int)));
421
422 QTextCursor cursor(d->doc);
423 cursor.beginEditBlock();
424 for (QTextBlock blk = d->doc->begin(); blk.isValid(); blk = blk.next())
425 blk.layout()->clearAdditionalFormats();
426 cursor.endEditBlock();
427 }
428 d->doc = doc;
429 if (d->doc) {
430 connect(d->doc, SIGNAL(contentsChange(int,int,int)),
431 this, SLOT(_q_reformatBlocks(int,int,int)));
432 d->rehighlightPending = true;
433 QTimer::singleShot(0, this, SLOT(_q_delayedRehighlight()));
434 }
435 }
436
437 /*!
438 Returns the QTextDocument on which this syntax highlighter is
439 installed.
440 */
document() const441 QTextDocument *SyntaxHighlighter::document() const
442 {
443 Q_D(const SyntaxHighlighter);
444 return d->doc;
445 }
446
447 /*!
448 \since 4.2
449
450 Reapplies the highlighting to the whole document.
451
452 \sa rehighlightBlock()
453 */
rehighlight()454 void SyntaxHighlighter::rehighlight()
455 {
456 Q_D(SyntaxHighlighter);
457 if (!d->doc)
458 return;
459
460 QTextCursor cursor(d->doc);
461 d->rehighlight(cursor, QTextCursor::End);
462 }
463
464 /*!
465 \since 4.6
466
467 Reapplies the highlighting to the given QTextBlock \a block.
468
469 \sa rehighlight()
470 */
rehighlightBlock(const QTextBlock & block)471 void SyntaxHighlighter::rehighlightBlock(const QTextBlock &block)
472 {
473 Q_D(SyntaxHighlighter);
474 if (!d->doc || !block.isValid() || block.document() != d->doc)
475 return;
476
477 const bool rehighlightPending = d->rehighlightPending;
478
479 QTextCursor cursor(block);
480 d->rehighlight(cursor, QTextCursor::EndOfBlock);
481
482 if (rehighlightPending)
483 d->rehighlightPending = rehighlightPending;
484 }
485
486 /*!
487 \fn void SyntaxHighlighter::highlightBlock(const QString &text)
488
489 Highlights the given text block. This function is called when
490 necessary by the rich text engine, i.e. on text blocks which have
491 changed.
492
493 To provide your own syntax highlighting, you must subclass
494 SyntaxHighlighter and reimplement highlightBlock(). In your
495 reimplementation you should parse the block's \a text and call
496 setFormat() as often as necessary to apply any font and color
497 changes that you require. For example:
498
499 \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 3
500
501 Some syntaxes can have constructs that span several text
502 blocks. For example, a C++ syntax highlighter should be able to
503 cope with \c{/}\c{*...*}\c{/} multiline comments. To deal with
504 these cases it is necessary to know the end state of the previous
505 text block (e.g. "in comment").
506
507 Inside your highlightBlock() implementation you can query the end
508 state of the previous text block using the previousBlockState()
509 function. After parsing the block you can save the last state
510 using setCurrentBlockState().
511
512 The currentBlockState() and previousBlockState() functions return
513 an int value. If no state is set, the returned value is -1. You
514 can designate any other value to identify any given state using
515 the setCurrentBlockState() function. Once the state is set the
516 QTextBlock keeps that value until it is set set again or until the
517 corresponding paragraph of text gets deleted.
518
519 For example, if you're writing a simple C++ syntax highlighter,
520 you might designate 1 to signify "in comment". For a text block
521 that ended in the middle of a comment you'd set 1 using
522 setCurrentBlockState, and for other paragraphs you'd set 0.
523 In your parsing code if the return value of previousBlockState()
524 is 1, you would highlight the text as a C++ comment until you
525 reached the closing \c{*}\c{/}.
526
527 \sa previousBlockState(), setFormat(), setCurrentBlockState()
528 */
529
530 /*!
531 This function is applied to the syntax highlighter's current text
532 block (i.e. the text that is passed to the highlightBlock()
533 function).
534
535 The specified \a format is applied to the text from the \a start
536 position for a length of \a count characters (if \a count is 0,
537 nothing is done). The formatting properties set in \a format are
538 merged at display time with the formatting information stored
539 directly in the document, for example as previously set with
540 QTextCursor's functions. Note that the document itself remains
541 unmodified by the format set through this function.
542
543 \sa format(), highlightBlock()
544 */
setFormat(int start,int count,const QTextCharFormat & format,int id)545 void SyntaxHighlighter::setFormat(int start, int count, const QTextCharFormat &format, int id)
546 {
547 Q_D(SyntaxHighlighter);
548 if (start < 0 || start >= d->formatChanges.count())
549 return;
550
551 const int end = qMin(start + count, d->formatChanges.count());
552 for (int i = start; i < end; ++i)
553 d->formatChanges[i] = format;
554
555 if (id >= Normal) {
556 int offset = start;
557 int count = end-start;
558 if (!d->tokens.empty()) {
559 SyntaxToken &last = d->tokens.last();
560 if ((last.id == id) && (last.offset+last.count == offset)) {
561 last.count += count;
562 return;
563 }
564 }
565 SyntaxToken token;
566 token.offset = offset;
567 token.count = count;
568 token.id = id;
569 d->tokens.append(token);
570 }
571 }
572
573 /*!
574 \overload
575
576 The specified \a color is applied to the current text block from
577 the \a start position for a length of \a count characters.
578
579 The other attributes of the current text block, e.g. the font and
580 background color, are reset to default values.
581
582 \sa format(), highlightBlock()
583 */
584 //void SyntaxHighlighter::setFormat(int start, int count, const QColor &color)
585 //{
586 // QTextCharFormat format;
587 // format.setForeground(color);
588 // setFormat(start, count, format);
589 //}
590
591 /*!
592 \overload
593
594 The specified \a font is applied to the current text block from
595 the \a start position for a length of \a count characters.
596
597 The other attributes of the current text block, e.g. the font and
598 background color, are reset to default values.
599
600 \sa format(), highlightBlock()
601 */
602 //void SyntaxHighlighter::setFormat(int start, int count, const QFont &font)
603 //{
604 // QTextCharFormat format;
605 // format.setFont(font);
606 // setFormat(start, count, format);
607 //}
608
applyFormatToSpaces(const QString & text,const QTextCharFormat & format)609 void SyntaxHighlighter::applyFormatToSpaces(const QString &text, const QTextCharFormat &format)
610 {
611 int offset = 0;
612 const int length = text.length();
613 while (offset < length) {
614 if (text.at(offset).isSpace()) {
615 int start = offset++;
616 while (offset < length && text.at(offset).isSpace())
617 ++offset;
618 setFormat(start, offset - start, format);
619 } else {
620 ++offset;
621 }
622 }
623 }
624
625 /*!
626 \fn QTextCharFormat SyntaxHighlighter::format(int position) const
627
628 Returns the format at \a position inside the syntax highlighter's
629 current text block.
630 */
format(int pos) const631 QTextCharFormat SyntaxHighlighter::format(int pos) const
632 {
633 Q_D(const SyntaxHighlighter);
634 if (pos < 0 || pos >= d->formatChanges.count())
635 return QTextCharFormat();
636 return d->formatChanges.at(pos);
637 }
638
639 /*!
640 Returns the end state of the text block previous to the
641 syntax highlighter's current block. If no value was
642 previously set, the returned value is -1.
643
644 \sa highlightBlock(), setCurrentBlockState()
645 */
previousBlockState() const646 int SyntaxHighlighter::previousBlockState() const
647 {
648 Q_D(const SyntaxHighlighter);
649 if (!d->currentBlock.isValid())
650 return -1;
651
652 const QTextBlock previous = d->currentBlock.previous();
653 if (!previous.isValid())
654 return -1;
655
656 return previous.userState();
657 }
658
659 /*!
660 Returns the state of the current text block. If no value is set,
661 the returned value is -1.
662 */
currentBlockState() const663 int SyntaxHighlighter::currentBlockState() const
664 {
665 Q_D(const SyntaxHighlighter);
666 if (!d->currentBlock.isValid())
667 return -1;
668
669 return d->currentBlock.userState();
670 }
671
672 /*!
673 Sets the state of the current text block to \a newState.
674
675 \sa highlightBlock()
676 */
setCurrentBlockState(int newState)677 void SyntaxHighlighter::setCurrentBlockState(int newState)
678 {
679 Q_D(SyntaxHighlighter);
680 if (!d->currentBlock.isValid())
681 return;
682
683 d->currentBlock.setUserState(newState);
684 }
685
686 /*!
687 Attaches the given \a data to the current text block. The
688 ownership is passed to the underlying text document, i.e. the
689 provided QTextBlockUserData object will be deleted if the
690 corresponding text block gets deleted.
691
692 QTextBlockUserData can be used to store custom settings. In the
693 case of syntax highlighting, it is in particular interesting as
694 cache storage for information that you may figure out while
695 parsing the paragraph's text.
696
697 For example while parsing the text, you can keep track of
698 parenthesis characters that you encounter ('{[(' and the like),
699 and store their relative position and the actual QChar in a simple
700 class derived from QTextBlockUserData:
701
702 \snippet doc/src/snippets/code/src_gui_text_SyntaxHighlighter.cpp 4
703
704 During cursor navigation in the associated editor, you can ask the
705 current QTextBlock (retrieved using the QTextCursor::block()
706 function) if it has a user data object set and cast it to your \c
707 BlockData object. Then you can check if the current cursor
708 position matches with a previously recorded parenthesis position,
709 and, depending on the type of parenthesis (opening or closing),
710 find the next opening or closing parenthesis on the same level.
711
712 In this way you can do a visual parenthesis matching and highlight
713 from the current cursor position to the matching parenthesis. That
714 makes it easier to spot a missing parenthesis in your code and to
715 find where a corresponding opening/closing parenthesis is when
716 editing parenthesis intensive code.
717
718 \sa QTextBlock::setUserData()
719 */
setCurrentBlockUserData(QTextBlockUserData * data)720 void SyntaxHighlighter::setCurrentBlockUserData(QTextBlockUserData *data)
721 {
722 Q_D(SyntaxHighlighter);
723 if (!d->currentBlock.isValid())
724 return;
725
726 d->currentBlock.setUserData(data);
727 }
728
729 /*!
730 Returns the QTextBlockUserData object previously attached to the
731 current text block.
732
733 \sa QTextBlock::userData(), setCurrentBlockUserData()
734 */
currentBlockUserData() const735 QTextBlockUserData *SyntaxHighlighter::currentBlockUserData() const
736 {
737 Q_D(const SyntaxHighlighter);
738 if (!d->currentBlock.isValid())
739 return 0;
740
741 return d->currentBlock.userData();
742 }
743
744 /*!
745 \since 4.4
746
747 Returns the current text block.
748 */
currentBlock() const749 QTextBlock SyntaxHighlighter::currentBlock() const
750 {
751 Q_D(const SyntaxHighlighter);
752 return d->currentBlock;
753 }
754
byStartOfRange(const QTextLayout::FormatRange & range,const QTextLayout::FormatRange & other)755 static bool byStartOfRange(const QTextLayout::FormatRange &range, const QTextLayout::FormatRange &other)
756 {
757 return range.start < other.start;
758 }
759
setExtraAdditionalFormats(const QTextBlock & block,const QList<QTextLayout::FormatRange> & fmts)760 void SyntaxHighlighter::setExtraAdditionalFormats(const QTextBlock& block,
761 const QList<QTextLayout::FormatRange> &fmts)
762 {
763
764 // qDebug() << "setAdditionalFormats() on block" << block.blockNumber();
765 // for (int i = 0; i < overrides.count(); ++i)
766 // qDebug() << " from " << overrides.at(i).start << "length"
767 // << overrides.at(i).length
768 // << "color:" << overrides.at(i).format.foreground().color();
769 Q_D(SyntaxHighlighter);
770
771 if (block.layout() == 0)
772 return;
773
774 QList<QTextLayout::FormatRange> formats;
775 formats.reserve(fmts.size());
776 foreach (QTextLayout::FormatRange r, fmts) {
777 r.format.setProperty(QTextFormat::UserProperty, true);
778 formats.append(r);
779 }
780 qSort(formats.begin(), formats.end(), byStartOfRange);
781
782 QList<QTextLayout::FormatRange> previousSemanticFormats;
783 QList<QTextLayout::FormatRange> formatsToApply;
784
785 const QList<QTextLayout::FormatRange> all = block.layout()->additionalFormats();
786 foreach (const QTextLayout::FormatRange &r, all) {
787 if (r.format.hasProperty(QTextFormat::UserProperty))
788 previousSemanticFormats.append(r);
789 else
790 formatsToApply.append(r);
791 }
792
793 if (formats.size() == previousSemanticFormats.size()) {
794 qSort(previousSemanticFormats.begin(), previousSemanticFormats.end(), byStartOfRange);
795
796 int index = 0;
797 for (; index != formats.size(); ++index) {
798 const QTextLayout::FormatRange &range = formats.at(index);
799 const QTextLayout::FormatRange &previousRange = previousSemanticFormats.at(index);
800
801 if (range.start != previousRange.start ||
802 range.length != previousRange.length ||
803 range.format != previousRange.format)
804 break;
805 }
806
807 if (index == formats.size())
808 return;
809 }
810
811 formatsToApply += formats;
812
813 bool wasInReformatBlocks = d->inReformatBlocks;
814 d->inReformatBlocks = true;
815 block.layout()->setAdditionalFormats(formatsToApply);
816 document()->markContentsDirty(block.position(), block.length()-1);
817 d->inReformatBlocks = wasInReformatBlocks;
818 }
819
820 #include "moc_syntaxhighlighter.cpp"
821