forked from qt-creator/qt-creator
Clang: Split clangcompletion.[h|cpp]
First step for refactor them. Change-Id: If9de084e39ddf31317035ccbbc1fd57d7797d193 Reviewed-by: Nikolai Kosjar <nikolai.kosjar@theqtcompany.com>
This commit is contained in:
788
src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp
Normal file
788
src/plugins/clangcodemodel/clangcompletionassistprocessor.cpp
Normal file
@@ -0,0 +1,788 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2015 The Qt Company Ltd.
|
||||
** Contact: http://www.qt.io/licensing
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** 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 The Qt Company. For licensing terms and
|
||||
** conditions see http://www.qt.io/terms-conditions. For further information
|
||||
** use the contact form at http://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** 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.
|
||||
**
|
||||
** In addition, as a special exception, The Qt Company gives you certain additional
|
||||
** rights. These rights are described in The Qt Company LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "clangassistproposalitem.h"
|
||||
|
||||
#include "clangassistproposal.h"
|
||||
#include "clangassistproposalmodel.h"
|
||||
#include "clangcompletionassistprocessor.h"
|
||||
#include "clangcompletioncontextanalyzer.h"
|
||||
#include "clangfunctionhintmodel.h"
|
||||
#include "clangutils.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
|
||||
#include <texteditor/codeassist/assistproposalitem.h>
|
||||
#include <texteditor/codeassist/functionhintproposal.h>
|
||||
#include <texteditor/codeassist/ifunctionhintproposalmodel.h>
|
||||
#include <texteditor/convenience.h>
|
||||
|
||||
#include <cpptools/cppdoxygen.h>
|
||||
|
||||
#include <cplusplus/BackwardsScanner.h>
|
||||
#include <cplusplus/ExpressionUnderCursor.h>
|
||||
#include <cplusplus/SimpleLexer.h>
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTextBlock>
|
||||
#include <filecontainer.h>
|
||||
|
||||
#include <utils/mimetypes/mimedatabase.h>
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
using TextEditor::AssistProposalItem;
|
||||
|
||||
namespace {
|
||||
|
||||
const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png";
|
||||
|
||||
int activationSequenceChar(const QChar &ch,
|
||||
const QChar &ch2,
|
||||
const QChar &ch3,
|
||||
unsigned *kind,
|
||||
bool wantFunctionCall)
|
||||
{
|
||||
using namespace CPlusPlus;
|
||||
|
||||
int referencePosition = 0;
|
||||
int completionKind = T_EOF_SYMBOL;
|
||||
switch (ch.toLatin1()) {
|
||||
case '.':
|
||||
if (ch2 != QLatin1Char('.')) {
|
||||
completionKind = T_DOT;
|
||||
referencePosition = 1;
|
||||
}
|
||||
break;
|
||||
case ',':
|
||||
completionKind = T_COMMA;
|
||||
referencePosition = 1;
|
||||
break;
|
||||
case '(':
|
||||
if (wantFunctionCall) {
|
||||
completionKind = T_LPAREN;
|
||||
referencePosition = 1;
|
||||
}
|
||||
break;
|
||||
case ':':
|
||||
if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) {
|
||||
completionKind = T_COLON_COLON;
|
||||
referencePosition = 2;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
if (ch2 == QLatin1Char('-')) {
|
||||
completionKind = T_ARROW;
|
||||
referencePosition = 2;
|
||||
}
|
||||
break;
|
||||
case '*':
|
||||
if (ch2 == QLatin1Char('.')) {
|
||||
completionKind = T_DOT_STAR;
|
||||
referencePosition = 2;
|
||||
} else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) {
|
||||
completionKind = T_ARROW_STAR;
|
||||
referencePosition = 3;
|
||||
}
|
||||
break;
|
||||
case '\\':
|
||||
case '@':
|
||||
if (ch2.isNull() || ch2.isSpace()) {
|
||||
completionKind = T_DOXY_COMMENT;
|
||||
referencePosition = 1;
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
completionKind = T_ANGLE_STRING_LITERAL;
|
||||
referencePosition = 1;
|
||||
break;
|
||||
case '"':
|
||||
completionKind = T_STRING_LITERAL;
|
||||
referencePosition = 1;
|
||||
break;
|
||||
case '/':
|
||||
completionKind = T_SLASH;
|
||||
referencePosition = 1;
|
||||
break;
|
||||
case '#':
|
||||
completionKind = T_POUND;
|
||||
referencePosition = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (kind)
|
||||
*kind = completionKind;
|
||||
|
||||
return referencePosition;
|
||||
}
|
||||
|
||||
QList<AssistProposalItem *> toAssistProposalItems(const CodeCompletions &completions)
|
||||
{
|
||||
static CPlusPlus::Icons m_icons; // de-deduplicate
|
||||
|
||||
QList<AssistProposalItem *> result;
|
||||
|
||||
bool signalCompletion = false; // TODO
|
||||
bool slotCompletion = false; // TODO
|
||||
|
||||
const QIcon snippetIcon = QIcon(QLatin1String(SNIPPET_ICON_PATH));
|
||||
QHash<QString, ClangAssistProposalItem *> items;
|
||||
foreach (const CodeCompletion &ccr, completions) {
|
||||
if (ccr.text().isEmpty()) // TODO: Make isValid()?
|
||||
continue;
|
||||
if (signalCompletion && ccr.completionKind() != CodeCompletion::SignalCompletionKind)
|
||||
continue;
|
||||
if (slotCompletion && ccr.completionKind() != CodeCompletion::SlotCompletionKind)
|
||||
continue;
|
||||
|
||||
const QString txt(ccr.text().toString());
|
||||
ClangAssistProposalItem *item = items.value(txt, 0);
|
||||
if (item) {
|
||||
item->addOverload(ccr);
|
||||
} else {
|
||||
item = new ClangAssistProposalItem;
|
||||
items.insert(txt, item);
|
||||
item->setText(txt);
|
||||
item->setDetail(ccr.hint().toString());
|
||||
item->setOrder(ccr.priority());
|
||||
|
||||
const QString snippet = ccr.snippet().toString();
|
||||
if (!snippet.isEmpty())
|
||||
item->setData(snippet);
|
||||
else
|
||||
item->setData(qVariantFromValue(ccr));
|
||||
}
|
||||
|
||||
// FIXME: show the effective accessebility instead of availability
|
||||
using ClangBackEnd::CodeCompletion;
|
||||
using CPlusPlus::Icons;
|
||||
|
||||
switch (ccr.completionKind()) {
|
||||
case CodeCompletion::ClassCompletionKind:
|
||||
case CodeCompletion::TemplateClassCompletionKind:
|
||||
item->setIcon(m_icons.iconForType(Icons::ClassIconType)); break;
|
||||
case CodeCompletion::EnumerationCompletionKind: item->setIcon(m_icons.iconForType(Icons::EnumIconType)); break;
|
||||
case CodeCompletion::EnumeratorCompletionKind: item->setIcon(m_icons.iconForType(Icons::EnumeratorIconType)); break;
|
||||
|
||||
case CodeCompletion::ConstructorCompletionKind: // fall through
|
||||
case CodeCompletion::DestructorCompletionKind: // fall through
|
||||
case CodeCompletion::FunctionCompletionKind:
|
||||
case CodeCompletion::TemplateFunctionCompletionKind:
|
||||
case CodeCompletion::ObjCMessageCompletionKind:
|
||||
switch (ccr.availability()) {
|
||||
case CodeCompletion::Available:
|
||||
case CodeCompletion::Deprecated:
|
||||
item->setIcon(m_icons.iconForType(Icons::FuncPublicIconType));
|
||||
break;
|
||||
default:
|
||||
item->setIcon(m_icons.iconForType(Icons::FuncPrivateIconType));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CodeCompletion::SignalCompletionKind:
|
||||
item->setIcon(m_icons.iconForType(Icons::SignalIconType));
|
||||
break;
|
||||
|
||||
case CodeCompletion::SlotCompletionKind:
|
||||
switch (ccr.availability()) {
|
||||
case CodeCompletion::Available:
|
||||
case CodeCompletion::Deprecated:
|
||||
item->setIcon(m_icons.iconForType(Icons::SlotPublicIconType));
|
||||
break;
|
||||
case CodeCompletion::NotAccessible:
|
||||
case CodeCompletion::NotAvailable:
|
||||
item->setIcon(m_icons.iconForType(Icons::SlotPrivateIconType));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CodeCompletion::NamespaceCompletionKind: item->setIcon(m_icons.iconForType(Icons::NamespaceIconType)); break;
|
||||
case CodeCompletion::PreProcessorCompletionKind: item->setIcon(m_icons.iconForType(Icons::MacroIconType)); break;
|
||||
case CodeCompletion::VariableCompletionKind:
|
||||
switch (ccr.availability()) {
|
||||
case CodeCompletion::Available:
|
||||
case CodeCompletion::Deprecated:
|
||||
item->setIcon(m_icons.iconForType(Icons::VarPublicIconType));
|
||||
break;
|
||||
default:
|
||||
item->setIcon(m_icons.iconForType(Icons::VarPrivateIconType));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CodeCompletion::KeywordCompletionKind:
|
||||
item->setIcon(m_icons.iconForType(Icons::KeywordIconType));
|
||||
break;
|
||||
|
||||
case CodeCompletion::ClangSnippetKind:
|
||||
item->setIcon(snippetIcon);
|
||||
break;
|
||||
|
||||
case CodeCompletion::Other:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ClangAssistProposalItem *item, items.values())
|
||||
result.append(item);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isFunctionHintLikeCompletion(CodeCompletion::Kind kind)
|
||||
{
|
||||
return kind == CodeCompletion::FunctionCompletionKind
|
||||
|| kind == CodeCompletion::ConstructorCompletionKind
|
||||
|| kind == CodeCompletion::DestructorCompletionKind
|
||||
|| kind == CodeCompletion::SignalCompletionKind
|
||||
|| kind == CodeCompletion::SlotCompletionKind;
|
||||
}
|
||||
|
||||
QVector<CodeCompletion> matchingFunctionCompletions(const QVector<CodeCompletion> completions,
|
||||
const QString &functionName)
|
||||
{
|
||||
QVector<CodeCompletion> matching;
|
||||
|
||||
foreach (const CodeCompletion &completion, completions) {
|
||||
if (isFunctionHintLikeCompletion(completion.completionKind())
|
||||
&& completion.text().toString() == functionName) {
|
||||
matching.append(completion);
|
||||
}
|
||||
}
|
||||
|
||||
return matching;
|
||||
}
|
||||
|
||||
} // Anonymous
|
||||
|
||||
using namespace CPlusPlus;
|
||||
using namespace TextEditor;
|
||||
|
||||
ClangCompletionAssistProcessor::ClangCompletionAssistProcessor()
|
||||
: m_completionOperator(T_EOF_SYMBOL)
|
||||
{
|
||||
}
|
||||
|
||||
ClangCompletionAssistProcessor::~ClangCompletionAssistProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
IAssistProposal *ClangCompletionAssistProcessor::perform(const AssistInterface *interface)
|
||||
{
|
||||
m_interface.reset(static_cast<const ClangCompletionAssistInterface *>(interface));
|
||||
|
||||
if (interface->reason() != ExplicitlyInvoked && !accepts())
|
||||
return 0;
|
||||
|
||||
return startCompletionHelper(); // == 0 if results are calculated asynchronously
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::asyncCompletionsAvailable(const CodeCompletions &completions)
|
||||
{
|
||||
switch (m_sentRequestType) {
|
||||
case CompletionRequestType::NormalCompletion:
|
||||
onCompletionsAvailable(completions);
|
||||
break;
|
||||
case CompletionRequestType::FunctionHintCompletion:
|
||||
onFunctionHintCompletionsAvailable(completions);
|
||||
break;
|
||||
default:
|
||||
QTC_CHECK(!"Unhandled ClangCompletionAssistProcessor::CompletionRequestType");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const TextEditorWidget *ClangCompletionAssistProcessor::textEditorWidget() const
|
||||
{
|
||||
return m_interface->textEditorWidget();
|
||||
}
|
||||
|
||||
/// Seach backwards in the document starting from pos to find the first opening
|
||||
/// parenthesis. Nested parenthesis are skipped.
|
||||
static int findOpenParen(QTextDocument *document, int start)
|
||||
{
|
||||
unsigned parenCount = 1;
|
||||
for (int position = start; position >= 0; --position) {
|
||||
const QChar ch = document->characterAt(position);
|
||||
if (ch == QLatin1Char('(')) {
|
||||
--parenCount;
|
||||
if (parenCount == 0)
|
||||
return position;
|
||||
} else if (ch == QLatin1Char(')')) {
|
||||
++parenCount;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static QByteArray modifyInput(QTextDocument *doc, int endOfExpression) {
|
||||
int comma = endOfExpression;
|
||||
while (comma > 0) {
|
||||
const QChar ch = doc->characterAt(comma);
|
||||
if (ch == QLatin1Char(','))
|
||||
break;
|
||||
if (ch == QLatin1Char(';') || ch == QLatin1Char('{') || ch == QLatin1Char('}')) {
|
||||
// Safety net: we don't seem to have "connect(pointer, SIGNAL(" as
|
||||
// input, so stop searching.
|
||||
comma = -1;
|
||||
break;
|
||||
}
|
||||
--comma;
|
||||
}
|
||||
if (comma < 0)
|
||||
return QByteArray();
|
||||
const int openBrace = findOpenParen(doc, comma);
|
||||
if (openBrace < 0)
|
||||
return QByteArray();
|
||||
|
||||
QByteArray modifiedInput = doc->toPlainText().toUtf8();
|
||||
const int len = endOfExpression - comma;
|
||||
QByteArray replacement(len - 4, ' ');
|
||||
replacement.append(")->");
|
||||
modifiedInput.replace(comma, len, replacement);
|
||||
modifiedInput.insert(openBrace, '(');
|
||||
return modifiedInput;
|
||||
}
|
||||
|
||||
IAssistProposal *ClangCompletionAssistProcessor::startCompletionHelper()
|
||||
{
|
||||
sendFileContent(Utils::projectFilePathForFile(m_interface->fileName()), QByteArray()); // TODO: Remoe
|
||||
|
||||
ClangCompletionContextAnalyzer analyzer(m_interface.data(), m_interface->languageFeatures());
|
||||
analyzer.analyze();
|
||||
m_completionOperator = analyzer.completionOperator();
|
||||
m_positionForProposal = analyzer.positionForProposal();
|
||||
|
||||
QByteArray modifiedFileContent;
|
||||
|
||||
const ClangCompletionContextAnalyzer::CompletionAction action = analyzer.completionAction();
|
||||
switch (action) {
|
||||
case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
|
||||
if (completeDoxygenKeywords())
|
||||
return createProposal();
|
||||
break;
|
||||
case ClangCompletionContextAnalyzer::CompleteIncludePath:
|
||||
if (completeInclude(analyzer.positionEndOfExpression()))
|
||||
return createProposal();
|
||||
break;
|
||||
case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
|
||||
if (completePreprocessorDirectives())
|
||||
return createProposal();
|
||||
break;
|
||||
case ClangCompletionContextAnalyzer::CompleteSignal:
|
||||
case ClangCompletionContextAnalyzer::CompleteSlot:
|
||||
modifiedFileContent = modifyInput(m_interface->textDocument(),
|
||||
analyzer.positionEndOfExpression());
|
||||
// Fall through!
|
||||
case ClangCompletionContextAnalyzer::PassThroughToLibClang: {
|
||||
m_addSnippets = m_completionOperator == T_EOF_SYMBOL;
|
||||
m_sentRequestType = NormalCompletion;
|
||||
sendCompletionRequest(analyzer.positionForClang(), modifiedFileContent);
|
||||
break;
|
||||
}
|
||||
case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen: {
|
||||
m_sentRequestType = FunctionHintCompletion;
|
||||
m_functionName = analyzer.functionName();
|
||||
sendCompletionRequest(analyzer.positionForClang(), QByteArray());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Extract duplicated logic from InternalCppCompletionAssistProcessor::startOfOperator
|
||||
int ClangCompletionAssistProcessor::startOfOperator(int pos,
|
||||
unsigned *kind,
|
||||
bool wantFunctionCall) const
|
||||
{
|
||||
const QChar ch = pos > -1 ? m_interface->characterAt(pos - 1) : QChar();
|
||||
const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar();
|
||||
const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar();
|
||||
|
||||
int start = pos - activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall);
|
||||
if (start != pos) {
|
||||
QTextCursor tc(m_interface->textDocument());
|
||||
tc.setPosition(pos);
|
||||
|
||||
// Include completion: make sure the quote character is the first one on the line
|
||||
if (*kind == T_STRING_LITERAL) {
|
||||
QTextCursor s = tc;
|
||||
s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
|
||||
QString sel = s.selectedText();
|
||||
if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
|
||||
if (*kind == T_COMMA) {
|
||||
ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
|
||||
if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
|
||||
SimpleLexer tokenize;
|
||||
tokenize.setLanguageFeatures(m_interface->languageFeatures());
|
||||
tokenize.setSkipComments(false);
|
||||
const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
|
||||
const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor
|
||||
const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
|
||||
|
||||
if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
// Don't complete in comments or strings, but still check for include completion
|
||||
else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT)
|
||||
|| tk.is(T_CPP_DOXY_COMMENT) || tk.is(T_DOXY_COMMENT)
|
||||
|| (tk.isLiteral() && (*kind != T_STRING_LITERAL
|
||||
&& *kind != T_ANGLE_STRING_LITERAL
|
||||
&& *kind != T_SLASH))) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
// Include completion: can be triggered by slash, but only in a string
|
||||
else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
else if (*kind == T_LPAREN) {
|
||||
if (tokenIdx > 0) {
|
||||
const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN
|
||||
switch (previousToken.kind()) {
|
||||
case T_IDENTIFIER:
|
||||
case T_GREATER:
|
||||
case T_SIGNAL:
|
||||
case T_SLOT:
|
||||
break; // good
|
||||
|
||||
default:
|
||||
// that's a bad token :)
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for include preprocessor directive
|
||||
else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH) {
|
||||
bool include = false;
|
||||
if (tokens.size() >= 3) {
|
||||
if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) ||
|
||||
tokens.at(2).is(T_ANGLE_STRING_LITERAL))) {
|
||||
const Token &directiveToken = tokens.at(1);
|
||||
QString directive = tc.block().text().mid(directiveToken.bytesBegin(),
|
||||
directiveToken.bytes());
|
||||
if (directive == QLatin1String("include") ||
|
||||
directive == QLatin1String("include_next") ||
|
||||
directive == QLatin1String("import")) {
|
||||
include = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!include) {
|
||||
*kind = T_EOF_SYMBOL;
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
int ClangCompletionAssistProcessor::findStartOfName(int pos) const
|
||||
{
|
||||
if (pos == -1)
|
||||
pos = m_interface->position();
|
||||
QChar chr;
|
||||
|
||||
// Skip to the start of a name
|
||||
do {
|
||||
chr = m_interface->characterAt(--pos);
|
||||
} while (chr.isLetterOrNumber() || chr == QLatin1Char('_'));
|
||||
|
||||
return pos + 1;
|
||||
}
|
||||
|
||||
bool ClangCompletionAssistProcessor::accepts() const
|
||||
{
|
||||
const int pos = m_interface->position();
|
||||
unsigned token = T_EOF_SYMBOL;
|
||||
|
||||
const int start = startOfOperator(pos, &token, /*want function call=*/ true);
|
||||
if (start != pos) {
|
||||
if (token == T_POUND) {
|
||||
const int column = pos - m_interface->textDocument()->findBlock(start).position();
|
||||
if (column != 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// Trigger completion after three characters of a name have been typed, when not editing an existing name
|
||||
QChar characterUnderCursor = m_interface->characterAt(pos);
|
||||
if (!characterUnderCursor.isLetterOrNumber() && characterUnderCursor != QLatin1Char('_')) {
|
||||
const int startOfName = findStartOfName(pos);
|
||||
if (pos - startOfName >= 3) {
|
||||
const QChar firstCharacter = m_interface->characterAt(startOfName);
|
||||
if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) {
|
||||
// Finally check that we're not inside a comment or string (code copied from startOfOperator)
|
||||
QTextCursor tc(m_interface->textDocument());
|
||||
tc.setPosition(pos);
|
||||
|
||||
SimpleLexer tokenize;
|
||||
LanguageFeatures lf = tokenize.languageFeatures();
|
||||
lf.qtMocRunEnabled = true;
|
||||
lf.objCEnabled = true;
|
||||
tokenize.setLanguageFeatures(lf);
|
||||
tokenize.setSkipComments(false);
|
||||
const Tokens &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block()));
|
||||
const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1));
|
||||
const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx);
|
||||
|
||||
if (!tk.isComment() && !tk.isLiteral()) {
|
||||
return true;
|
||||
} else if (tk.isLiteral()
|
||||
&& tokens.size() == 3
|
||||
&& tokens.at(0).kind() == T_POUND
|
||||
&& tokens.at(1).kind() == T_IDENTIFIER) {
|
||||
const QString &line = tc.block().text();
|
||||
const Token &idToken = tokens.at(1);
|
||||
const QStringRef &identifier =
|
||||
line.midRef(idToken.bytesBegin(),
|
||||
idToken.bytesEnd() - idToken.bytesBegin());
|
||||
if (identifier == QLatin1String("include")
|
||||
|| identifier == QLatin1String("include_next")
|
||||
|| (m_interface->objcEnabled() && identifier == QLatin1String("import"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates completion proposals for #include and given cursor
|
||||
* @param cursor - cursor placed after opening bracked or quote
|
||||
* @return false if completions list is empty
|
||||
*/
|
||||
bool ClangCompletionAssistProcessor::completeInclude(const QTextCursor &cursor)
|
||||
{
|
||||
QString directoryPrefix;
|
||||
if (m_completionOperator == T_SLASH) {
|
||||
QTextCursor c = cursor;
|
||||
c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
|
||||
QString sel = c.selectedText();
|
||||
int startCharPos = sel.indexOf(QLatin1Char('"'));
|
||||
if (startCharPos == -1) {
|
||||
startCharPos = sel.indexOf(QLatin1Char('<'));
|
||||
m_completionOperator = T_ANGLE_STRING_LITERAL;
|
||||
} else {
|
||||
m_completionOperator = T_STRING_LITERAL;
|
||||
}
|
||||
if (startCharPos != -1)
|
||||
directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
|
||||
}
|
||||
|
||||
// Make completion for all relevant includes
|
||||
CppTools::ProjectPart::HeaderPaths headerPaths = m_interface->headerPaths();
|
||||
const CppTools::ProjectPart::HeaderPath currentFilePath(QFileInfo(m_interface->fileName()).path(),
|
||||
CppTools::ProjectPart::HeaderPath::IncludePath);
|
||||
if (!headerPaths.contains(currentFilePath))
|
||||
headerPaths.append(currentFilePath);
|
||||
|
||||
::Utils::MimeDatabase mdb;
|
||||
const ::Utils::MimeType mimeType = mdb.mimeTypeForName(QLatin1String("text/x-c++hdr"));
|
||||
const QStringList suffixes = mimeType.suffixes();
|
||||
|
||||
foreach (const CppTools::ProjectPart::HeaderPath &headerPath, headerPaths) {
|
||||
QString realPath = headerPath.path;
|
||||
if (!directoryPrefix.isEmpty()) {
|
||||
realPath += QLatin1Char('/');
|
||||
realPath += directoryPrefix;
|
||||
if (headerPath.isFrameworkPath())
|
||||
realPath += QLatin1String(".framework/Headers");
|
||||
}
|
||||
completeIncludePath(realPath, suffixes);
|
||||
}
|
||||
|
||||
return !m_completions.isEmpty();
|
||||
}
|
||||
|
||||
bool ClangCompletionAssistProcessor::completeInclude(int position)
|
||||
{
|
||||
QTextCursor textCursor(m_interface->textDocument()); // TODO: Simplify, move into function
|
||||
textCursor.setPosition(position);
|
||||
return completeInclude(textCursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds #include completion proposals using given include path
|
||||
* @param realPath - one of directories where compiler searches includes
|
||||
* @param suffixes - file suffixes for C/C++ header files
|
||||
*/
|
||||
void ClangCompletionAssistProcessor::completeIncludePath(const QString &realPath,
|
||||
const QStringList &suffixes)
|
||||
{
|
||||
QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
//: Parent folder for proposed #include completion
|
||||
const QString hint = tr("Location: %1").arg(QDir::toNativeSeparators(QDir::cleanPath(realPath)));
|
||||
while (i.hasNext()) {
|
||||
const QString fileName = i.next();
|
||||
const QFileInfo fileInfo = i.fileInfo();
|
||||
const QString suffix = fileInfo.suffix();
|
||||
if (suffix.isEmpty() || suffixes.contains(suffix)) {
|
||||
QString text = fileName.mid(realPath.length() + 1);
|
||||
if (fileInfo.isDir())
|
||||
text += QLatin1Char('/');
|
||||
|
||||
ClangAssistProposalItem *item = new ClangAssistProposalItem;
|
||||
item->setText(text);
|
||||
item->setDetail(hint);
|
||||
item->setIcon(m_icons.keywordIcon());
|
||||
item->keepCompletionOperator(m_completionOperator);
|
||||
m_completions.append(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ClangCompletionAssistProcessor::completePreprocessorDirectives()
|
||||
{
|
||||
foreach (const QString &preprocessorCompletion, m_preprocessorCompletions)
|
||||
addCompletionItem(preprocessorCompletion,
|
||||
m_icons.iconForType(Icons::MacroIconType));
|
||||
|
||||
if (m_interface->objcEnabled())
|
||||
addCompletionItem(QLatin1String("import"),
|
||||
m_icons.iconForType(Icons::MacroIconType));
|
||||
|
||||
return !m_completions.isEmpty();
|
||||
}
|
||||
|
||||
bool ClangCompletionAssistProcessor::completeDoxygenKeywords()
|
||||
{
|
||||
for (int i = 1; i < CppTools::T_DOXY_LAST_TAG; ++i)
|
||||
addCompletionItem(QString::fromLatin1(CppTools::doxygenTagSpell(i)), m_icons.keywordIcon());
|
||||
return !m_completions.isEmpty();
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::addCompletionItem(const QString &text,
|
||||
const QIcon &icon,
|
||||
int order,
|
||||
const QVariant &data)
|
||||
{
|
||||
ClangAssistProposalItem *item = new ClangAssistProposalItem;
|
||||
item->setText(text);
|
||||
item->setIcon(icon);
|
||||
item->setOrder(order);
|
||||
item->setData(data);
|
||||
item->keepCompletionOperator(m_completionOperator);
|
||||
m_completions.append(item);
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::sendFileContent(const QString &projectFilePath,
|
||||
const QByteArray &modifiedFileContent)
|
||||
{
|
||||
const QString filePath = m_interface->fileName();
|
||||
const QByteArray unsavedContent = modifiedFileContent.isEmpty()
|
||||
? m_interface->textDocument()->toPlainText().toUtf8()
|
||||
: modifiedFileContent;
|
||||
const bool hasUnsavedContent = true; // TODO
|
||||
|
||||
IpcCommunicator &ipcCommunicator = m_interface->ipcCommunicator();
|
||||
ipcCommunicator.registerFilesForCodeCompletion(
|
||||
{ClangBackEnd::FileContainer(filePath,
|
||||
projectFilePath,
|
||||
Utf8String::fromByteArray(unsavedContent),
|
||||
hasUnsavedContent)});
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::sendCompletionRequest(int position,
|
||||
const QByteArray &modifiedFileContent)
|
||||
{
|
||||
int line, column;
|
||||
TextEditor::Convenience::convertPosition(m_interface->textDocument(), position, &line, &column);
|
||||
++column;
|
||||
|
||||
const QString filePath = m_interface->fileName();
|
||||
const QString projectFilePath = Utils::projectFilePathForFile(filePath);
|
||||
sendFileContent(projectFilePath, modifiedFileContent);
|
||||
m_interface->ipcCommunicator().completeCode(this, filePath, line, column, projectFilePath);
|
||||
}
|
||||
|
||||
TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal() const
|
||||
{
|
||||
ClangAssistProposalModel *model = new ClangAssistProposalModel;
|
||||
model->loadContent(m_completions);
|
||||
return new ClangAssistProposal(m_positionForProposal, model);
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::onCompletionsAvailable(const CodeCompletions &completions)
|
||||
{
|
||||
QTC_CHECK(m_completions.isEmpty());
|
||||
|
||||
m_completions = toAssistProposalItems(completions);
|
||||
if (m_addSnippets)
|
||||
addSnippets();
|
||||
|
||||
setAsyncProposalAvailable(createProposal());
|
||||
}
|
||||
|
||||
void ClangCompletionAssistProcessor::onFunctionHintCompletionsAvailable(
|
||||
const CodeCompletions &completions)
|
||||
{
|
||||
QTC_CHECK(!m_functionName.isEmpty());
|
||||
const auto relevantCompletions = matchingFunctionCompletions(completions, m_functionName);
|
||||
|
||||
if (!relevantCompletions.isEmpty()) {
|
||||
TextEditor::IFunctionHintProposalModel *model = new ClangFunctionHintModel(relevantCompletions);
|
||||
TextEditor::FunctionHintProposal *proposal = new FunctionHintProposal(m_positionForProposal, model);
|
||||
|
||||
setAsyncProposalAvailable(proposal);
|
||||
} else {
|
||||
QTC_CHECK(!"Function completion failed. Would fallback to global completion here...");
|
||||
// TODO: If we need this, the processor can't be deleted in IpcClient.
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
Reference in New Issue
Block a user