cpphighlighter.cpp 13.6 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "cpphighlighter.h"
31
#include <cpptools/cppdoxygen.h>
con's avatar
con committed
32
33
34
35
36
37
38
39
40
41
42
43

#include <Token.h>
#include <cplusplus/SimpleLexer.h>
#include <texteditor/basetexteditor.h>

#include <QtGui/QTextDocument>
#include <QtCore/QDebug>

using namespace CppEditor::Internal;
using namespace TextEditor;
using namespace CPlusPlus;

44
CppHighlighter::CppHighlighter(QTextDocument *document) :
con's avatar
con committed
45
46
47
48
    QSyntaxHighlighter(document)
{
}

49
void CppHighlighter::highlightBlock(const QString &text)
con's avatar
con committed
50
51
{
    const int previousState = previousBlockState();
52
    int state = 0, initialBraceDepth = 0;
con's avatar
con committed
53
54
    if (previousState != -1) {
        state = previousState & 0xff;
55
        initialBraceDepth = previousState >> 8;
con's avatar
con committed
56
57
    }

58
    int braceDepth = initialBraceDepth;
59

con's avatar
con committed
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    SimpleLexer tokenize;
    tokenize.setQtMocRunEnabled(false);

    int initialState = state;
    const QList<SimpleToken> tokens = tokenize(text, initialState);
    state = tokenize.state(); // refresh the state

    if (tokens.isEmpty()) {
        setCurrentBlockState(previousState);
        if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) {
            userData->setClosingCollapseMode(TextBlockUserData::NoClosingCollapse);
            userData->setCollapseMode(TextBlockUserData::NoCollapse);
        }
        TextEditDocumentLayout::clearParentheses(currentBlock());
74
        if (text.length()) // the empty line can still contain whitespace
mae's avatar
mae committed
75
            setFormat(0, text.length(), m_formats[CppVisualWhitespace]);
con's avatar
con committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
        return;
    }

    const int firstNonSpace = tokens.first().position();

    Parentheses parentheses;
    parentheses.reserve(20); // assume wizard level ;-)

    bool highlightAsPreprocessor = false;

    for (int i = 0; i < tokens.size(); ++i) {
        const SimpleToken &tk = tokens.at(i);

        int previousTokenEnd = 0;
        if (i != 0) {
            // mark the whitespaces
            previousTokenEnd = tokens.at(i - 1).position() +
                               tokens.at(i - 1).length();
        }

        if (previousTokenEnd != tk.position()) {
            setFormat(previousTokenEnd, tk.position() - previousTokenEnd,
mae's avatar
mae committed
98
                      m_formats[CppVisualWhitespace]);
con's avatar
con committed
99
100
101
102
103
104
105
106
107
108
        }

        if (tk.is(T_LPAREN) || tk.is(T_LBRACE) || tk.is(T_LBRACKET)) {
            const QChar c(tk.text().at(0));
            parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.position()));
            if (tk.is(T_LBRACE))
                ++braceDepth;
        } else if (tk.is(T_RPAREN) || tk.is(T_RBRACE) || tk.is(T_RBRACKET)) {
            const QChar c(tk.text().at(0));
            parentheses.append(Parenthesis(Parenthesis::Closed, c, tk.position()));
109
110
            if (tk.is(T_RBRACE))
                --braceDepth;
con's avatar
con committed
111
112
113
        }

        bool highlightCurrentWordAsPreprocessor = highlightAsPreprocessor;
114

con's avatar
con committed
115
116
117
118
        if (highlightAsPreprocessor)
            highlightAsPreprocessor = false;

        if (i == 0 && tk.is(T_POUND)) {
119
            highightLine(text, tk.position(), tk.length(), m_formats[CppPreprocessorFormat]);
con's avatar
con committed
120
            highlightAsPreprocessor = true;
121

con's avatar
con committed
122
123
124
        } else if (highlightCurrentWordAsPreprocessor &&
                   (tk.isKeyword() || tk.is(T_IDENTIFIER)) && isPPKeyword(tk.text()))
            setFormat(tk.position(), tk.length(), m_formats[CppPreprocessorFormat]);
125

126
        else if (tk.is(T_NUMERIC_LITERAL))
con's avatar
con committed
127
            setFormat(tk.position(), tk.length(), m_formats[CppNumberFormat]);
128

129
130
        else if (tk.is(T_STRING_LITERAL) || tk.is(T_CHAR_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL) ||
                 tk.is(T_AT_STRING_LITERAL))
131
            highightLine(text, tk.position(), tk.length(), m_formats[CppStringFormat]);
132

con's avatar
con committed
133
        else if (tk.is(T_WIDE_STRING_LITERAL) || tk.is(T_WIDE_CHAR_LITERAL))
134
            highightLine(text, tk.position(), tk.length(), m_formats[CppStringFormat]);
