cpphighlighter.cpp 14.1 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 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

#include <Token.h>
#include <cplusplus/SimpleLexer.h>
35
#include <texteditor/basetextdocumentlayout.h>
con's avatar
con committed
36
37
38
39
40
41
42
43

#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
    SimpleLexer tokenize;
    tokenize.setQtMocRunEnabled(false);
62
    tokenize.setObjCEnabled(false);
con's avatar
con committed
63
64
65
66
67

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

mae's avatar
mae committed
68
69
70
71
72
73
74
    int foldingIndent = initialBraceDepth;
    if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(currentBlock())) {
        userData->setFoldingIndent(0);
        userData->setFoldingStartIncluded(false);
        userData->setFoldingEndIncluded(false);
    }

con's avatar
con committed
75
76
    if (tokens.isEmpty()) {
        setCurrentBlockState(previousState);
77
        BaseTextDocumentLayout::clearParentheses(currentBlock());
78
        if (text.length()) // the empty line can still contain whitespace
mae's avatar
mae committed
79
            setFormat(0, text.length(), m_formats[CppVisualWhitespace]);
mae's avatar
mae committed
80
        BaseTextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
con's avatar
con committed
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
        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
103
                      m_formats[CppVisualWhitespace]);
con's avatar
con committed
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()));
mae's avatar
mae committed
109
            if (tk.is(T_LBRACE)) {
con's avatar
con committed
110
                ++braceDepth;
mae's avatar
mae committed
111
112
113
114
115
116
117
118

                // if a folding block opens at the beginning of a line, treat the entire line
                // as if it were inside the folding block
                if (tk.position() == firstNonSpace) {
                    ++foldingIndent;
                    BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
                }
            }
con's avatar
con committed
119
120
121
        } 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()));
mae's avatar
mae committed
122
            if (tk.is(T_RBRACE)) {
123
                --braceDepth;
mae's avatar
mae committed
124
125
126
127
128
129
130
131
                if (braceDepth < foldingIndent) {
                    // unless we are at the end of the block, we reduce the folding indent
                    if (i == tokens.size()-1 || tokens.at(i+1).is(T_SEMICOLON))
                        BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
                    else
                        foldingIndent = qMin(braceDepth, foldingIndent);
                }
            }
con's avatar
con committed
132
133
134
        }

        bool highlightCurrentWordAsPreprocessor = highlightAsPreprocessor;
135

con's avatar
con committed
136
137
138
139
        if (highlightAsPreprocessor)
            highlightAsPreprocessor = false;

        if (i == 0 && tk.is(T_POUND)) {
140
            highlightLine(text, tk.position(), tk.length(), m_formats[CppPreprocessorFormat]);
con's avatar
con committed
141
            highlightAsPreprocessor = true;
142

con's avatar
con committed
143
144
145
        } else if (highlightCurrentWordAsPreprocessor &&
                   (tk.isKeyword() || tk.is(T_IDENTIFIER)) && isPPKeyword(tk.text()))
            setFormat(tk.position(), tk.length(), m_formats[CppPreprocessorFormat]);
146

147
        else if (tk.is(T_NUMERIC_LITERAL))
con's avatar
con committed
148
            setFormat(tk.position(), tk.length(), m_formats[CppNumberFormat]);
149

150
151
        else if (tk.is(T_STRING_LITERAL) || tk.is(T_CHAR_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL) ||
                 tk.is(T_AT_STRING_LITERAL))
152
            highlightLine(text, tk.position(), tk.length(), m_formats[CppStringFormat]);
153

con's avatar
con committed
154
        else if (tk.is(T_WIDE_STRING_LITERAL) || tk.is(T_WIDE_CHAR_LITERAL))
155
            highlightLine(text, tk.position(), tk.length(), m_formats[CppStringFormat]);
156
157
158

        else if (tk.isComment()) {

159
            if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT))
160
                highlightLine(text, tk.position(), tk.length(), m_formats[CppCommentFormat]);
161
162
163
164

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

con's avatar
con committed
165
166
167
168
169
170
            // 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;
mae's avatar
mae committed
171
172
173
174
175
                // unless we are at the end of the block, we reduce the folding indent
                if (i == tokens.size()-1)
                    BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
                else
                    foldingIndent = qMin(braceDepth, foldingIndent);
