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