1
/****************************************************************************
2
**
3
** Copyright (C) 2012 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
** GNU Lesser General Public License Usage
11
** This file may be used under the terms of the GNU Lesser General Public
12
** License version 2.1 as published by the Free Software Foundation and
13
** appearing in the file LICENSE.LGPL included in the packaging of this
14
** file. Please review the following information to ensure the GNU Lesser
15
** General Public License version 2.1 requirements will be met:
16
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
**
18
** In addition, as a special exception, Nokia gives you certain additional
19
** rights. These rights are described in the Nokia Qt LGPL Exception
20
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
**
22
** GNU General Public License Usage
23
** Alternatively, this file may be used under the terms of the GNU General
24
** Public License version 3.0 as published by the Free Software Foundation
25
** and appearing in the file LICENSE.GPL included in the packaging of this
26
** file. Please review the following information to ensure the GNU General
27
** Public License version 3.0 requirements will be met:
28
** http://www.gnu.org/copyleft/gpl.html.
29
**
30
** Other Usage
31
** Alternatively, this file may be used in accordance with the terms and
32
** conditions contained in a signed written agreement between you and Nokia.
33
**
34
**
35
**
36
**
37
**
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
56
QT_BEGIN_NAMESPACE
57
58
class QSyntaxHighlighterPrivate : public QObjectPrivate
59
{
60
    Q_DECLARE_PUBLIC(QSyntaxHighlighter)
61
public:
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
96
void 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
180
void QSyntaxHighlighterPrivate::_q_reformatBlocks(int from, int charsRemoved, int charsAdded)
181
{
182
    if (!inReformatBlocks)
183
        reformatBlocks(from, charsRemoved, charsAdded);
184
}
185
186
void 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
216
void 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
*/
315
QSyntaxHighlighter::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
*/
325
QSyntaxHighlighter::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
*/
336
QSyntaxHighlighter::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
*/
345
QSyntaxHighlighter::~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
*/
354
void 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
*/
380
QTextDocument *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
*/
393
void 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
*/
410
void 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
*/
484
void 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
*/
506
void 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
*/
524
void 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
*/
537
QTextCharFormat 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
*/
552
int 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
*/
569
int 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
*/
583
void 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
*/
626
void 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
*/
641
QTextBlockUserData *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
*/
655
QTextBlock QSyntaxHighlighter::currentBlock() const
656
{
657
    Q_D(const QSyntaxHighlighter);
658
    return d->currentBlock;
659
}
660
661
QT_END_NAMESPACE
662
663
#include "moc_qsyntaxhighlighter.cpp"
664
665
#endif // QT_NO_SYNTAXHIGHLIGHTER