cpphighlighter.cpp 16.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13 14
** conditions see http://www.qt.io/licensing.  For further information
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24 25 26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
#include "cpphighlighter.h"
32
#include "cppeditorenums.h"
con's avatar
con committed
33

34
#include <cpptools/cppdoxygen.h>
35
#include <cpptools/cpptoolsreuse.h>
36
#include <texteditor/textdocumentlayout.h>
con's avatar
con committed
37

38 39 40
#include <cplusplus/SimpleLexer.h>
#include <cplusplus/Lexer.h>

41
#include <QTextDocument>
con's avatar
con committed
42 43 44 45 46

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

47
CppHighlighter::CppHighlighter(QTextDocument *document) :
48
    SyntaxHighlighter(document)
con's avatar
con committed
49
{
50
    static QVector<TextStyle> categories;
51
    if (categories.isEmpty()) {
52 53 54 55 56 57 58 59 60 61 62 63
        categories << C_NUMBER
                   << C_STRING
                   << C_TYPE
                   << C_KEYWORD
                   << C_PRIMITIVE_TYPE
                   << C_OPERATOR
                   << C_PREPROCESSOR
                   << C_LABEL
                   << C_COMMENT
                   << C_DOXYGEN_COMMENT
                   << C_DOXYGEN_TAG
                   << C_VISUAL_WHITESPACE;
64 65
    }
    setTextFormatCategories(categories);
con's avatar
con committed
66 67
}

68
void CppHighlighter::highlightBlock(const QString &text)
con's avatar
con committed
69
{
70 71 72 73 74
    const int previousBlockState_ = previousBlockState();
    int lexerState = 0, initialBraceDepth = 0;
    if (previousBlockState_ != -1) {
        lexerState = previousBlockState_ & 0xff;
        initialBraceDepth = previousBlockState_ >> 8;
con's avatar
con committed
75 76
    }

77
    int braceDepth = initialBraceDepth;
78

79 80 81
    // FIXME: Check defaults or get from document.
    LanguageFeatures features;
    features.cxx11Enabled = true;
82
    features.c99Enabled = true;
83

con's avatar
con committed
84
    SimpleLexer tokenize;
85
    tokenize.setLanguageFeatures(features);
con's avatar
con committed
86

87
    int initialLexerState = lexerState;
88
    const Tokens tokens = tokenize(text, initialLexerState);
89
    lexerState = tokenize.state(); // refresh lexer state
con's avatar
con committed
90

91
    initialLexerState &= ~0x80; // discard newline expected bit
mae's avatar
mae committed
92
    int foldingIndent = initialBraceDepth;
93
    if (TextBlockUserData *userData = TextDocumentLayout::testUserData(currentBlock())) {
mae's avatar
mae committed
94 95 96 97 98
        userData->setFoldingIndent(0);
        userData->setFoldingStartIncluded(false);
        userData->setFoldingEndIncluded(false);
    }

con's avatar
con committed
99
    if (tokens.isEmpty()) {
100
        setCurrentBlockState((braceDepth << 8) | lexerState);
101
        TextDocumentLayout::clearParentheses(currentBlock());
102
        if (text.length())  {// the empty line can still contain whitespace
103
            if (initialLexerState == T_COMMENT)
104
                highlightLine(text, 0, text.length(), formatForCategory(CppCommentFormat));
105
            else if (initialLexerState == T_DOXY_COMMENT)
106
                highlightLine(text, 0, text.length(), formatForCategory(CppDoxygenCommentFormat));
107
            else
108
                setFormat(0, text.length(), formatForCategory(CppVisualWhitespace));
109
        }
110
        TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
con's avatar
con committed
111 112 113
        return;
    }

114
    const unsigned firstNonSpace = tokens.first().utf16charsBegin();
con's avatar
con committed
115 116 117 118

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

119 120
    bool expectPreprocessorKeyword = false;
    bool onlyHighlightComments = false;
con's avatar
con committed
121 122

    for (int i = 0; i < tokens.size(); ++i) {
Erik Verbruggen's avatar
Erik Verbruggen committed
123
        const Token &tk = tokens.at(i);
con's avatar
con committed
124

Erik Verbruggen's avatar
Erik Verbruggen committed
125
        unsigned previousTokenEnd = 0;
con's avatar
con committed
126 127
        if (i != 0) {
            // mark the whitespaces
128 129
            previousTokenEnd = tokens.at(i - 1).utf16charsBegin() +
                               tokens.at(i - 1).utf16chars();
con's avatar
con committed
130 131
        }

132
        if (previousTokenEnd != tk.utf16charsBegin()) {
133
            setFormat(previousTokenEnd,
134
                      tk.utf16charsBegin() - previousTokenEnd,
135 136
                      formatForCategory(CppVisualWhitespace));
        }
con's avatar
con committed
137 138

        if (tk.is(T_LPAREN) || tk.is(T_LBRACE) || tk.is(T_LBRACKET)) {
139 140
            const QChar c = text.at(tk.utf16charsBegin());
            parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.utf16charsBegin()));
mae's avatar
mae committed
141
            if (tk.is(T_LBRACE)) {
con's avatar
con committed
142
                ++braceDepth;
mae's avatar
mae committed
143 144 145

                // if a folding block opens at the beginning of a line, treat the entire line
                // as if it were inside the folding block
146
                if (tk.utf16charsBegin() == firstNonSpace) {
mae's avatar
mae committed
147
                    ++foldingIndent;
148
                    TextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
mae's avatar
mae committed
149 150
                }
            }
con's avatar
con committed
151
        } else if (tk.is(T_RPAREN) || tk.is(T_RBRACE) || tk.is(T_RBRACKET)) {
152 153
            const QChar c = text.at(tk.utf16charsBegin());
            parentheses.append(Parenthesis(Parenthesis::Closed, c, tk.utf16charsBegin()));
mae's avatar
mae committed
154
            if (tk.is(T_RBRACE)) {
155
                --braceDepth;
mae's avatar
mae committed
156 157 158
                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))
