1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "cppcompletionassistprocessor.h"
27 
28 #include <cppeditor/cppeditorconstants.h>
29 
30 #include <cplusplus/BackwardsScanner.h>
31 #include <cplusplus/ExpressionUnderCursor.h>
32 #include <cplusplus/SimpleLexer.h>
33 #include <cplusplus/Token.h>
34 
35 #include <QTextBlock>
36 #include <QTextCursor>
37 #include <QTextDocument>
38 
39 using namespace CPlusPlus;
40 
41 namespace CppTools {
42 
CppCompletionAssistProcessor(int snippetItemOrder)43 CppCompletionAssistProcessor::CppCompletionAssistProcessor(int snippetItemOrder)
44     : m_preprocessorCompletions(
45           QStringList({"define", "error", "include", "line", "pragma", "pragma once",
46                        "pragma omp atomic", "pragma omp parallel", "pragma omp for",
47                        "pragma omp ordered", "pragma omp parallel for", "pragma omp section",
48                        "pragma omp sections", "pragma omp parallel sections", "pragma omp single",
49                        "pragma omp master", "pragma omp critical", "pragma omp barrier",
50                        "pragma omp flush", "pragma omp threadprivate", "undef", "if", "ifdef",
51                        "ifndef", "elif", "else", "endif"}))
52     , m_snippetCollector(QLatin1String(CppEditor::Constants::CPP_SNIPPETS_GROUP_ID),
53                          QIcon(QLatin1String(":/texteditor/images/snippet.png")),
54                          snippetItemOrder)
55 {
56 }
57 
addSnippets()58 void CppCompletionAssistProcessor::addSnippets()
59 {
60     m_completions.append(m_snippetCollector.collect());
61 }
62 
isDoxygenTagCompletionCharacter(const QChar & character)63 static bool isDoxygenTagCompletionCharacter(const QChar &character)
64 {
65     return character == QLatin1Char('\\')
66         || character == QLatin1Char('@') ;
67 }
68 
twoIndentifiersBeforeLBrace(const Tokens & tokens,int tokenIdx)69 static bool twoIndentifiersBeforeLBrace(const Tokens &tokens, int tokenIdx)
70 {
71     const Token &previousToken = tokens.at(tokenIdx - 1);
72     if (previousToken.kind() != T_IDENTIFIER)
73         return false;
74     for (int index = tokenIdx - 2; index >= 0; index -= 2) {
75         const Token &token = tokens.at(index);
76         if (token.kind() == T_IDENTIFIER)
77             return true;
78 
79         if (token.kind() != T_COLON_COLON)
80             return false;
81     }
82     return false;
83 }
84 
startOfOperator(QTextDocument * textDocument,int positionInDocument,unsigned * kind,int & start,const CPlusPlus::LanguageFeatures & languageFeatures,bool adjustForQt5SignalSlotCompletion,DotAtIncludeCompletionHandler dotAtIncludeCompletionHandler)85 void CppCompletionAssistProcessor::startOfOperator(QTextDocument *textDocument,
86         int positionInDocument,
87         unsigned *kind,
88         int &start,
89         const CPlusPlus::LanguageFeatures &languageFeatures,
90         bool adjustForQt5SignalSlotCompletion,
91         DotAtIncludeCompletionHandler dotAtIncludeCompletionHandler)
92 {
93     if (start != positionInDocument) {
94         QTextCursor tc(textDocument);
95         tc.setPosition(positionInDocument);
96 
97         // Include completion: make sure the quote character is the first one on the line
98         if (*kind == T_STRING_LITERAL) {
99             QTextCursor s = tc;
100             s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
101             QString sel = s.selectedText();
102             if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) {
103                 *kind = T_EOF_SYMBOL;
104                 start = positionInDocument;
105             }
106         }
107 
108         if (*kind == T_COMMA) {
109             ExpressionUnderCursor expressionUnderCursor(languageFeatures);
110             if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
111                 *kind = T_EOF_SYMBOL;
112                 start = positionInDocument;
113             }
114         }
115 
116         SimpleLexer tokenize;
117         tokenize.setLanguageFeatures(languageFeatures);
118         tokenize.setSkipComments(false);
119         const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
120         const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
121         const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
122         const QChar characterBeforePositionInDocument
123                 = textDocument->characterAt(positionInDocument - 1);
124 
125         if (adjustForQt5SignalSlotCompletion && *kind == T_AMPER && tokenIdx > 0) {
126             const Token &previousToken = tokens.at(tokenIdx - 1);
127             if (previousToken.kind() == T_COMMA)
128                 start = positionInDocument - (tk.utf16charOffset - previousToken.utf16charOffset) - 1;
129         } else if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
130             *kind = T_EOF_SYMBOL;
131             start = positionInDocument;
132         // Do not complete in comments, except in doxygen comments for doxygen commands.
133         // Do not complete in strings, except it is for include completion.
134         } else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT)
135                  || ((tk.is(T_CPP_DOXY_COMMENT) || tk.is(T_DOXY_COMMENT))
136                         && !isDoxygenTagCompletionCharacter(characterBeforePositionInDocument))
137                  || (tk.isLiteral() && (*kind != T_STRING_LITERAL
138                                      && *kind != T_ANGLE_STRING_LITERAL
139                                      && *kind != T_SLASH
140                                      && *kind != T_DOT))) {
141             *kind = T_EOF_SYMBOL;
142             start = positionInDocument;
143         // Include completion: can be triggered by slash, but only in a string
144         } else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) {
145             *kind = T_EOF_SYMBOL;
146             start = positionInDocument;
147         } else if (*kind == T_LPAREN) {
148             if (tokenIdx > 0) {
149                 const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN
150                 switch (previousToken.kind()) {
151                 case T_IDENTIFIER:
152                 case T_GREATER:
153                 case T_SIGNAL:
154                 case T_SLOT:
155                     break; // good
156 
157                 default:
158                     // that's a bad token :)
159                     *kind = T_EOF_SYMBOL;
160                     start = positionInDocument;
161                 }
162             } else {
163                 *kind = T_EOF_SYMBOL;
164                 start = positionInDocument;
165             }
166         } else if (*kind == T_LBRACE) {
167             if (tokenIdx <= 0 || !twoIndentifiersBeforeLBrace(tokens, tokenIdx)) {
168                 *kind = T_EOF_SYMBOL;
169                 start = positionInDocument;
170             }
171         }
172         // Check for include preprocessor directive
173         else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH
174                  || (*kind == T_DOT
175                         && (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)))) {
176             bool include = false;
177             if (tokens.size() >= 3) {
178                 if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) ||
179                                                                                   tokens.at(2).is(T_ANGLE_STRING_LITERAL))) {
180                     const Token &directiveToken = tokens.at(1);
181                     QString directive = tc.block().text().mid(directiveToken.utf16charsBegin(),
182                                                               directiveToken.utf16chars());
183                     if (directive == QLatin1String("include") ||
184                             directive == QLatin1String("include_next") ||
185                             directive == QLatin1String("import")) {
186                         include = true;
187                     }
188                 }
189             }
190 
191             if (!include) {
192                 *kind = T_EOF_SYMBOL;
193                 start = positionInDocument;
194             } else if (*kind == T_DOT && dotAtIncludeCompletionHandler){
195                 dotAtIncludeCompletionHandler(start, kind);
196             }
197         }
198     }
199 }
200 
201 } // namespace CppTools
202