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