159
                        TextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
mae's avatar
mae committed
160 161 162 163
                    else
                        foldingIndent = qMin(braceDepth, foldingIndent);
                }
            }
con's avatar
con committed
164 165
        }

166
        bool highlightCurrentWordAsPreprocessor = expectPreprocessorKeyword;
167

168 169 170 171 172
        if (expectPreprocessorKeyword)
            expectPreprocessorKeyword = false;

        if (onlyHighlightComments && !tk.isComment())
            continue;
con's avatar
con committed
173 174

        if (i == 0 && tk.is(T_POUND)) {
175
            highlightLine(text, tk.utf16charsBegin(), tk.utf16chars(),
176
                          formatForCategory(CppPreprocessorFormat));
177
            expectPreprocessorKeyword = true;
178 179
        } else if (highlightCurrentWordAsPreprocessor
                   && (tk.isKeyword() || tk.is(T_IDENTIFIER))
180 181 182
                   && isPPKeyword(text.midRef(tk.utf16charsBegin(), tk.utf16chars()))) {
            setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppPreprocessorFormat));
            const QStringRef ppKeyword = text.midRef(tk.utf16charsBegin(), tk.utf16chars());
183 184 185 186 187
            if (ppKeyword == QLatin1String("error")
                    || ppKeyword == QLatin1String("warning")
                    || ppKeyword == QLatin1String("pragma")) {
                onlyHighlightComments = true;
            }
188

189
        } else if (tk.is(T_NUMERIC_LITERAL)) {
190
            setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppNumberFormat));
191
        } else if (tk.isStringLiteral() || tk.isCharLiteral()) {
192
            highlightLine(text, tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppStringFormat));
193
        } else if (tk.isComment()) {
194
            const int startPosition = initialLexerState ? previousTokenEnd : tk.utf16charsBegin();
195
            if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT)) {
196
                highlightLine(text, startPosition, tk.utf16charsEnd() - startPosition,
197 198
                              formatForCategory(CppCommentFormat));
            }
199 200

            else // a doxygen comment
201
                highlightDoxygenComment(text, startPosition, tk.utf16charsEnd() - startPosition);
202

con's avatar
con committed
203 204 205
            // 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))
206
            //  - is not a continuation line (tokens.size() > 1 || !state)