135
136
137

        else if (tk.isComment()) {

138
            if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT))
139
                highightLine(text, tk.position(), tk.length(), m_formats[CppCommentFormat]);
140
141
142
143

            else // a doxygen comment
                highlightDoxygenComment(text, tk.position(), tk.length());

con's avatar
con committed
144
145
146
147
148
149
150
151
152
153
154
155
156
            // we need to insert a close comment parenthesis, if
            //  - the line starts in a C Comment (initalState != 0)
            //  - the first token of the line is a T_COMMENT (i == 0 && tk.is(T_COMMENT))
            //  - is not a continuation line (tokens.size() > 1 || ! state)
            if (initialState && i == 0 && (tokens.size() > 1 || ! state)) {
                --braceDepth;

                const int tokenEnd = tk.position() + tk.length() - 1;
                parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));

                // clear the initial state.
                initialState = 0;
            }
157

158
        } else if (tk.isKeyword() || isQtKeyword(tk.text()) || tk.isObjCAtKeyword())
con's avatar
con committed
159
            setFormat(tk.position(), tk.length(), m_formats[CppKeywordFormat]);
160

con's avatar
con committed
161
162
        else if (tk.isOperator())
            setFormat(tk.position(), tk.length(), m_formats[CppOperatorFormat]);
163

con's avatar
con committed
164
165
        else if (i == 0 && tokens.size() > 1 && tk.is(T_IDENTIFIER) && tokens.at(1).is(T_COLON))
            setFormat(tk.position(), tk.length(), m_formats[CppLabelFormat]);
166

con's avatar
con committed
167
168
169
170
171
        else if (tk.is(T_IDENTIFIER))
            highlightWord(tk.text(), tk.position(), tk.length());
    }

    // mark the trailing white spaces
172
    {
con's avatar
con committed
173
174
175
        const SimpleToken tk = tokens.last();
        const int lastTokenEnd = tk.position() + tk.length();
        if (text.length() > lastTokenEnd)
mae's avatar
mae committed
176
            setFormat(lastTokenEnd, text.length() - lastTokenEnd, m_formats[CppVisualWhitespace]);
con's avatar
con committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    }

    if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) {
        userData->setClosingCollapseMode(TextBlockUserData::NoClosingCollapse);
        userData->setCollapseMode(TextBlockUserData::NoCollapse);
    }

    if (! initialState && state && ! tokens.isEmpty()) {
        parentheses.append(Parenthesis(Parenthesis::Opened, QLatin1Char('+'),
                                       tokens.last().position()));
        ++braceDepth;
    }

    QChar c;
    int collapse = Parenthesis::collapseAtPos(parentheses, &c);
    if (collapse >= 0) {
        TextBlockUserData::CollapseMode collapseMode = TextBlockUserData::CollapseAfter;
        if (collapse == firstNonSpace && c != QLatin1Char('+'))
            collapseMode = TextBlockUserData::CollapseThis;
        TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(collapseMode);
    }


    int cc = Parenthesis::closeCollapseAtPos(parentheses);
    if (cc >= 0) {
        TextBlockUserData *userData = TextEditDocumentLayout::userData(currentBlock());
        userData->setClosingCollapseMode(TextBlockUserData::ClosingCollapse);
        QString trailingText = text.mid(cc+1).simplified();
        if (trailingText.isEmpty() || trailingText == QLatin1String(";")) {
            userData->setClosingCollapseMode(TextBlockUserData::ClosingCollapseAtEnd);
        }
    }

    TextEditDocumentLayout::setParentheses(currentBlock(), parentheses);

212
213
214
215
    // if the block is ifdefed out, we only store the parentheses, but
    // do not adjust the brace depth.
    if (TextEditDocumentLayout::ifdefedOut(currentBlock()))
        braceDepth = initialBraceDepth;
216
217
218
219
220
221
222
223
224
225

    // optimization: if only the brace depth changes, we adjust subsequent blocks
    // to have QSyntaxHighlighter stop the rehighlighting
    int currentState = currentBlockState();
    if (currentState != -1) {
        int oldState = currentState & 0xff;
        int oldBraceDepth = currentState >> 8;
        if (oldState == tokenize.state() && oldBraceDepth != braceDepth) {
            int delta = braceDepth - oldBraceDepth;
            QTextBlock block = currentBlock().next();
226
            while (block.isValid() && block.userState() != -1) {
227
                TextEditDocumentLayout::changeBraceDepth(block, delta);
228
229
230
231
232
                block = block.next();
            }
        }
    }

con's avatar
con committed
233
234
235
236
    setCurrentBlockState((braceDepth << 8) | tokenize.state());
}


