cpphighlighter.cpp 16.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
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
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26
#include "cpphighlighter.h"
27
#include "cppeditorenums.h"
con's avatar
con committed
28

29
#include <cpptools/cppdoxygen.h>
30
#include <cpptools/cpptoolsreuse.h>
31
#include <texteditor/textdocumentlayout.h>
con's avatar
con committed
32

33 34 35
#include <cplusplus/SimpleLexer.h>
#include <cplusplus/Lexer.h>

36
#include <QTextDocument>
con's avatar
con committed
37 38 39 40 41

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

42
CppHighlighter::CppHighlighter(QTextDocument *document) :
43
    SyntaxHighlighter(document)
con's avatar
con committed
44
{
45
    static QVector<TextStyle> categories;
46
    if (categories.isEmpty()) {
47 48 49 50 51 52 53 54 55 56 57 58
        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;
59 60
    }
    setTextFormatCategories(categories);
con's avatar
con committed
61 62
}

63
void CppHighlighter::highlightBlock(const QString &text)
con's avatar
con committed
64
{
65 66 67 68 69
    const int previousBlockState_ = previousBlockState();
    int lexerState = 0, initialBraceDepth = 0;
    if (previousBlockState_ != -1) {
        lexerState = previousBlockState_ & 0xff;
        initialBraceDepth = previousBlockState_ >> 8;
con's avatar
con committed
70 71
    }

72
    int braceDepth = initialBraceDepth;
73

74 75 76
    // FIXME: Check defaults or get from document.
    LanguageFeatures features;
    features.cxx11Enabled = true;
77
    features.c99Enabled = true;
78

con's avatar
con committed
79
    SimpleLexer tokenize;
80
    tokenize.setLanguageFeatures(features);
con's avatar
con committed
81

82
    int initialLexerState = lexerState;
83
    const Tokens tokens = tokenize(text, initialLexerState);
84
    lexerState = tokenize.state(); // refresh lexer state
con's avatar
con committed
85

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

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

109
    const unsigned firstNonSpace = tokens.first().utf16charsBegin();
con's avatar
con committed
110 111

    Parentheses parentheses;
112
    parentheses.reserve(5);
con's avatar
con committed
113

114 115
    bool expectPreprocessorKeyword = false;
    bool onlyHighlightComments = false;
con's avatar
con committed
116 117

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

Erik Verbruggen's avatar
Erik Verbruggen committed
120
        unsigned previousTokenEnd = 0;
con's avatar
con committed
121 122
        if (i != 0) {
            // mark the whitespaces
123 124
            previousTokenEnd = tokens.at(i - 1).utf16charsBegin() +
                               tokens.at(i - 1).utf16chars();
con's avatar
con committed
125 126
        }

127
        if (previousTokenEnd != tk.utf16charsBegin()) {
128
            setFormat(previousTokenEnd,
129
                      tk.utf16charsBegin() - previousTokenEnd,
130 131
                      formatForCategory(CppVisualWhitespace));
        }
con's avatar
con committed
132 133

        if (tk.is(T_LPAREN) || tk.is(T_LBRACE) || tk.is(T_LBRACKET)) {
134 135
            const QChar c = text.at(tk.utf16charsBegin());
            parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.utf16charsBegin()));
mae's avatar
mae committed
136
            if (tk.is(T_LBRACE)) {
con's avatar
con committed
137
                ++braceDepth;
mae's avatar
mae committed
138 139 140

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

161
        bool highlightCurrentWordAsPreprocessor = expectPreprocessorKeyword;
162

163 164 165 166 167
        if (expectPreprocessorKeyword)
            expectPreprocessorKeyword = false;

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

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

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

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

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

                // clear the initial state.
213
                initialLexerState = 0;
con's avatar
con committed
214
            }
215

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

    // mark the trailing white spaces
234
    const int lastTokenEnd = tokens.last().utf16charsEnd();
235
    if (text.length() > lastTokenEnd)
236
        highlightLine(text, lastTokenEnd, text.length() - lastTokenEnd, formatForCategory(CppVisualWhitespace));