207
            if (initialLexerState && i == 0 && (tokens.size() > 1 || !lexerState)) {
con's avatar
con committed
208
                --braceDepth;
mae's avatar
mae committed
209 210
                // unless we are at the end of the block, we reduce the folding indent
                if (i == tokens.size()-1)
211
                    TextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
mae's avatar
mae committed
212 213
                else
                    foldingIndent = qMin(braceDepth, foldingIndent);
214
                const int tokenEnd = tk.utf16charsBegin() + tk.utf16chars() - 1;
con's avatar
con committed
215 216 217
                parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));

                // clear the initial state.
218
                initialLexerState = 0;
con's avatar
con committed
219
            }
220

221
        } else if (tk.isKeyword()
222
                   || CppTools::isQtKeyword(text.midRef(tk.utf16charsBegin(), tk.utf16chars()))
223
                   || tk.isObjCAtKeyword()) {
224
            setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppKeywordFormat));
225 226 227
        } else if (tk.isPrimitiveType()) {
            setFormat(tk.utf16charsBegin(), tk.utf16chars(),
                      formatForCategory(CppPrimitiveTypeFormat));
228
        } else if (tk.isOperator()) {
229
            setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppOperatorFormat));
230
        } else if (i == 0 && tokens.size() > 1 && tk.is(T_IDENTIFIER) && tokens.at(1).is(T_COLON)) {
231
            setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(CppLabelFormat));
232
        } else if (tk.is(T_IDENTIFIER)) {
233 234
            highlightWord(text.midRef(tk.utf16charsBegin(), tk.utf16chars()), tk.utf16charsBegin(),
                          tk.utf16chars());
235
        }
con's avatar
con committed
236 237 238
    }

    // mark the trailing white spaces
239
    const int lastTokenEnd = tokens.last().utf16charsEnd();
240
    if (text.length() > lastTokenEnd)
241
        highlightLine(text, lastTokenEnd, text.length() - lastTokenEnd, formatForCategory(CppVisualWhitespace));
con's avatar
con committed
242

243 244 245 246
    if (!initialLexerState && lexerState && !tokens.isEmpty()) {
        const Token &lastToken = tokens.last();
        if (lastToken.is(T_COMMENT) || lastToken.is(T_DOXY_COMMENT)) {
            parentheses.append(Parenthesis(Parenthesis::Opened, QLatin1Char('+'),
247
                                           lastToken.utf16charsBegin()));
248 249
            ++braceDepth;
        }
con's avatar
con committed
250 251
    }

252
    TextDocumentLayout::setParentheses(currentBlock(), parentheses);
con's avatar
con committed
253

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

256
    // do not adjust the brace depth.
257
    if (TextDocumentLayout::ifdefedOut(currentBlock())) {
258
        braceDepth = initialBraceDepth;
mae's avatar
mae committed
259 260 261
        foldingIndent = initialBraceDepth;
    }

262
    TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
263 264 265 266 267 268 269 270

    // 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) {
271 272
            TextDocumentLayout::FoldValidator foldValidor;
            foldValidor.setup(qobject_cast<TextDocumentLayout *>(document()->documentLayout()));
273 274
            int delta = braceDepth - oldBraceDepth;
            QTextBlock block = currentBlock().next();
275
            while (block.isValid() && block.userState() != -1) {
276 277
                TextDocumentLayout::changeBraceDepth(block, delta);
                TextDocumentLayout::changeFoldingIndent(block, delta);
278
                foldValidor.process(block);
279 280
                block = block.next();
            }
281
            foldValidor.finalize();
282 283 284
        }
    }

con's avatar
con committed
285 286 287 288
    setCurrentBlockState((braceDepth << 8) | tokenize.state());
}