237
bool CppHighlighter::isPPKeyword(const QStringRef &text) const
con's avatar
con committed
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
{
    switch (text.length())
    {
    case 2:
        if (text.at(0) == 'i' && text.at(1) == 'f')
            return true;
        break;

    case 4:
        if (text.at(0) == 'e' && text == QLatin1String("elif"))
            return true;
        else if (text.at(0) == 'e' && text == QLatin1String("else"))
            return true;
        break;

    case 5:
        if (text.at(0) == 'i' && text == QLatin1String("ifdef"))
            return true;
        else if (text.at(0) == 'u' && text == QLatin1String("undef"))
            return true;
        else if (text.at(0) == 'e' && text == QLatin1String("endif"))
            return true;
        else if (text.at(0) == 'e' && text == QLatin1String("error"))
            return true;
        break;

    case 6:
        if (text.at(0) == 'i' && text == QLatin1String("ifndef"))
            return true;
267
268
        if (text.at(0) == 'i' && text == QLatin1String("import"))
            return true;
con's avatar
con committed
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
        else if (text.at(0) == 'd' && text == QLatin1String("define"))
            return true;
        else if (text.at(0) == 'p' && text == QLatin1String("pragma"))
            return true;
        break;

    case 7:
        if (text.at(0) == 'i' && text == QLatin1String("include"))
            return true;
        else if (text.at(0) == 'w' && text == QLatin1String("warning"))
            return true;
        break;

    case 12:
        if (text.at(0) == 'i' && text == QLatin1String("include_next"))
            return true;
        break;

    default:
        break;
    }

    return false;
}

294
bool CppHighlighter::isQtKeyword(const QStringRef &text) const
con's avatar
con committed
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
{
    switch (text.length()) {
    case 4:
        if (text.at(0) == 'e' && text == QLatin1String("emit"))
            return true;
        else if (text.at(0) == 'S' && text == QLatin1String("SLOT"))
            return true;
        break;

    case 5:
        if (text.at(0) == 's' && text == QLatin1String("slots"))
            return true;
        break;

    case 6:
        if (text.at(0) == 'S' && text == QLatin1String("SIGNAL"))
            return true;
        break;

    case 7:
        if (text.at(0) == 's' && text == QLatin1String("signals"))
            return true;
        else if (text.at(0) == 'f' && text == QLatin1String("foreach"))
            return true;
        else if (text.at(0) == 'f' && text == QLatin1String("forever"))
            return true;
        break;

    default:
        break;
    }
    return false;
}

329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
void CppHighlighter::highightLine(const QString &text, int position, int length,
                                  const QTextCharFormat &format)
{
    const QTextCharFormat visualSpaceFormat = m_formats[CppVisualWhitespace];

    const int end = position + length;
    int index = position;

    while (index != end) {
        const bool isSpace = text.at(index).isSpace();
        const int start = index;

        do { ++index; }
        while (index != end && text.at(index).isSpace() == isSpace);

        const int tokenLength = index - start;
        setFormat(start, tokenLength, isSpace ? visualSpaceFormat : format);
    }
}

349
void CppHighlighter::highlightWord(QStringRef word, int position, int length)
con's avatar
con committed
350
351
352
353
354
355
356
357
358
359
360
{
    // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
    // but don't highlight words like 'Query'
    if (word.length() > 1
        && word.at(0) == QLatin1Char('Q')
        && (word.at(1).isUpper()
            || word.at(1) == QLatin1Char('_')
            || word.at(1) == QLatin1Char('t'))) {
        setFormat(position, length, m_formats[CppTypeFormat]);
    }
}
361

Roberto Raggi's avatar
Roberto Raggi committed
362
void CppHighlighter::highlightDoxygenComment(const QString &text, int position, int)
363
364
365
366
367
368
{
    int initial = position;

    const QChar *uc = text.unicode();
    const QChar *it = uc + position;

Roberto Raggi's avatar
Roberto Raggi committed
369
370
    const QTextCharFormat &format = m_formats[CppDoxygenCommentFormat];
    const QTextCharFormat &kwFormat = m_formats[CppDoxygenTagFormat];
371
372
373
374
375
376
377
378
379
380

    while (! it->isNull()) {
        if (it->unicode() == QLatin1Char('\\') ||
            it->unicode() == QLatin1Char('@')) {
            ++it;

            const QChar *start = it;
            while (it->isLetterOrNumber() || it->unicode() == '_')
                ++it;

381
382
            int k = CppTools::classifyDoxygenTag(start, it - start);
            if (k != CppTools::T_DOXY_IDENTIFIER) {
383
                highightLine(text, initial, start - uc - initial, format);
384
385
386
387
388
389
390
                setFormat(start - uc - 1, it - start + 1, kwFormat);
                initial = it - uc;
            }
        } else
            ++it;
    }

391
    highightLine(text, initial, it - uc - initial, format);
392
393
}