forked from qt-creator/qt-creator
* Rename some members/functions to clarify their meaning. * Ensure that the position for the proposal widget is at start of the identifer, so that the filter prefix will be found correctly in the GenericProposalWidget. For certain cases the completion were calculated but the widget was never shown: Case 1: void f() { <COMPLETION_CURSOR> } Case 2: void f() { st<COMPLETION_CURSOR> } Case 3: if (true) <COMPLETION_CURSOR> Case 4: foo. mem<COMPLETION_CURSOR> Change-Id: Ie79e01e8a22f8ec306136ec4ccbfffd544edd573 Reviewed-by: Erik Verbruggen <erik.verbruggen@theqtcompany.com>
735 lines
28 KiB
C++
735 lines
28 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 "activationsequenceprocessor.h"
|
|
#include "clangassistproposal.h"
|
|
#include "clangassistproposalmodel.h"
|
|
#include "clangcompletionassistprocessor.h"
|
|
#include "clangcompletioncontextanalyzer.h"
|
|
#include "clangfunctionhintmodel.h"
|
|
#include "clangutils.h"
|
|
#include "completionchunkstotextconverter.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 ClangBackEnd::CodeCompletion;
|
|
using TextEditor::AssistProposalItem;
|
|
|
|
namespace {
|
|
|
|
const char SNIPPET_ICON_PATH[] = ":/texteditor/images/snippet.png";
|
|
|
|
|
|
QList<AssistProposalItem *> toAssistProposalItems(const CodeCompletions &completions)
|
|
{
|
|
static CPlusPlus::Icons m_icons; // de-deduplicate
|
|
|
|
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;
|
|
|
|
QString name;
|
|
if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind)
|
|
name = CompletionChunksToTextConverter::convertToName(ccr.chunks());
|
|
else
|
|
name = ccr.text().toString();
|
|
|
|
ClangAssistProposalItem *item = items.value(name, 0);
|
|
if (item) {
|
|
item->addOverload(ccr);
|
|
} else {
|
|
item = new ClangAssistProposalItem;
|
|
items.insert(name, item);
|
|
item->setText(name);
|
|
item->setOrder(ccr.priority());
|
|
|
|
if (ccr.completionKind() == CodeCompletion::KeywordCompletionKind)
|
|
item->setDetail(CompletionChunksToTextConverter::convertToToolTip(ccr.chunks()));
|
|
|
|
item->setCodeCompletion(ccr);
|
|
}
|
|
|
|
// FIXME: show the effective accessebility instead of availability
|
|
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;
|
|
}
|
|
}
|
|
|
|
QList<AssistProposalItem *> results;
|
|
results.reserve(items.size());
|
|
std::copy(items.cbegin(), items.cend(), std::back_inserter(results));
|
|
|
|
return results;
|
|
}
|
|
|
|
bool isFunctionHintLikeCompletion(CodeCompletion::Kind kind)
|
|
{
|
|
return kind == CodeCompletion::FunctionCompletionKind
|
|
|| kind == CodeCompletion::ConstructorCompletionKind
|
|
|| kind == CodeCompletion::DestructorCompletionKind
|
|
|| kind == CodeCompletion::SignalCompletionKind
|
|
|| kind == CodeCompletion::SlotCompletionKind;
|
|
}
|
|
|
|
CodeCompletions matchingFunctionCompletions(const CodeCompletions completions,
|
|
const QString &functionName)
|
|
{
|
|
CodeCompletions 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
|
|
}
|
|
|
|
bool ClangCompletionAssistProcessor::handleAvailableAsyncCompletions(
|
|
const CodeCompletions &completions)
|
|
{
|
|
bool handled = true;
|
|
|
|
switch (m_sentRequestType) {
|
|
case CompletionRequestType::NormalCompletion:
|
|
handleAvailableCompletions(completions);
|
|
break;
|
|
case CompletionRequestType::FunctionHintCompletion:
|
|
handled = handleAvailableFunctionHintCompletions(completions);
|
|
break;
|
|
default:
|
|
QTC_CHECK(!"Unhandled ClangCompletionAssistProcessor::CompletionRequestType");
|
|
break;
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
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()
|
|
{
|
|
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 positionInDocument,
|
|
unsigned *kind,
|
|
bool wantFunctionCall) const
|
|
{
|
|
auto activationSequence = m_interface->textAt(positionInDocument - 3, 3);
|
|
ActivationSequenceProcessor activationSequenceProcessor(activationSequence,
|
|
positionInDocument,
|
|
wantFunctionCall);
|
|
|
|
*kind = activationSequenceProcessor.completionKind();
|
|
|
|
int start = activationSequenceProcessor.operatorStartPosition();
|
|
if (start != positionInDocument) {
|
|
QTextCursor tc(m_interface->textDocument());
|
|
tc.setPosition(positionInDocument);
|
|
|
|
// 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 = positionInDocument;
|
|
}
|
|
}
|
|
|
|
if (*kind == T_COMMA) {
|
|
ExpressionUnderCursor expressionUnderCursor(m_interface->languageFeatures());
|
|
if (expressionUnderCursor.startOfFunctionCall(tc) == -1) {
|
|
*kind = T_EOF_SYMBOL;
|
|
start = positionInDocument;
|
|
}
|
|
}
|
|
|
|
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 = positionInDocument;
|
|
}
|
|
// 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 = positionInDocument;
|
|
}
|
|
// 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 = positionInDocument;
|
|
}
|
|
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 = positionInDocument;
|
|
}
|
|
}
|
|
}
|
|
// 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 = positionInDocument;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
ClangCompletionAssistProcessor::UnsavedFileContentInfo
|
|
ClangCompletionAssistProcessor::unsavedFileContent(const QByteArray &customFileContent) const
|
|
{
|
|
const bool hasCustomModification = !customFileContent.isEmpty();
|
|
|
|
UnsavedFileContentInfo info;
|
|
info.isDocumentModified = hasCustomModification || m_interface->textDocument()->isModified();
|
|
info.unsavedContent = hasCustomModification
|
|
? customFileContent
|
|
: m_interface->textDocument()->toPlainText().toUtf8();
|
|
return info;
|
|
}
|
|
|
|
void ClangCompletionAssistProcessor::sendFileContent(const QString &projectPartId,
|
|
const QByteArray &customFileContent)
|
|
{
|
|
// TODO: Revert custom modification after the completions
|
|
const UnsavedFileContentInfo info = unsavedFileContent(customFileContent);
|
|
|
|
IpcCommunicator &ipcCommunicator = m_interface->ipcCommunicator();
|
|
ipcCommunicator.registerFilesForCodeCompletion(
|
|
{ClangBackEnd::FileContainer(m_interface->fileName(),
|
|
projectPartId,
|
|
Utf8String::fromByteArray(info.unsavedContent),
|
|
info.isDocumentModified)});
|
|
}
|
|
|
|
void ClangCompletionAssistProcessor::sendCompletionRequest(int position,
|
|
const QByteArray &customFileContent)
|
|
{
|
|
int line, column;
|
|
TextEditor::Convenience::convertPosition(m_interface->textDocument(), position, &line, &column);
|
|
++column;
|
|
|
|
const QString filePath = m_interface->fileName();
|
|
const QString projectPartId = Utils::projectPartIdForFile(filePath);
|
|
sendFileContent(projectPartId, customFileContent);
|
|
m_interface->ipcCommunicator().completeCode(this, filePath, line, column, projectPartId);
|
|
}
|
|
|
|
TextEditor::IAssistProposal *ClangCompletionAssistProcessor::createProposal() const
|
|
{
|
|
ClangAssistProposalModel *model = new ClangAssistProposalModel;
|
|
model->loadContent(m_completions);
|
|
return new ClangAssistProposal(m_positionForProposal, model);
|
|
}
|
|
|
|
void ClangCompletionAssistProcessor::handleAvailableCompletions(const CodeCompletions &completions)
|
|
{
|
|
QTC_CHECK(m_completions.isEmpty());
|
|
|
|
m_completions = toAssistProposalItems(completions);
|
|
if (m_addSnippets)
|
|
addSnippets();
|
|
|
|
setAsyncProposalAvailable(createProposal());
|
|
}
|
|
|
|
bool ClangCompletionAssistProcessor::handleAvailableFunctionHintCompletions(
|
|
const CodeCompletions &completions)
|
|
{
|
|
QTC_CHECK(!m_functionName.isEmpty());
|
|
const auto relevantCompletions = matchingFunctionCompletions(completions, m_functionName);
|
|
|
|
if (!relevantCompletions.isEmpty()) {
|
|
auto *model = new ClangFunctionHintModel(relevantCompletions);
|
|
auto *proposal = new FunctionHintProposal(m_positionForProposal, model);
|
|
|
|
setAsyncProposalAvailable(proposal);
|
|
return true;
|
|
} else {
|
|
m_addSnippets = false;
|
|
m_functionName.clear();
|
|
m_sentRequestType = NormalCompletion;
|
|
sendCompletionRequest(m_interface->position(), QByteArray());
|
|
return false; // We are not yet finished.
|
|
}
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace ClangCodeModel
|
|
|