con's avatar
con committed
176
177
178
179
180
181
                const int tokenEnd = tk.position() + tk.length() - 1;
                parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));

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

183
        } else if (tk.isKeyword() || isQtKeyword(tk.text()) || tk.isObjCAtKeyword() || tk.isObjCTypeQualifier())
con's avatar
con committed
184
            setFormat(tk.position(), tk.length(), m_formats[CppKeywordFormat]);
185

con's avatar
con committed
186
187
        else if (tk.isOperator())
            setFormat(tk.position(), tk.length(), m_formats[CppOperatorFormat]);
188

con's avatar
con committed
189
190
        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]);
191

con's avatar
con committed
192
193
        else if (tk.is(T_IDENTIFIER))
            highlightWord(tk.text(), tk.position(), tk.length());
mae's avatar
mae committed
194

con's avatar
con committed
195
196
197
    }

    // mark the trailing white spaces
198
    {
con's avatar
con committed
199
200
201
        const SimpleToken tk = tokens.last();
        const int lastTokenEnd = tk.position() + tk.length();
        if (text.length() > lastTokenEnd)
202
            highlightLine(text, lastTokenEnd, text.length() - lastTokenEnd, QTextCharFormat());
con's avatar
con committed
203
204
205
206
207
208
209
210
    }

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

211
    BaseTextDocumentLayout::setParentheses(currentBlock(), parentheses);
con's avatar
con committed
212

213
    // if the block is ifdefed out, we only store the parentheses, but
mae's avatar
mae committed
214

215
    // do not adjust the brace depth.
mae's avatar
mae committed
216
    if (BaseTextDocumentLayout::ifdefedOut(currentBlock())) {
217
        braceDepth = initialBraceDepth;
mae's avatar
mae committed
218
219
220
221
        foldingIndent = initialBraceDepth;
    }

    BaseTextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
222
223
224
225
226
227
228
229
230
231

    // 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();
232
            while (block.isValid() && block.userState() != -1) {
233
                BaseTextDocumentLayout::changeBraceDepth(block, delta);
mae's avatar
mae committed
234
                BaseTextDocumentLayout::changeFoldingIndent(block, delta);
235
236
237
238
239
                block = block.next();
            }
        }
    }

con's avatar
con committed
240
241
242
243
    setCurrentBlockState((braceDepth << 8) | tokenize.state());
}


244
bool CppHighlighter::isPPKeyword(const QStringRef &text) const
con's avatar
con committed
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
{
    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;
274
275
        if (text.at(0) == 'i' && text == QLatin1String("import"))
            return true;
con's avatar
con committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
        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;
}

301
bool CppHighlighter::isQtKeyword(const QStringRef &text) const
con's avatar
con committed
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
329
330
331
332
333
334
335
{
    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;
}

336
337
void CppHighlighter::highlightLine(const QString &text, int position, int length,
                                   const QTextCharFormat &format)
338
339
340
341
342
343
344
345
346
347
348
349
350
351
{
    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;
352
353
354
355
        if (isSpace)
            setFormat(start, tokenLength, visualSpaceFormat);
        else if (format.isValid())
            setFormat(start, tokenLength, format);
356
357
358
    }
}

359
void CppHighlighter::highlightWord(QStringRef word, int position, int length)
con's avatar
con committed
360
361
362
363
364
365
366
367
368
369
370
{
    // 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]);
    }
}
371

Roberto Raggi's avatar
Roberto Raggi committed
372
void CppHighlighter::highlightDoxygenComment(const QString &text, int position, int)
373
374
375
376
377
378
{
    int initial = position;

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

Roberto Raggi's avatar
Roberto Raggi committed
379
380
    const QTextCharFormat &format = m_formats[CppDoxygenCommentFormat];
    const QTextCharFormat &kwFormat = m_formats[CppDoxygenTagFormat];
381
382
383
384
385
386
387
388
389
390

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

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

391
392
            int k = CppTools::classifyDoxygenTag(start, it - start);
            if (k != CppTools::T_DOXY_IDENTIFIER) {
393
                highlightLine(text, initial, start - uc - initial, format);
394
395
396
397
398
399
400
                setFormat(start - uc - 1, it - start + 1, kwFormat);
                initial = it - uc;
            }
        } else
            ++it;
    }

401
    highlightLine(text, initial, it - uc - initial, format);
402
403
}