con's avatar
con committed
237

238 239 240 241
    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('+'),
242
                                           lastToken.utf16charsBegin()));
243 244
            ++braceDepth;
        }
con's avatar
con committed
245 246
    }

247
    TextDocumentLayout::setParentheses(currentBlock(), parentheses);
con's avatar
con committed
248

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

251
    // do not adjust the brace depth.
252
    if (TextDocumentLayout::ifdefedOut(currentBlock())) {
253
        braceDepth = initialBraceDepth;
mae's avatar
mae committed
254 255 256
        foldingIndent = initialBraceDepth;
    }

257
    TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
258 259 260 261 262 263 264 265

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

con's avatar
con committed
280 281 282 283
    setCurrentBlockState((braceDepth << 8) | tokenize.state());
}


284
bool CppHighlighter::isPPKeyword(const QStringRef &text) const
con's avatar
con committed
285 286 287 288
{
    switch (text.length())
    {
    case 2:
289
        if (text.at(0) == QLatin1Char('i') && text.at(1) == QLatin1Char('f'))
con's avatar
con committed
290 291 292 293
            return true;
        break;

    case 4:
294 295
        if (text.at(0) == QLatin1Char('e')
            && (text == QLatin1String("elif") || text == QLatin1String("else")))
con's avatar
con committed
296 297 298 299
            return true;
        break;

    case 5:
300 301 302 303 304 305 306 307 308 309 310 311 312 313
        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
314 315 316
        break;

    case 6:
317 318 319 320 321 322 323 324 325 326 327 328 329 330
        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
331 332 333
        break;

    case 7:
334 335 336 337 338 339 340 341 342 343
        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
344 345 346
        break;

    case 12:
347
        if (text.at(0) == QLatin1Char('i') && text == QLatin1String("include_next"))
con's avatar
con committed
348 349 350 351 352 353 354 355 356 357
            return true;
        break;

    default:
        break;
    }

    return false;
}

358 359
void CppHighlighter::highlightLine(const QString &text, int position, int length,
                                   const QTextCharFormat &format)
360
{
361
    QTextCharFormat visualSpaceFormat = formatForCategory(CppVisualWhitespace);
362
    visualSpaceFormat.setBackground(format.background());
363 364 365 366 367 368 369 370 371 372 373 374

    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;
375 376 377 378
        if (isSpace)
            setFormat(start, tokenLength, visualSpaceFormat);
        else if (format.isValid())
            setFormat(start, tokenLength, format);
379 380 381
    }
}

382
void CppHighlighter::highlightWord(QStringRef word, int position, int length)
con's avatar
con committed
383 384
{
    // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY
385

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

395
            setFormat(position, length, formatForCategory(CppTypeFormat));
396
        }
con's avatar
con committed
397 398
    }
}
399

Roberto Raggi's avatar
Roberto Raggi committed
400
void CppHighlighter::highlightDoxygenComment(const QString &text, int position, int)
401 402 403 404 405 406
{
    int initial = position;

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

407 408
    const QTextCharFormat &format = formatForCategory(CppDoxygenCommentFormat);
    const QTextCharFormat &kwFormat = formatForCategory(CppDoxygenTagFormat);
409

410
    while (!it->isNull()) {
411 412 413 414 415
        if (it->unicode() == QLatin1Char('\\') ||
            it->unicode() == QLatin1Char('@')) {
            ++it;

            const QChar *start = it;
416
            while (CppTools::isValidAsciiIdentifierChar(*it))
417 418
                ++it;

419 420
            int k = CppTools::classifyDoxygenTag(start, it - start);
            if (k != CppTools::T_DOXY_IDENTIFIER) {
421
                highlightLine(text, initial, start - uc - initial, format);
422 423 424 425 426 427 428
                setFormat(start - uc - 1, it - start + 1, kwFormat);
                initial = it - uc;
            }
        } else
            ++it;
    }

429
    highlightLine(text, initial, it - uc - initial, format);
430 431
}