289
bool CppHighlighter::isPPKeyword(const QStringRef &text) const
con's avatar
con committed
290 291 292 293
{
    switch (text.length())
    {
    case 2:
294
        if (text.at(0) == QLatin1Char('i') && text.at(1) == QLatin1Char('f'))
con's avatar
con committed
295 296 297 298
            return true;
        break;

    case 4:
299 300
        if (text.at(0) == QLatin1Char('e')
            && (text == QLatin1String("elif") || text == QLatin1String("else")))
con's avatar
con committed
301 302 303 304
            return true;
        break;

    case 5:
305 306 307 308 309 310 311 312 313 314 315 316 317 318
        switch (text.at(0).toLatin1()) {
        case 'i':
            if (text == QLatin1String("ifdef"))
                return true;
            break;
          case 'u':
            if (text == QLatin1String("undef"))
                return true;
            break;
        case 'e':
            if (text == QLatin1String("endif") || text == QLatin1String("error"))
                return true;
            break;
        }
con's avatar
con committed
319 320 321
        break;

    case 6:
322 323 324 325 326 327 328 329 330 331 332 333 334 335
        switch (text.at(0).toLatin1()) {
        case 'i':
            if (text == QLatin1String("ifndef") || text == QLatin1String("import"))
                return true;
            break;
        case 'd':
            if (text == QLatin1String("define"))
                return true;
            break;
        case 'p':
            if (text == QLatin1String("pragma"))
                return true;
            break;
        }
con's avatar
con committed
336 337 338
        break;

    case 7:
339 340 341 342 343 344 345 346 347 348
        switch (text.at(0).toLatin1()) {
        case 'i':
            if (text == QLatin1String("include"))
                return true;
            break;
        case 'w':
            if (text == QLatin1String("warning"))
                return true;
            break;
        }
con's avatar
con committed
349 350 351
        break;

    case 12:
352
        if (text.at(0) == QLatin1Char('i') && text == QLatin1String("include_next"))
con's avatar
con committed
353 354 355 356 357 358 359 360 361 362
            return true;
        break;

    default:
        break;
    }

    return false;
}

363 364
void CppHighlighter::highlightLine(const QString &text, int position, int length,
                                   const QTextCharFormat &format)
365
{
366
    QTextCharFormat visualSpaceFormat = formatForCategory(CppVisualWhitespace);
367
    visualSpaceFormat.setBackground(format.background());
368 369 370 371 372 373 374 375 376 377 378 379

    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;
380 381 382 383
        if (isSpace)
            setFormat(start, tokenLength, visualSpaceFormat);
        else if (format.isValid())
            setFormat(start, tokenLength, format);
384 385 386
    }
}

387
void CppHighlighter::highlightWord(QStringRef word, int position, int length)
con's avatar
con committed
388 389
{
    // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
390

391 392
    if (word.length() > 2 && word.at(0) == QLatin1Char('Q')) {
        if (word.at(1) == QLatin1Char('_') // Q_
393
            || (word.at(1) == QLatin1Char('T') && word.at(2) == QLatin1Char('_'))) { // QT_
394 395
            for (int i = 1; i < word.length(); ++i) {
                const QChar &ch = word.at(i);
396
                if (!(ch.isUpper() || ch == QLatin1Char('_')))
397 398
                    return;
            }
399

400
            setFormat(position, length, formatForCategory(CppTypeFormat));
401
        }
con's avatar
con committed
402 403
    }
}
404

Roberto Raggi's avatar
Roberto Raggi committed
405
void CppHighlighter::highlightDoxygenComment(const QString &text, int position, int)
406 407 408 409 410 411
{
    int initial = position;

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

412 413
    const QTextCharFormat &format = formatForCategory(CppDoxygenCommentFormat);
    const QTextCharFormat &kwFormat = formatForCategory(CppDoxygenTagFormat);
414

415
    while (!it->isNull()) {
416 417 418 419 420
        if (it->unicode() == QLatin1Char('\\') ||
            it->unicode() == QLatin1Char('@')) {
            ++it;

            const QChar *start = it;
421
            while (CppTools::isValidAsciiIdentifierChar(*it))
422 423
                ++it;

424 425
            int k = CppTools::classifyDoxygenTag(start, it - start);
            if (k != CppTools::T_DOXY_IDENTIFIER) {
426
                highlightLine(text, initial, start - uc - initial, format);
427 428 429 430 431 432 433
                setFormat(start - uc - 1, it - start + 1, kwFormat);
                initial = it - uc;
            }
        } else
            ++it;
    }

434
    highlightLine(text, initial, it - uc - initial, format);
435 436
}