forked from qt-creator/qt-creator
ClangCodeModel: Use clangd for completion and function hint
Change-Id: I80160f3a40da18ac178682afe6caba5e5af6e3eb Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -276,7 +276,7 @@ Position::Position(const QTextCursor &cursor)
|
||||
: Position(cursor.blockNumber(), cursor.positionInBlock())
|
||||
{ }
|
||||
|
||||
int Position::toPositionInDocument(QTextDocument *doc) const
|
||||
int Position::toPositionInDocument(const QTextDocument *doc) const
|
||||
{
|
||||
const QTextBlock block = doc->findBlockByNumber(line());
|
||||
if (!block.isValid())
|
||||
|
@@ -88,7 +88,7 @@ public:
|
||||
bool isValid() const override
|
||||
{ return contains(lineKey) && contains(characterKey); }
|
||||
|
||||
int toPositionInDocument(QTextDocument *doc) const;
|
||||
int toPositionInDocument(const QTextDocument *doc) const;
|
||||
QTextCursor toTextCursor(QTextDocument *doc) const;
|
||||
};
|
||||
|
||||
|
@@ -30,16 +30,19 @@
|
||||
#include <cplusplus/BackwardsScanner.h>
|
||||
#include <cplusplus/ExpressionUnderCursor.h>
|
||||
#include <cplusplus/SimpleLexer.h>
|
||||
#include <utils/textutils.h>
|
||||
|
||||
#include <QTextDocument>
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
|
||||
ActivationSequenceContextProcessor::ActivationSequenceContextProcessor(const ClangCompletionAssistInterface *assistInterface)
|
||||
: m_textCursor(assistInterface->textDocument()),
|
||||
m_assistInterface(assistInterface),
|
||||
m_positionInDocument(assistInterface->position()),
|
||||
ActivationSequenceContextProcessor::ActivationSequenceContextProcessor(
|
||||
QTextDocument *document, int position, CPlusPlus::LanguageFeatures languageFeatures)
|
||||
: m_textCursor(document),
|
||||
m_document(document),
|
||||
m_languageFeatures(languageFeatures),
|
||||
m_positionInDocument(position),
|
||||
m_startOfNamePosition(m_positionInDocument),
|
||||
m_operatorStartPosition(m_positionInDocument)
|
||||
|
||||
@@ -49,6 +52,13 @@ ActivationSequenceContextProcessor::ActivationSequenceContextProcessor(const Cla
|
||||
process();
|
||||
}
|
||||
|
||||
ActivationSequenceContextProcessor::ActivationSequenceContextProcessor(
|
||||
const ClangCompletionAssistInterface *interface)
|
||||
: ActivationSequenceContextProcessor(interface->textDocument(), interface->position(),
|
||||
interface->languageFeatures())
|
||||
{
|
||||
}
|
||||
|
||||
CPlusPlus::Kind ActivationSequenceContextProcessor::completionKind() const
|
||||
{
|
||||
return m_completionKind;
|
||||
@@ -91,8 +101,9 @@ void ActivationSequenceContextProcessor::process()
|
||||
|
||||
void ActivationSequenceContextProcessor::processActivationSequence()
|
||||
{
|
||||
const int nonSpacePosition = skipPrecedingWhitespace(m_assistInterface, m_startOfNamePosition);
|
||||
const auto activationSequence = m_assistInterface->textAt(nonSpacePosition - 3, 3);
|
||||
const int nonSpacePosition = skipPrecedingWhitespace(m_document, m_startOfNamePosition);
|
||||
const auto activationSequence = Utils::Text::textAt(QTextCursor(m_document),
|
||||
nonSpacePosition - 3, 3);
|
||||
ActivationSequenceProcessor activationSequenceProcessor(activationSequence,
|
||||
nonSpacePosition,
|
||||
true);
|
||||
@@ -115,7 +126,7 @@ void ActivationSequenceContextProcessor::processStringLiteral()
|
||||
void ActivationSequenceContextProcessor::processComma()
|
||||
{
|
||||
if (m_completionKind == CPlusPlus::T_COMMA) {
|
||||
CPlusPlus::ExpressionUnderCursor expressionUnderCursor(m_assistInterface->languageFeatures());
|
||||
CPlusPlus::ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
|
||||
if (expressionUnderCursor.startOfFunctionCall(m_textCursor) == -1)
|
||||
m_completionKind = CPlusPlus::T_EOF_SYMBOL;
|
||||
}
|
||||
@@ -124,7 +135,7 @@ void ActivationSequenceContextProcessor::processComma()
|
||||
void ActivationSequenceContextProcessor::generateTokens()
|
||||
{
|
||||
CPlusPlus::SimpleLexer tokenize;
|
||||
tokenize.setLanguageFeatures(m_assistInterface->languageFeatures());
|
||||
tokenize.setLanguageFeatures(m_languageFeatures);
|
||||
tokenize.setSkipComments(false);
|
||||
auto state = CPlusPlus::BackwardsScanner::previousBlockState(m_textCursor.block());
|
||||
m_tokens = tokenize(m_textCursor.block().text(), state);
|
||||
@@ -222,12 +233,11 @@ void ActivationSequenceContextProcessor::resetPositionsForEOFCompletionKind()
|
||||
m_operatorStartPosition = m_positionInDocument;
|
||||
}
|
||||
|
||||
int ActivationSequenceContextProcessor::skipPrecedingWhitespace(
|
||||
const TextEditor::AssistInterface *assistInterface,
|
||||
int ActivationSequenceContextProcessor::skipPrecedingWhitespace(const QTextDocument *document,
|
||||
int startPosition)
|
||||
{
|
||||
int position = startPosition;
|
||||
while (assistInterface->characterAt(position - 1).isSpace())
|
||||
while (document->characterAt(position - 1).isSpace())
|
||||
--position;
|
||||
return position;
|
||||
}
|
||||
@@ -241,7 +251,7 @@ static bool isValidIdentifierChar(const QChar &character)
|
||||
}
|
||||
|
||||
int ActivationSequenceContextProcessor::findStartOfName(
|
||||
const TextEditor::AssistInterface *assistInterface,
|
||||
const QTextDocument *document,
|
||||
int startPosition,
|
||||
NameCategory category)
|
||||
{
|
||||
@@ -249,32 +259,32 @@ int ActivationSequenceContextProcessor::findStartOfName(
|
||||
QChar character;
|
||||
|
||||
if (category == NameCategory::Function
|
||||
&& position > 2 && assistInterface->characterAt(position - 1) == '>'
|
||||
&& assistInterface->characterAt(position - 2) != '-') {
|
||||
&& position > 2 && document->characterAt(position - 1) == '>'
|
||||
&& document->characterAt(position - 2) != '-') {
|
||||
uint unbalancedLessGreater = 1;
|
||||
--position;
|
||||
while (unbalancedLessGreater > 0 && position > 2) {
|
||||
character = assistInterface->characterAt(--position);
|
||||
character = document->characterAt(--position);
|
||||
// Do not count -> usage inside temlate argument list
|
||||
if (character == '<')
|
||||
--unbalancedLessGreater;
|
||||
else if (character == '>' && assistInterface->characterAt(position-1) != '-')
|
||||
else if (character == '>' && document->characterAt(position-1) != '-')
|
||||
++unbalancedLessGreater;
|
||||
}
|
||||
position = skipPrecedingWhitespace(assistInterface, position) - 1;
|
||||
position = skipPrecedingWhitespace(document, position) - 1;
|
||||
}
|
||||
|
||||
do {
|
||||
character = assistInterface->characterAt(--position);
|
||||
character = document->characterAt(--position);
|
||||
} while (isValidIdentifierChar(character));
|
||||
|
||||
int prevPosition = skipPrecedingWhitespace(assistInterface, position);
|
||||
int prevPosition = skipPrecedingWhitespace(document, position);
|
||||
if (category == NameCategory::Function
|
||||
&& assistInterface->characterAt(prevPosition) == ':'
|
||||
&& assistInterface->characterAt(prevPosition - 1) == ':') {
|
||||
&& document->characterAt(prevPosition) == ':'
|
||||
&& document->characterAt(prevPosition - 1) == ':') {
|
||||
// Handle :: case - go recursive
|
||||
prevPosition = skipPrecedingWhitespace(assistInterface, prevPosition - 2);
|
||||
return findStartOfName(assistInterface, prevPosition + 1, category);
|
||||
prevPosition = skipPrecedingWhitespace(document, prevPosition - 2);
|
||||
return findStartOfName(document, prevPosition + 1, category);
|
||||
}
|
||||
|
||||
return position + 1;
|
||||
@@ -283,7 +293,7 @@ int ActivationSequenceContextProcessor::findStartOfName(
|
||||
void ActivationSequenceContextProcessor::goBackToStartOfName()
|
||||
{
|
||||
CPlusPlus::SimpleLexer tokenize;
|
||||
tokenize.setLanguageFeatures(m_assistInterface->languageFeatures());
|
||||
tokenize.setLanguageFeatures(m_languageFeatures);
|
||||
tokenize.setSkipComments(false);
|
||||
const int state = CPlusPlus::BackwardsScanner::previousBlockState(m_textCursor.block());
|
||||
const CPlusPlus::Tokens tokens = tokenize(m_textCursor.block().text(), state);
|
||||
@@ -297,7 +307,7 @@ void ActivationSequenceContextProcessor::goBackToStartOfName()
|
||||
m_startOfNamePosition = m_textCursor.block().position() + std::max(slashIndex, tokenStart)
|
||||
+ 1;
|
||||
} else {
|
||||
m_startOfNamePosition = findStartOfName(m_assistInterface, m_positionInDocument);
|
||||
m_startOfNamePosition = findStartOfName(m_document, m_positionInDocument);
|
||||
}
|
||||
|
||||
if (m_startOfNamePosition != m_positionInDocument)
|
||||
|
@@ -41,7 +41,9 @@ namespace Internal {
|
||||
class ActivationSequenceContextProcessor
|
||||
{
|
||||
public:
|
||||
ActivationSequenceContextProcessor(const ClangCompletionAssistInterface *assistInterface);
|
||||
ActivationSequenceContextProcessor(QTextDocument *document, int position,
|
||||
CPlusPlus::LanguageFeatures languageFeatures);
|
||||
ActivationSequenceContextProcessor(const ClangCompletionAssistInterface *interface);
|
||||
|
||||
CPlusPlus::Kind completionKind() const;
|
||||
int startOfNamePosition() const; // e.g. points to 'b' in "foo.bar<CURSOR>"
|
||||
@@ -50,10 +52,10 @@ public:
|
||||
const QTextCursor &textCursor_forTestOnly() const;
|
||||
|
||||
enum class NameCategory { Function, NonFunction };
|
||||
static int findStartOfName(const TextEditor::AssistInterface *assistInterface,
|
||||
static int findStartOfName(const QTextDocument *document,
|
||||
int startPosition,
|
||||
NameCategory category = NameCategory::NonFunction);
|
||||
static int skipPrecedingWhitespace(const TextEditor::AssistInterface *assistInterface,
|
||||
static int skipPrecedingWhitespace(const QTextDocument *document,
|
||||
int startPosition);
|
||||
|
||||
protected:
|
||||
@@ -78,7 +80,8 @@ private:
|
||||
QVector<CPlusPlus::Token> m_tokens;
|
||||
QTextCursor m_textCursor;
|
||||
CPlusPlus::Token m_token;
|
||||
const ClangCompletionAssistInterface *m_assistInterface;
|
||||
QTextDocument * const m_document;
|
||||
const CPlusPlus::LanguageFeatures m_languageFeatures;
|
||||
int m_tokenIndex;
|
||||
const int m_positionInDocument;
|
||||
int m_startOfNamePosition;
|
||||
|
@@ -79,41 +79,6 @@ bool ClangAssistProposalItem::implicitlyApplies() const
|
||||
return true;
|
||||
}
|
||||
|
||||
static QString textUntilPreviousStatement(TextDocumentManipulatorInterface &manipulator,
|
||||
int startPosition)
|
||||
{
|
||||
static const QString stopCharacters(";{}#");
|
||||
|
||||
int endPosition = 0;
|
||||
for (int i = startPosition; i >= 0 ; --i) {
|
||||
if (stopCharacters.contains(manipulator.characterAt(i))) {
|
||||
endPosition = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return manipulator.textAt(endPosition, startPosition - endPosition);
|
||||
}
|
||||
|
||||
// 7.3.3: using typename(opt) nested-name-specifier unqualified-id ;
|
||||
static bool isAtUsingDeclaration(TextDocumentManipulatorInterface &manipulator,
|
||||
int basePosition)
|
||||
{
|
||||
SimpleLexer lexer;
|
||||
lexer.setLanguageFeatures(LanguageFeatures::defaultFeatures());
|
||||
const QString textToLex = textUntilPreviousStatement(manipulator, basePosition);
|
||||
const Tokens tokens = lexer(textToLex);
|
||||
if (tokens.empty())
|
||||
return false;
|
||||
|
||||
// The nested-name-specifier always ends with "::", so check for this first.
|
||||
const Token lastToken = tokens[tokens.size() - 1];
|
||||
if (lastToken.kind() != T_COLON_COLON)
|
||||
return false;
|
||||
|
||||
return contains(tokens, [](const Token &token) { return token.kind() == T_USING; });
|
||||
}
|
||||
|
||||
static QString methodDefinitionParameters(const CodeCompletionChunks &chunks)
|
||||
{
|
||||
QString result;
|
||||
|
@@ -210,6 +210,7 @@ QVector<QObject *> ClangCodeModelPlugin::createTestObjects() const
|
||||
{
|
||||
return {
|
||||
new Tests::ClangCodeCompletionTest,
|
||||
new Tests::ClangdTestCompletion,
|
||||
new Tests::ClangdTestFindReferences,
|
||||
new Tests::ClangdTestFollowSymbol,
|
||||
new Tests::ClangdTestHighlighting,
|
||||
|
@@ -68,24 +68,33 @@ namespace Internal {
|
||||
ClangCompletionContextAnalyzer::ClangCompletionContextAnalyzer(
|
||||
const ClangCompletionAssistInterface *assistInterface,
|
||||
CPlusPlus::LanguageFeatures languageFeatures)
|
||||
: m_interface(assistInterface)
|
||||
, m_languageFeatures(languageFeatures)
|
||||
: ClangCompletionContextAnalyzer(assistInterface->textDocument(), assistInterface->position(),
|
||||
assistInterface->type() == CompletionType::FunctionHint,
|
||||
languageFeatures)
|
||||
{
|
||||
}
|
||||
|
||||
ClangCompletionContextAnalyzer::ClangCompletionContextAnalyzer(
|
||||
QTextDocument *document, int position, bool isFunctionHint,
|
||||
CPlusPlus::LanguageFeatures languageFeatures)
|
||||
: m_document(document), m_position(position), m_isFunctionHint(isFunctionHint),
|
||||
m_languageFeatures(languageFeatures)
|
||||
{
|
||||
}
|
||||
|
||||
void ClangCompletionContextAnalyzer::analyze()
|
||||
{
|
||||
QTC_ASSERT(m_interface, return);
|
||||
QTC_ASSERT(m_document, return);
|
||||
setActionAndClangPosition(PassThroughToLibClang, -1);
|
||||
|
||||
ActivationSequenceContextProcessor activationSequenceContextProcessor(m_interface);
|
||||
ActivationSequenceContextProcessor activationSequenceContextProcessor(
|
||||
m_document, m_position, m_languageFeatures);
|
||||
m_completionOperator = activationSequenceContextProcessor.completionKind();
|
||||
int afterOperatorPosition = activationSequenceContextProcessor.startOfNamePosition();
|
||||
m_positionEndOfExpression = activationSequenceContextProcessor.operatorStartPosition();
|
||||
m_positionForProposal = activationSequenceContextProcessor.startOfNamePosition();
|
||||
|
||||
const bool actionIsSet = m_interface->type() != CompletionType::FunctionHint
|
||||
&& handleNonFunctionCall(afterOperatorPosition);
|
||||
const bool actionIsSet = !m_isFunctionHint && handleNonFunctionCall(afterOperatorPosition);
|
||||
if (!actionIsSet) {
|
||||
handleCommaInFunctionCall();
|
||||
handleFunctionCall(afterOperatorPosition);
|
||||
@@ -94,20 +103,20 @@ void ClangCompletionContextAnalyzer::analyze()
|
||||
|
||||
int ClangCompletionContextAnalyzer::startOfFunctionCall(int endOfOperator) const
|
||||
{
|
||||
int index = ActivationSequenceContextProcessor::skipPrecedingWhitespace(m_interface,
|
||||
int index = ActivationSequenceContextProcessor::skipPrecedingWhitespace(m_document,
|
||||
endOfOperator);
|
||||
QTextCursor textCursor(m_interface->textDocument());
|
||||
QTextCursor textCursor(m_document);
|
||||
textCursor.setPosition(index);
|
||||
|
||||
ExpressionUnderCursor euc(m_languageFeatures);
|
||||
index = euc.startOfFunctionCall(textCursor);
|
||||
index = ActivationSequenceContextProcessor::skipPrecedingWhitespace(m_interface, index);
|
||||
index = ActivationSequenceContextProcessor::skipPrecedingWhitespace(m_document, index);
|
||||
const int functionNameStart = ActivationSequenceContextProcessor::findStartOfName(
|
||||
m_interface, index, ActivationSequenceContextProcessor::NameCategory::Function);
|
||||
m_document, index, ActivationSequenceContextProcessor::NameCategory::Function);
|
||||
if (functionNameStart == -1)
|
||||
return -1;
|
||||
|
||||
QTextCursor functionNameSelector(m_interface->textDocument());
|
||||
QTextCursor functionNameSelector(m_document);
|
||||
functionNameSelector.setPosition(functionNameStart);
|
||||
functionNameSelector.setPosition(index, QTextCursor::KeepAnchor);
|
||||
const QString functionName = functionNameSelector.selectedText().trimmed();
|
||||
@@ -137,12 +146,12 @@ void ClangCompletionContextAnalyzer::handleCommaInFunctionCall()
|
||||
{
|
||||
if (m_completionOperator == T_COMMA) {
|
||||
ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
|
||||
QTextCursor textCursor(m_interface->textDocument());
|
||||
QTextCursor textCursor(m_document);
|
||||
textCursor.setPosition(m_positionEndOfExpression);
|
||||
const int start = expressionUnderCursor.startOfFunctionCall(textCursor);
|
||||
m_positionEndOfExpression = start;
|
||||
m_positionForProposal = start + 1; // After '(' of function call
|
||||
if (m_interface->characterAt(start) == '(')
|
||||
if (m_document->characterAt(start) == '(')
|
||||
m_completionOperator = T_LPAREN;
|
||||
else
|
||||
m_completionOperator = T_LBRACE;
|
||||
@@ -151,7 +160,7 @@ void ClangCompletionContextAnalyzer::handleCommaInFunctionCall()
|
||||
|
||||
void ClangCompletionContextAnalyzer::handleFunctionCall(int afterOperatorPosition)
|
||||
{
|
||||
if (m_interface->type() == CompletionType::FunctionHint) {
|
||||
if (m_isFunctionHint) {
|
||||
const int functionNameStart = startOfFunctionCall(afterOperatorPosition);
|
||||
if (functionNameStart >= 0) {
|
||||
m_addSnippets = functionNameStart == afterOperatorPosition;
|
||||
@@ -166,15 +175,20 @@ void ClangCompletionContextAnalyzer::handleFunctionCall(int afterOperatorPositio
|
||||
|
||||
if (m_completionOperator == T_LPAREN || m_completionOperator == T_LBRACE) {
|
||||
ExpressionUnderCursor expressionUnderCursor(m_languageFeatures);
|
||||
QTextCursor textCursor(m_interface->textDocument());
|
||||
QTextCursor textCursor(m_document);
|
||||
textCursor.setPosition(m_positionEndOfExpression);
|
||||
const QString expression = expressionUnderCursor(textCursor);
|
||||
const QString trimmedExpression = expression.trimmed();
|
||||
const QChar lastExprChar = trimmedExpression.isEmpty()
|
||||
? QChar() : trimmedExpression.at(trimmedExpression.length() - 1);
|
||||
const bool mightBeConstructorCall = lastExprChar != ')';
|
||||
|
||||
if (expression.endsWith(QLatin1String("SIGNAL"))) {
|
||||
setActionAndClangPosition(CompleteSignal, afterOperatorPosition);
|
||||
} else if (expression.endsWith(QLatin1String("SLOT"))) {
|
||||
setActionAndClangPosition(CompleteSlot, afterOperatorPosition);
|
||||
} else if (m_interface->position() != afterOperatorPosition) {
|
||||
} else if (m_position != afterOperatorPosition
|
||||
|| (m_completionOperator == T_LBRACE && !mightBeConstructorCall)) {
|
||||
// No function completion if cursor is not after '(' or ','
|
||||
m_addSnippets = true;
|
||||
m_positionForProposal = afterOperatorPosition;
|
||||
|
@@ -29,6 +29,10 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QTextDocument;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace TextEditor { class AssistInterface; }
|
||||
|
||||
namespace ClangCodeModel {
|
||||
@@ -42,6 +46,8 @@ public:
|
||||
ClangCompletionContextAnalyzer() = delete;
|
||||
ClangCompletionContextAnalyzer(const ClangCompletionAssistInterface *assistInterface,
|
||||
CPlusPlus::LanguageFeatures languageFeatures);
|
||||
ClangCompletionContextAnalyzer(QTextDocument *document, int position, bool isFunctionHint,
|
||||
CPlusPlus::LanguageFeatures languageFeatures);
|
||||
void analyze();
|
||||
|
||||
enum CompletionAction {
|
||||
@@ -75,8 +81,10 @@ private:
|
||||
void handleFunctionCall(int endOfOperator);
|
||||
|
||||
private:
|
||||
const ClangCompletionAssistInterface *m_interface; // Not owned
|
||||
const CPlusPlus::LanguageFeatures m_languageFeatures; // TODO: Get from assistInterface?!
|
||||
QTextDocument * const m_document;
|
||||
const int m_position;
|
||||
const bool m_isFunctionHint;
|
||||
const CPlusPlus::LanguageFeatures m_languageFeatures;
|
||||
|
||||
// Results
|
||||
CompletionAction m_completionAction = PassThroughToLibClang;
|
||||
|
@@ -25,7 +25,9 @@
|
||||
|
||||
#include "clangdclient.h"
|
||||
|
||||
#include "clangcompletioncontextanalyzer.h"
|
||||
#include "clangdiagnosticmanager.h"
|
||||
#include "clangpreprocessorassistproposalitem.h"
|
||||
#include "clangtextmark.h"
|
||||
#include "clangutils.h"
|
||||
|
||||
@@ -34,6 +36,11 @@
|
||||
#include <coreplugin/find/searchresultitem.h>
|
||||
#include <coreplugin/find/searchresultwindow.h>
|
||||
#include <cplusplus/FindUsages.h>
|
||||
#include <cplusplus/Icons.h>
|
||||
#include <cplusplus/MatchingText.h>
|
||||
#include <cppeditor/cppeditorconstants.h>
|
||||
#include <cpptools/cppcodemodelsettings.h>
|
||||
#include <cpptools/cppdoxygen.h>
|
||||
#include <cpptools/cppeditorwidgetinterface.h>
|
||||
#include <cpptools/cppfindreferences.h>
|
||||
#include <cpptools/cppmodelmanager.h>
|
||||
@@ -50,6 +57,8 @@
|
||||
#include <texteditor/codeassist/assistinterface.h>
|
||||
#include <texteditor/codeassist/iassistprocessor.h>
|
||||
#include <texteditor/codeassist/iassistprovider.h>
|
||||
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
||||
#include <texteditor/texteditorsettings.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/runextensions.h>
|
||||
@@ -676,6 +685,62 @@ public:
|
||||
{ insert("publishDiagnostics", caps); }
|
||||
};
|
||||
|
||||
class DoxygenAssistProcessor : public TextEditor::IAssistProcessor
|
||||
{
|
||||
public:
|
||||
DoxygenAssistProcessor(int position, unsigned completionOperator,
|
||||
const ProposalHandler &handler)
|
||||
: m_position(position), m_completionOperator(completionOperator), m_handler(handler) {}
|
||||
|
||||
private:
|
||||
TextEditor::IAssistProposal *perform(const TextEditor::AssistInterface *) override
|
||||
{
|
||||
QList<TextEditor::AssistProposalItemInterface *> completions;
|
||||
for (int i = 1; i < CppTools::T_DOXY_LAST_TAG; ++i) {
|
||||
const auto item = new ClangPreprocessorAssistProposalItem;
|
||||
item->setText(QLatin1String(CppTools::doxygenTagSpell(i)));
|
||||
item->setIcon(CPlusPlus::Icons::keywordIcon());
|
||||
item->setCompletionOperator(m_completionOperator);
|
||||
completions.append(item);
|
||||
}
|
||||
TextEditor::GenericProposalModelPtr model(new TextEditor::GenericProposalModel);
|
||||
model->loadContent(completions);
|
||||
const auto proposal = new TextEditor::GenericProposal(m_position, model);
|
||||
if (m_handler) {
|
||||
m_handler(proposal);
|
||||
return nullptr;
|
||||
}
|
||||
return proposal;
|
||||
}
|
||||
|
||||
const int m_position;
|
||||
const unsigned m_completionOperator;
|
||||
const ProposalHandler m_handler;
|
||||
};
|
||||
|
||||
class DoxygenAssistProvider : public TextEditor::IAssistProvider
|
||||
{
|
||||
public:
|
||||
void setProposalHandler(const ProposalHandler &handler) { m_proposalHandler = handler; }
|
||||
|
||||
void setParameters(int position, unsigned completionOperator)
|
||||
{
|
||||
m_position = position;
|
||||
m_completionOperator = completionOperator;
|
||||
}
|
||||
|
||||
private:
|
||||
RunType runType() const override { return Synchronous; }
|
||||
TextEditor::IAssistProcessor *createProcessor() const override
|
||||
{
|
||||
return new DoxygenAssistProcessor(m_position, m_completionOperator, m_proposalHandler);
|
||||
}
|
||||
|
||||
ProposalHandler m_proposalHandler;
|
||||
int m_position = 0;
|
||||
unsigned m_completionOperator = 0;
|
||||
};
|
||||
|
||||
class ClangdClient::Private
|
||||
{
|
||||
public:
|
||||
@@ -711,8 +776,13 @@ public:
|
||||
void handleSemanticTokens(TextEditor::TextDocument *doc,
|
||||
const QList<ExpandedSemanticToken> &tokens);
|
||||
|
||||
void applyCompletionItem(const CompletionItem &item,
|
||||
TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
QChar typedChar);
|
||||
|
||||
ClangdClient * const q;
|
||||
const CppTools::ClangdSettings::Data settings;
|
||||
DoxygenAssistProvider doxygenAssistProvider;
|
||||
QHash<quint64, ReferencesData> runningFindUsages;
|
||||
Utils::optional<FollowSymbolData> followSymbolData;
|
||||
Utils::optional<SwitchDeclDefData> switchDeclDefData;
|
||||
@@ -724,6 +794,16 @@ public:
|
||||
bool isTesting = false;
|
||||
};
|
||||
|
||||
class ClangdCompletionCapabilities : public TextDocumentClientCapabilities::CompletionCapabilities
|
||||
{
|
||||
public:
|
||||
explicit ClangdCompletionCapabilities(const JsonObject &object)
|
||||
: TextDocumentClientCapabilities::CompletionCapabilities(object)
|
||||
{
|
||||
insert("editsNearCursor", true); // For dot-to-arrow correction.
|
||||
}
|
||||
};
|
||||
|
||||
ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
||||
: Client(clientInterface(project, jsonDbDir)), d(new Private(this, project))
|
||||
{
|
||||
@@ -745,12 +825,15 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
||||
Utils::optional<TextDocumentClientCapabilities> textCaps = caps.textDocument();
|
||||
if (textCaps) {
|
||||
ClangdTextDocumentClientCapabilities clangdTextCaps(*textCaps);
|
||||
clangdTextCaps.clearCompletion();
|
||||
clangdTextCaps.clearDocumentHighlight();
|
||||
DiagnosticsCapabilities diagnostics;
|
||||
diagnostics.enableCategorySupport();
|
||||
diagnostics.enableCodeActionsInline();
|
||||
clangdTextCaps.setPublishDiagnostics(diagnostics);
|
||||
Utils::optional<TextDocumentClientCapabilities::CompletionCapabilities> completionCaps
|
||||
= textCaps->completion();
|
||||
if (completionCaps)
|
||||
clangdTextCaps.setCompletion(ClangdCompletionCapabilities(*completionCaps));
|
||||
caps.setTextDocument(clangdTextCaps);
|
||||
}
|
||||
caps.clearExperimental();
|
||||
@@ -800,6 +883,34 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir)
|
||||
const DocumentUri &uri) {
|
||||
gatherHelpItemForTooltip(response, uri);
|
||||
});
|
||||
setCompletionItemsTransformer([](const Utils::FilePath &filePath, const QString &content,
|
||||
int pos, const QList<CompletionItem> &items) {
|
||||
qCDebug(clangdLog) << "received" << items.count() << "completions";
|
||||
|
||||
// If there are signals among the candidates, we employ the built-in code model to find out
|
||||
// whether the cursor was on the second argument of a (dis)connect() call.
|
||||
// If so, we offer only signals, as nothing else makes sense in that context.
|
||||
static const auto criterion = [](const CompletionItem &ci) {
|
||||
const Utils::optional<MarkupOrString> doc = ci.documentation();
|
||||
if (!doc)
|
||||
return false;
|
||||
QString docText;
|
||||
if (Utils::holds_alternative<QString>(*doc))
|
||||
docText = Utils::get<QString>(*doc);
|
||||
else if (Utils::holds_alternative<MarkupContent>(*doc))
|
||||
docText = Utils::get<MarkupContent>(*doc).content();
|
||||
return docText.contains("Annotation: qt_signal");
|
||||
};
|
||||
if (pos != -1 && Utils::anyOf(items, criterion) && CppTools::CppModelManager::instance()
|
||||
->positionRequiresSignal(filePath.toString(), content.toUtf8(), pos)) {
|
||||
return Utils::filtered(items, criterion);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
setCompletionApplyHelper([this](const CompletionItem &item,
|
||||
TextEditor::TextDocumentManipulatorInterface &manipulator, QChar typedChar) {
|
||||
d->applyCompletionItem(item, manipulator, typedChar);
|
||||
});
|
||||
|
||||
connect(this, &Client::workDone, this,
|
||||
[this, p = QPointer(project)](const ProgressToken &token) {
|
||||
@@ -900,8 +1011,6 @@ void ClangdClient::findUsages(TextEditor::TextDocument *document, const QTextCur
|
||||
sendContent(symReq);
|
||||
}
|
||||
|
||||
void ClangdClient::enableTesting() { d->isTesting = true; }
|
||||
|
||||
void ClangdClient::handleDiagnostics(const PublishDiagnosticsParams ¶ms)
|
||||
{
|
||||
const DocumentUri &uri = params.uri();
|
||||
@@ -1003,6 +1112,55 @@ void ClangdClient::Private::findUsages(TextEditor::TextDocument *document,
|
||||
});
|
||||
}
|
||||
|
||||
void ClangdClient::enableTesting()
|
||||
{
|
||||
d->isTesting = true;
|
||||
setCompletionProposalHandler([this](TextEditor::IAssistProposal *proposal) {
|
||||
emit proposalReady(proposal);
|
||||
});
|
||||
setFunctionHintProposalHandler([this](TextEditor::IAssistProposal *proposal) {
|
||||
emit proposalReady(proposal);
|
||||
});
|
||||
d->doxygenAssistProvider.setProposalHandler([this](TextEditor::IAssistProposal *proposal) {
|
||||
QMetaObject::invokeMethod(this, [this, proposal] { emit proposalReady(proposal); },
|
||||
Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void ClangdClient::openEditorDocument(TextEditor::BaseTextEditor *editor)
|
||||
{
|
||||
if (!documentOpen(editor->textDocument()))
|
||||
openDocument(editor->textDocument());
|
||||
const auto assistRequestHandler = [self = QPointer(this)](
|
||||
TextEditor::TextEditorWidget *editorWidget, TextEditor::AssistKind kind,
|
||||
TextEditor::IAssistProvider *provider) {
|
||||
if (!self)
|
||||
return false;
|
||||
if (kind != TextEditor::Completion || provider)
|
||||
return false;
|
||||
ClangCompletionContextAnalyzer contextAnalyzer(editorWidget->document(),
|
||||
editorWidget->position(), false, {});
|
||||
contextAnalyzer.analyze();
|
||||
self->setSnippetsGroup(contextAnalyzer.addSnippets()
|
||||
? CppEditor::Constants::CPP_SNIPPETS_GROUP_ID : QString());
|
||||
switch (contextAnalyzer.completionAction()) {
|
||||
case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen:
|
||||
qCDebug(clangdLog) << "completion changed to function hint";
|
||||
editorWidget->invokeAssist(TextEditor::FunctionHint, provider);
|
||||
return true;
|
||||
case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
|
||||
self->d->doxygenAssistProvider.setParameters(contextAnalyzer.positionForProposal(),
|
||||
contextAnalyzer.completionOperator());
|
||||
editorWidget->invokeAssist(kind, &self->d->doxygenAssistProvider);
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
editor->editorWidget()->setAssistRequestHandler(assistRequestHandler);
|
||||
}
|
||||
|
||||
void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QList<Location> &locations)
|
||||
{
|
||||
const auto refData = runningFindUsages.find(key);
|
||||
@@ -2414,7 +2572,7 @@ static void semanticHighlighter(QFutureInterface<TextEditor::HighlightingResult>
|
||||
void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc,
|
||||
const QList<ExpandedSemanticToken> &tokens)
|
||||
{
|
||||
qCDebug(clangdLog()) << "handling LSP tokens" << tokens.size();
|
||||
qCDebug(clangdLog()) << "handling LSP tokens" << doc->filePath() << tokens.size();
|
||||
for (const ExpandedSemanticToken &t : tokens)
|
||||
qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type
|
||||
<< t.modifiers;
|
||||
@@ -2437,8 +2595,8 @@ void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc,
|
||||
if (isTesting) {
|
||||
const auto watcher = new QFutureWatcher<TextEditor::HighlightingResult>(q);
|
||||
connect(watcher, &QFutureWatcher<TextEditor::HighlightingResult>::finished,
|
||||
q, [this, watcher] {
|
||||
emit q->highlightingResultsReady(watcher->future().results());
|
||||
q, [this, watcher, fp = doc->filePath()] {
|
||||
emit q->highlightingResultsReady(watcher->future().results(), fp);
|
||||
watcher->deleteLater();
|
||||
});
|
||||
watcher->setFuture(runner());
|
||||
@@ -2457,6 +2615,125 @@ void ClangdClient::Private::handleSemanticTokens(TextEditor::TextDocument *doc,
|
||||
q->sendContent(astReq, SendDocUpdates::Ignore);
|
||||
}
|
||||
|
||||
void ClangdClient::Private::applyCompletionItem(const CompletionItem &item,
|
||||
TextEditor::TextDocumentManipulatorInterface &manipulator, QChar typedChar)
|
||||
{
|
||||
const auto edit = item.textEdit();
|
||||
if (!edit)
|
||||
return;
|
||||
|
||||
const auto kind = static_cast<CompletionItemKind::Kind>(
|
||||
item.kind().value_or(CompletionItemKind::Text));
|
||||
if (kind != CompletionItemKind::Function && kind != CompletionItemKind::Method
|
||||
&& kind != CompletionItemKind::Constructor) {
|
||||
applyTextEdit(manipulator, *edit, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString rawInsertText = edit->newText();
|
||||
const int firstParenOffset = rawInsertText.indexOf('(');
|
||||
const int lastParenOffset = rawInsertText.lastIndexOf(')');
|
||||
if (firstParenOffset == -1 || lastParenOffset == -1) {
|
||||
applyTextEdit(manipulator, *edit, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString detail = item.detail().value_or(QString());
|
||||
const TextEditor::CompletionSettings &completionSettings
|
||||
= TextEditor::TextEditorSettings::completionSettings();
|
||||
QString textToBeInserted = rawInsertText.left(firstParenOffset);
|
||||
QString extraCharacters;
|
||||
int cursorOffset = 0;
|
||||
bool setAutoCompleteSkipPos = false;
|
||||
const QTextDocument * const doc = manipulator.textCursorAt(
|
||||
manipulator.currentPosition()).document();
|
||||
const Range range = edit->range();
|
||||
const int rangeStart = range.start().toPositionInDocument(doc);
|
||||
const int rangeLength = range.end().toPositionInDocument(doc) - rangeStart;
|
||||
|
||||
if (completionSettings.m_autoInsertBrackets) {
|
||||
// If the user typed the opening parenthesis, they'll likely also type the closing one,
|
||||
// in which case it would be annoying if we put the cursor after the already automatically
|
||||
// inserted closing parenthesis.
|
||||
const bool skipClosingParenthesis = typedChar != '(';
|
||||
QTextCursor cursor = manipulator.textCursorAt(rangeStart);
|
||||
|
||||
bool abandonParen = false;
|
||||
if (matchPreviousWord(manipulator, cursor, "&")) {
|
||||
moveToPreviousWord(manipulator, cursor);
|
||||
moveToPreviousChar(manipulator, cursor);
|
||||
const QChar prevChar = manipulator.characterAt(cursor.position());
|
||||
cursor.setPosition(rangeStart);
|
||||
abandonParen = QString("(;,{}=").contains(prevChar);
|
||||
}
|
||||
if (!abandonParen)
|
||||
abandonParen = isAtUsingDeclaration(manipulator, rangeStart);
|
||||
if (!abandonParen && matchPreviousWord(manipulator, cursor, detail)) // function definition?
|
||||
abandonParen = true;
|
||||
if (!abandonParen) {
|
||||
if (completionSettings.m_spaceAfterFunctionName)
|
||||
extraCharacters += ' ';
|
||||
extraCharacters += '(';
|
||||
if (typedChar == '(')
|
||||
typedChar = {};
|
||||
|
||||
// If the function doesn't return anything, automatically place the semicolon,
|
||||
// unless we're doing a scope completion (then it might be function definition).
|
||||
const QChar characterAtCursor = manipulator.characterAt(manipulator.currentPosition());
|
||||
bool endWithSemicolon = typedChar == ';';
|
||||
const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar;
|
||||
if (endWithSemicolon && characterAtCursor == semicolon) {
|
||||
endWithSemicolon = false;
|
||||
typedChar = {};
|
||||
}
|
||||
|
||||
// If the function takes no arguments, automatically place the closing parenthesis
|
||||
if (firstParenOffset + 1 == lastParenOffset && skipClosingParenthesis) {
|
||||
extraCharacters += QLatin1Char(')');
|
||||
if (endWithSemicolon) {
|
||||
extraCharacters += semicolon;
|
||||
typedChar = {};
|
||||
}
|
||||
} else {
|
||||
const QChar lookAhead = manipulator.characterAt(manipulator.currentPosition() + 1);
|
||||
if (MatchingText::shouldInsertMatchingText(lookAhead)) {
|
||||
extraCharacters += ')';
|
||||
--cursorOffset;
|
||||
setAutoCompleteSkipPos = true;
|
||||
if (endWithSemicolon) {
|
||||
extraCharacters += semicolon;
|
||||
--cursorOffset;
|
||||
typedChar = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append an unhandled typed character, adjusting cursor offset when it had been adjusted before
|
||||
if (!typedChar.isNull()) {
|
||||
extraCharacters += typedChar;
|
||||
if (cursorOffset != 0)
|
||||
--cursorOffset;
|
||||
}
|
||||
|
||||
textToBeInserted += extraCharacters;
|
||||
|
||||
const bool isReplaced = manipulator.replace(rangeStart, rangeLength, textToBeInserted);
|
||||
manipulator.setCursorPosition(rangeStart + textToBeInserted.length());
|
||||
if (isReplaced) {
|
||||
if (cursorOffset)
|
||||
manipulator.setCursorPosition(manipulator.currentPosition() + cursorOffset);
|
||||
if (setAutoCompleteSkipPos)
|
||||
manipulator.setAutoCompleteSkipPosition(manipulator.currentPosition());
|
||||
}
|
||||
|
||||
if (auto additionalEdits = item.additionalTextEdits()) {
|
||||
for (const auto &edit : *additionalEdits)
|
||||
applyTextEdit(manipulator, edit);
|
||||
}
|
||||
}
|
||||
|
||||
void ClangdClient::VirtualFunctionAssistProcessor::cancel()
|
||||
{
|
||||
resetData();
|
||||
|
@@ -36,7 +36,7 @@
|
||||
namespace Core { class SearchResultItem; }
|
||||
namespace CppTools { class CppEditorWidgetInterface; }
|
||||
namespace ProjectExplorer { class Project; }
|
||||
namespace TextEditor { class TextDocument; }
|
||||
namespace TextEditor { class BaseTextEditor; }
|
||||
|
||||
namespace ClangCodeModel {
|
||||
namespace Internal {
|
||||
@@ -52,6 +52,8 @@ public:
|
||||
QVersionNumber versionNumber() const;
|
||||
CppTools::ClangdSettings::Data settingsData() const;
|
||||
|
||||
void openEditorDocument(TextEditor::BaseTextEditor *editor);
|
||||
|
||||
void openExtraFile(const Utils::FilePath &filePath, const QString &content = {});
|
||||
void closeExtraFile(const Utils::FilePath &filePath);
|
||||
|
||||
@@ -83,7 +85,9 @@ signals:
|
||||
void foundReferences(const QList<Core::SearchResultItem> &items);
|
||||
void findUsagesDone();
|
||||
void helpItemGathered(const Core::HelpItem &helpItem);
|
||||
void highlightingResultsReady(const TextEditor::HighlightingResults &results);
|
||||
void highlightingResultsReady(const TextEditor::HighlightingResults &results,
|
||||
const Utils::FilePath &file);
|
||||
void proposalReady(TextEditor::IAssistProposal *proposal);
|
||||
|
||||
private:
|
||||
void handleDiagnostics(const LanguageServerProtocol::PublishDiagnosticsParams ¶ms) override;
|
||||
|
@@ -334,8 +334,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
|
||||
continue;
|
||||
if (fallbackClient && fallbackClient->documentOpen(editor->textDocument()))
|
||||
fallbackClient->closeDocument(editor->textDocument());
|
||||
if (!client->documentOpen(editor->textDocument()))
|
||||
client->openDocument(editor->textDocument());
|
||||
client->openEditorDocument(editor);
|
||||
ClangEditorDocumentProcessor::clearTextMarks(editor->textDocument()->filePath());
|
||||
hasDocuments = true;
|
||||
}
|
||||
@@ -429,8 +428,8 @@ void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor)
|
||||
// instead. Is this feasible?
|
||||
ProjectExplorer::Project * const project
|
||||
= ProjectExplorer::SessionManager::projectForFile(document->filePath());
|
||||
if (Client * const client = clientForProject(project))
|
||||
client->openDocument(textDocument);
|
||||
if (ClangdClient * const client = clientForProject(project))
|
||||
client->openEditorDocument(qobject_cast<TextEditor::BaseTextEditor *>(editor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -44,7 +44,9 @@
|
||||
#include <projectexplorer/kitinformation.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
||||
|
||||
#include <cplusplus/SimpleLexer.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -608,5 +610,41 @@ const QStringList optionsForProject(ProjectExplorer::Project *project)
|
||||
return ClangProjectSettings::globalCommandLineOptions();
|
||||
}
|
||||
|
||||
// 7.3.3: using typename(opt) nested-name-specifier unqualified-id ;
|
||||
bool isAtUsingDeclaration(TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
int basePosition)
|
||||
{
|
||||
using namespace CPlusPlus;
|
||||
SimpleLexer lexer;
|
||||
lexer.setLanguageFeatures(LanguageFeatures::defaultFeatures());
|
||||
const QString textToLex = textUntilPreviousStatement(manipulator, basePosition);
|
||||
const Tokens tokens = lexer(textToLex);
|
||||
if (tokens.empty())
|
||||
return false;
|
||||
|
||||
// The nested-name-specifier always ends with "::", so check for this first.
|
||||
const Token lastToken = tokens[tokens.size() - 1];
|
||||
if (lastToken.kind() != T_COLON_COLON)
|
||||
return false;
|
||||
|
||||
return contains(tokens, [](const Token &token) { return token.kind() == T_USING; });
|
||||
}
|
||||
|
||||
QString textUntilPreviousStatement(TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
int startPosition)
|
||||
{
|
||||
static const QString stopCharacters(";{}#");
|
||||
|
||||
int endPosition = 0;
|
||||
for (int i = startPosition; i >= 0 ; --i) {
|
||||
if (stopCharacters.contains(manipulator.characterAt(i))) {
|
||||
endPosition = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return manipulator.textAt(endPosition, startPosition - endPosition);
|
||||
}
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Clang
|
||||
|
@@ -42,6 +42,8 @@ class ClangDiagnosticConfig;
|
||||
class CppEditorDocumentHandle;
|
||||
}
|
||||
|
||||
namespace TextEditor { class TextDocumentManipulatorInterface; }
|
||||
|
||||
namespace ClangBackEnd { class TokenInfoContainer; }
|
||||
|
||||
namespace ProjectExplorer { class Project; }
|
||||
@@ -107,7 +109,7 @@ private:
|
||||
};
|
||||
|
||||
template <class CharacterProvider>
|
||||
void moveToPreviousChar(CharacterProvider &provider, QTextCursor &cursor)
|
||||
void moveToPreviousChar(const CharacterProvider &provider, QTextCursor &cursor)
|
||||
{
|
||||
cursor.movePosition(QTextCursor::PreviousCharacter);
|
||||
while (provider.characterAt(cursor.position()).isSpace())
|
||||
@@ -123,7 +125,7 @@ void moveToPreviousWord(CharacterProvider &provider, QTextCursor &cursor)
|
||||
}
|
||||
|
||||
template <class CharacterProvider>
|
||||
bool matchPreviousWord(CharacterProvider &provider, QTextCursor cursor, QString pattern)
|
||||
bool matchPreviousWord(const CharacterProvider &provider, QTextCursor cursor, QString pattern)
|
||||
{
|
||||
cursor.movePosition(QTextCursor::PreviousWord);
|
||||
while (provider.characterAt(cursor.position()) == ':')
|
||||
@@ -151,5 +153,11 @@ bool matchPreviousWord(CharacterProvider &provider, QTextCursor cursor, QString
|
||||
return pattern.isEmpty();
|
||||
}
|
||||
|
||||
QString textUntilPreviousStatement(TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
int startPosition);
|
||||
|
||||
bool isAtUsingDeclaration(TextEditor::TextDocumentManipulatorInterface &manipulator,
|
||||
int basePosition);
|
||||
|
||||
} // namespace Internal
|
||||
} // namespace Clang
|
||||
|
@@ -105,7 +105,7 @@ public:
|
||||
TestDocument(const QByteArray &fileName, CppTools::Tests::TemporaryDir *temporaryDir = nullptr)
|
||||
{
|
||||
QTC_ASSERT(!fileName.isEmpty(), return);
|
||||
const QResource resource(qrcPath(fileName));
|
||||
const QResource resource(qrcPath("completion/" + fileName));
|
||||
QTC_ASSERT(resource.isValid(), return);
|
||||
const QByteArray contents = QByteArray(reinterpret_cast<const char*>(resource.data()),
|
||||
resource.size());
|
||||
@@ -534,7 +534,7 @@ void ClangCodeCompletionTest::testCompletePreprocessorKeywords()
|
||||
|
||||
void ClangCodeCompletionTest::testCompleteIncludeDirective()
|
||||
{
|
||||
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("exampleIncludeDir"));
|
||||
CppTools::Tests::TemporaryCopiedDir testDir(qrcPath("completion/exampleIncludeDir"));
|
||||
ProjectLessCompletionTest t("includeDirectiveCompletion.cpp",
|
||||
QString(),
|
||||
QStringList(testDir.path()));
|
||||
|
@@ -37,17 +37,22 @@
|
||||
#include <cpptools/cpptoolstestcase.h>
|
||||
#include <cpptools/semantichighlighter.h>
|
||||
#include <coreplugin/editormanager/editormanager.h>
|
||||
#include <languageclient/languageclientmanager.h>
|
||||
#include <projectexplorer/kitmanager.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <qtsupport/qtkitinformation.h>
|
||||
|
||||
#include <texteditor/codeassist/assistproposaliteminterface.h>
|
||||
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/filepath.h>
|
||||
#include <utils/textutils.h>
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QEventLoop>
|
||||
#include <QFileInfo>
|
||||
#include <QPair>
|
||||
#include <QScopedPointer>
|
||||
#include <QTimer>
|
||||
#include <QtTest>
|
||||
@@ -78,6 +83,7 @@ namespace Tests {
|
||||
|
||||
ClangdTest::~ClangdTest()
|
||||
{
|
||||
EditorManager::closeAllEditors(false);
|
||||
if (m_project)
|
||||
ProjectExplorerPlugin::unloadProject(m_project);
|
||||
delete m_projectDir;
|
||||
@@ -88,6 +94,40 @@ Utils::FilePath ClangdTest::filePath(const QString &fileName) const
|
||||
return Utils::FilePath::fromString(m_projectDir->absolutePath(fileName.toLocal8Bit()));
|
||||
}
|
||||
|
||||
void ClangdTest::waitForNewClient(bool withIndex)
|
||||
{
|
||||
// Setting up the project should result in a clangd client being created.
|
||||
// Wait until that has happened.
|
||||
m_client = nullptr;
|
||||
const auto modelManagerSupport = ClangModelManagerSupport::instance();
|
||||
m_client = modelManagerSupport->clientForProject(project());
|
||||
if (!m_client) {
|
||||
QVERIFY(waitForSignalOrTimeout(modelManagerSupport,
|
||||
&ClangModelManagerSupport::createdClient, timeOutInMs()));
|
||||
m_client = modelManagerSupport->clientForProject(m_project);
|
||||
}
|
||||
QVERIFY(m_client);
|
||||
m_client->enableTesting();
|
||||
|
||||
// Wait until the client is fully initialized, i.e. it's completed the handshake
|
||||
// with the server.
|
||||
if (!m_client->reachable())
|
||||
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::initialized, timeOutInMs()));
|
||||
QVERIFY(m_client->reachable());
|
||||
|
||||
if (m_minVersion != -1 && m_client->versionNumber() < QVersionNumber(m_minVersion))
|
||||
QSKIP("clangd is too old");
|
||||
|
||||
// Wait for index to build.
|
||||
if (withIndex) {
|
||||
if (!m_client->isFullyIndexed()) {
|
||||
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::indexingFinished,
|
||||
clangdIndexingTimeout()));
|
||||
}
|
||||
QVERIFY(m_client->isFullyIndexed());
|
||||
}
|
||||
}
|
||||
|
||||
void ClangdTest::initTestCase()
|
||||
{
|
||||
const QString clangdFromEnv = qEnvironmentVariable("QTC_CLANGD");
|
||||
@@ -115,34 +155,8 @@ void ClangdTest::initTestCase()
|
||||
m_project = openProjectResult.project();
|
||||
m_project->configureAsExampleProject(m_kit);
|
||||
|
||||
// Setting up the project should result in a clangd client being created.
|
||||
// Wait until that has happened.
|
||||
const auto modelManagerSupport = ClangModelManagerSupport::instance();
|
||||
m_client = modelManagerSupport->clientForProject(openProjectResult.project());
|
||||
if (!m_client) {
|
||||
QVERIFY(waitForSignalOrTimeout(modelManagerSupport,
|
||||
&ClangModelManagerSupport::createdClient, timeOutInMs()));
|
||||
m_client = modelManagerSupport->clientForProject(m_project);
|
||||
}
|
||||
waitForNewClient();
|
||||
QVERIFY(m_client);
|
||||
m_client->enableTesting();
|
||||
|
||||
// Wait until the client is fully initialized, i.e. it's completed the handshake
|
||||
// with the server.
|
||||
if (!m_client->reachable())
|
||||
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::initialized, timeOutInMs()));
|
||||
QVERIFY(m_client->reachable());
|
||||
|
||||
// The kind of AST support we need was introduced in LLVM 13.
|
||||
if (m_minVersion != -1 && m_client->versionNumber() < QVersionNumber(m_minVersion))
|
||||
QSKIP("clangd is too old");
|
||||
|
||||
// Wait for index to build.
|
||||
if (!m_client->isFullyIndexed()) {
|
||||
QVERIFY(waitForSignalOrTimeout(m_client, &ClangdClient::indexingFinished,
|
||||
clangdIndexingTimeout()));
|
||||
}
|
||||
QVERIFY(m_client->isFullyIndexed());
|
||||
|
||||
// Open cpp documents.
|
||||
for (const QString &sourceFileName : qAsConst(m_sourceFileNames)) {
|
||||
@@ -153,7 +167,8 @@ void ClangdTest::initTestCase()
|
||||
QVERIFY(editor);
|
||||
const auto doc = qobject_cast<TextEditor::TextDocument *>(editor->document());
|
||||
QVERIFY(doc);
|
||||
QVERIFY(m_client->documentForFilePath(sourceFilePath) == doc);
|
||||
QVERIFY2(m_client->documentForFilePath(sourceFilePath) == doc,
|
||||
qPrintable(sourceFilePath.toUserOutput()));
|
||||
m_sourceDocuments.insert(sourceFileName, doc);
|
||||
}
|
||||
}
|
||||
@@ -1328,6 +1343,586 @@ void ClangdTestHighlighting::test()
|
||||
QCOMPARE(result.kind, expectedKind);
|
||||
}
|
||||
|
||||
|
||||
class Manipulator : public TextDocumentManipulatorInterface
|
||||
{
|
||||
public:
|
||||
Manipulator()
|
||||
{
|
||||
const auto textEditor = static_cast<BaseTextEditor *>(EditorManager::currentEditor());
|
||||
QVERIFY(textEditor);
|
||||
m_doc = textEditor->textDocument()->document();
|
||||
m_cursor = textEditor->editorWidget()->textCursor();
|
||||
}
|
||||
|
||||
int currentPosition() const override { return m_cursor.position(); }
|
||||
int positionAt(TextPositionOperation) const override { return 0; }
|
||||
QChar characterAt(int position) const override { return m_doc->characterAt(position); }
|
||||
|
||||
QString textAt(int position, int length) const override
|
||||
{
|
||||
return m_doc->toPlainText().mid(position, length);
|
||||
}
|
||||
|
||||
QTextCursor textCursorAt(int position) const override
|
||||
{
|
||||
QTextCursor cursor(m_doc);
|
||||
cursor.setPosition(position);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void setCursorPosition(int position) override { m_cursor.setPosition(position); }
|
||||
void setAutoCompleteSkipPosition(int position) override { m_skipPos = position; }
|
||||
|
||||
bool replace(int position, int length, const QString &text) override
|
||||
{
|
||||
QTextCursor cursor = textCursorAt(position);
|
||||
cursor.setPosition(position + length, QTextCursor::KeepAnchor);
|
||||
cursor.insertText(text);
|
||||
return true;
|
||||
}
|
||||
|
||||
void insertCodeSnippet(int pos, const QString &text, const SnippetParser &parser) override
|
||||
{
|
||||
const auto parseResult = parser(text);
|
||||
if (const auto snippet = Utils::get_if<ParsedSnippet>(&parseResult)) {
|
||||
if (!snippet->parts.isEmpty())
|
||||
textCursorAt(pos).insertText(snippet->parts.first().text);
|
||||
}
|
||||
}
|
||||
|
||||
void paste() override {}
|
||||
void encourageApply() override {}
|
||||
void autoIndent(int, int) override {}
|
||||
|
||||
QString getLine(int line) const { return m_doc->findBlockByNumber(line - 1).text(); }
|
||||
|
||||
QPair<int, int> cursorPos() const
|
||||
{
|
||||
const int pos = currentPosition();
|
||||
QPair<int, int> lineAndColumn;
|
||||
Utils::Text::convertPosition(m_doc, pos, &lineAndColumn.first, &lineAndColumn.second);
|
||||
return lineAndColumn;
|
||||
}
|
||||
|
||||
int skipPos() const { return m_skipPos; }
|
||||
|
||||
private:
|
||||
QTextDocument *m_doc;
|
||||
QTextCursor m_cursor;
|
||||
int m_skipPos = -1;
|
||||
};
|
||||
|
||||
ClangdTestCompletion::ClangdTestCompletion()
|
||||
{
|
||||
setProjectFileName("completion.pro");
|
||||
setSourceFileNames({"completionWithProject.cpp", "constructorCompletion.cpp",
|
||||
"classAndConstructorCompletion.cpp", "dotToArrowCorrection.cpp",
|
||||
"doxygenKeywordsCompletion.cpp", "functionAddress.cpp",
|
||||
"functionCompletion.cpp", "functionCompletionFiltered2.cpp",
|
||||
"functionCompletionFiltered.cpp", "globalCompletion.cpp",
|
||||
"includeDirectiveCompletion.cpp", "mainwindow.cpp",
|
||||
"memberCompletion.cpp", "membercompletion-friend.cpp",
|
||||
"membercompletion-inside.cpp", "membercompletion-outside.cpp",
|
||||
"noDotToArrowCorrectionForFloats.cpp",
|
||||
"preprocessorKeywordsCompletion.cpp", "preprocessorKeywordsCompletion2.cpp",
|
||||
"preprocessorKeywordsCompletion3.cpp", "signalCompletion.cpp"});
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::initTestCase()
|
||||
{
|
||||
ClangdTest::initTestCase();
|
||||
client()->forceHighlightingOnEmptyDelta();
|
||||
startCollectingHighlightingInfo();
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteDoxygenKeywords()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("doxygenKeywordsCompletion.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, "brief"));
|
||||
QVERIFY(hasItem(proposal, "param"));
|
||||
QVERIFY(hasItem(proposal, "return"));
|
||||
QVERIFY(!hasSnippet(proposal, "class "));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompletePreprocessorKeywords()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("preprocessorKeywordsCompletion.cpp", proposal);
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " ifdef macro"));
|
||||
QVERIFY(!hasSnippet(proposal, "class "));
|
||||
|
||||
proposal.clear();
|
||||
getProposal("preprocessorKeywordsCompletion2.cpp", proposal);
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " endif"));
|
||||
QVERIFY(!hasSnippet(proposal, "class "));
|
||||
|
||||
proposal.clear();
|
||||
getProposal("preprocessorKeywordsCompletion3.cpp", proposal);
|
||||
QVERIFY(proposal);
|
||||
QEXPECT_FAIL("", "TODO: Fix in clangd", Continue);
|
||||
QVERIFY(hasItem(proposal, " endif"));
|
||||
QVERIFY(!hasSnippet(proposal, "class "));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteIncludeDirective()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("includeDirectiveCompletion.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " file.h>"));
|
||||
QVERIFY(hasItem(proposal, " otherFile.h>"));
|
||||
QVERIFY(hasItem(proposal, " mylib/"));
|
||||
QVERIFY(!hasSnippet(proposal, "class "));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteGlobals()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("globalCompletion.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " globalVariable", "int"));
|
||||
QVERIFY(hasItem(proposal, " GlobalClass"));
|
||||
QVERIFY(hasItem(proposal, " class")); // Keyword
|
||||
QVERIFY(hasSnippet(proposal, "class ")); // Snippet
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " globalFunction()", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(7), " globalFunction() /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(7, 20));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteMembers()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("memberCompletion.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(!hasItem(proposal, " globalVariable"));
|
||||
QVERIFY(!hasItem(proposal, " class")); // Keyword
|
||||
QVERIFY(!hasSnippet(proposal, "class ")); // Snippet
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " member", "int");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(7), " s.member /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(7, 13));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteMembersFromInside()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("membercompletion-inside.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " publicFunc()", "void"));
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " privateFunc()", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(4), " privateFunc() /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(4, 22));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteMembersFromOutside()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("membercompletion-outside.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(!hasItem(proposal, " privateFunc", "void"));
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " publicFunc()", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(13), " c.publicFunc() /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(13, 19));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteMembersFromFriend()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("membercompletion-friend.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " publicFunc()", "void"));
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " privateFunc()", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(14), " C().privateFunc() /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(14, 22));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testFunctionAddress()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("functionAddress.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " memberFunc()", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(7), " const auto p = &S::memberFunc /* COMPLETE HERE */;");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(7, 34));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testFunctionHints()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("functionCompletion.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, "f() -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(int a) -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(const QString &s) -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(char c, int optional = 3) -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(char c, int optional1 = 3, int optional2 = 3) -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(const TType<QString> *t) -> void"));
|
||||
QVERIFY(hasItem(proposal, "f(bool) -> TType<QString>"));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testFunctionHintsFiltered()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("functionCompletionFiltered.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QCOMPARE(proposal->size(), 1);
|
||||
QVERIFY(hasItem(proposal, "func(int i, <b>int j</b>) -> void"));
|
||||
|
||||
proposal.clear();
|
||||
getProposal("functionCompletionFiltered2.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QCOMPARE(proposal->size(), 2);
|
||||
QVERIFY(hasItem(proposal, "func(const S &s, <b>int j</b>) -> void"));
|
||||
QEXPECT_FAIL("", "FIXME: LanguageClient handles active parameter only in active signature", Abort);
|
||||
QVERIFY(hasItem(proposal, "func(const S &s, <b>int j</b>, int k) -> void"));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testFunctionHintConstructor()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("constructorCompletion.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(!hasItem(proposal, "globalVariable"));
|
||||
QVERIFY(!hasItem(proposal, " class"));
|
||||
QVERIFY(hasItem(proposal, "Foo(<b>int</b>)"));
|
||||
QEXPECT_FAIL("", "FIXME: LanguageClient handles active parameter only in active signature", Abort);
|
||||
QVERIFY(hasItem(proposal, "Foo(<b>int</b>, double)"));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteClassAndConstructor()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("classAndConstructorCompletion.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " Foo"));
|
||||
|
||||
const AssistProposalItemInterface * const item
|
||||
= getItem(proposal, QString::fromUtf8(" Foo(…)"), "[2 overloads]");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(7), " Foo( /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(7, 9));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteWithDotToArrowCorrection()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("dotToArrowCorrection.cpp", proposal, ".", &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
const AssistProposalItemInterface * const item = getItem(proposal, " member", "int");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(4), " bar->member /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(4, 16));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testDontCompleteWithDotToArrowCorrectionForFloats()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("noDotToArrowCorrectionForFloats.cpp", proposal, ".");
|
||||
|
||||
QVERIFY(proposal);
|
||||
for (int i = 0; i < proposal->size(); ++i) // Offer only snippets.
|
||||
QVERIFY2(hasSnippet(proposal, proposal->text(i)), qPrintable(proposal->text(i)));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteCodeInGeneratedUiFile()
|
||||
{
|
||||
ProposalModelPtr proposal;
|
||||
int cursorPos = -1;
|
||||
getProposal("mainwindow.cpp", proposal, {}, &cursorPos);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " menuBar"));
|
||||
QVERIFY(hasItem(proposal, " statusBar"));
|
||||
QVERIFY(hasItem(proposal, " centralWidget"));
|
||||
|
||||
const AssistProposalItemInterface * const item = getItem(
|
||||
proposal, " setupUi(QMainWindow *MainWindow)", "void");
|
||||
QVERIFY(item);
|
||||
Manipulator manipulator;
|
||||
item->apply(manipulator, cursorPos);
|
||||
QCOMPARE(manipulator.getLine(34), " ui->setupUi( /* COMPLETE HERE */");
|
||||
QCOMPARE(manipulator.cursorPos(), qMakePair(34, 17));
|
||||
QCOMPARE(manipulator.skipPos(), -1);
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testSignalCompletion_data()
|
||||
{
|
||||
QTest::addColumn<QString>("customCode");
|
||||
QTest::addColumn<QStringList>("expectedSuggestions");
|
||||
|
||||
QTest::addRow("positive: connect() on QObject class")
|
||||
<< QString("QObject::connect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: connect() on QObject object")
|
||||
<< QString("QObject o; o.connect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: connect() on QObject pointer")
|
||||
<< QString("QObject *o; o->connect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: connect() on QObject rvalue")
|
||||
<< QString("QObject().connect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: connect() on QObject pointer rvalue")
|
||||
<< QString("(new QObject)->connect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: disconnect() on QObject")
|
||||
<< QString("QObject::disconnect(dummy, &QObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()"};
|
||||
QTest::addRow("positive: connect() in member function of derived class")
|
||||
<< QString("}void DerivedFromQObject::alsoNotASignal() { connect(this, &DerivedFromQObject::")
|
||||
<< QStringList{"aSignal()", "anotherSignal()", "myOwnSignal()"};
|
||||
|
||||
const QStringList allQObjectFunctions{"aSignal()", "anotherSignal()", "notASignal()",
|
||||
"connect()", "disconnect()", "QObject", "~QObject()",
|
||||
"operator=(…)"};
|
||||
QTest::addRow("negative: different function name")
|
||||
<< QString("QObject::notASignal(dummy, &QObject::")
|
||||
<< allQObjectFunctions;
|
||||
QTest::addRow("negative: connect function from other class")
|
||||
<< QString("NotAQObject::connect(dummy, &QObject::")
|
||||
<< allQObjectFunctions;
|
||||
QTest::addRow("negative: first argument")
|
||||
<< QString("QObject::connect(QObject::")
|
||||
<< allQObjectFunctions;
|
||||
QTest::addRow("negative: third argument")
|
||||
<< QString("QObject::connect(dummy1, dummy2, QObject::")
|
||||
<< allQObjectFunctions;
|
||||
|
||||
QTest::addRow("negative: not a QObject")
|
||||
<< QString("QObject::connect(dummy, &NotAQObject::")
|
||||
<< QStringList{"notASignal()", "alsoNotASignal()", "connect()", "NotAQObject",
|
||||
"~NotAQObject()", "operator=(…)"};
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testSignalCompletion()
|
||||
{
|
||||
QFETCH(QString, customCode);
|
||||
QFETCH(QStringList, expectedSuggestions);
|
||||
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("signalCompletion.cpp", proposal, customCode);
|
||||
|
||||
QVERIFY(proposal);
|
||||
if (client()->versionNumber() < QVersionNumber(14)
|
||||
&& QString::fromLatin1(QTest::currentDataTag()).startsWith("positive:")) {
|
||||
QEXPECT_FAIL("", "Signal info in completions requires clangd >= 14", Abort);
|
||||
}
|
||||
QCOMPARE(proposal->size(), expectedSuggestions.size());
|
||||
for (const QString &expectedSuggestion : qAsConst(expectedSuggestions))
|
||||
QVERIFY2(hasItem(proposal, ' ' + expectedSuggestion), qPrintable(expectedSuggestion));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::testCompleteAfterProjectChange()
|
||||
{
|
||||
// No defines set, completion must come from #else branch.
|
||||
ProposalModelPtr proposal;
|
||||
getProposal("completionWithProject.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(!hasItem(proposal, " projectConfiguration1"));
|
||||
QVERIFY(!hasItem(proposal, " projectConfiguration2"));
|
||||
QVERIFY(hasItem(proposal, " noProjectConfigurationDetected"));
|
||||
|
||||
// Set define in project file, completion must come from #if branch.
|
||||
const auto proFileEditor = qobject_cast<BaseTextEditor *>(
|
||||
EditorManager::openEditor(project()->projectFilePath()));
|
||||
QVERIFY(proFileEditor);
|
||||
proFileEditor->insert("DEFINES += PROJECT_CONFIGURATION_1\n");
|
||||
QString saveError;
|
||||
QVERIFY2(proFileEditor->document()->save(&saveError), qPrintable(saveError));
|
||||
QVERIFY(waitForSignalOrTimeout(project(), &Project::anyParsingFinished, timeOutInMs()));
|
||||
QVERIFY(waitForSignalOrTimeout(LanguageClient::LanguageClientManager::instance(),
|
||||
&LanguageClient::LanguageClientManager::clientRemoved,
|
||||
timeOutInMs()));
|
||||
|
||||
// Waiting for the index will cause highlighting info collection to start too late,
|
||||
// so skip it.
|
||||
waitForNewClient(false);
|
||||
QVERIFY(client());
|
||||
|
||||
startCollectingHighlightingInfo();
|
||||
proposal.clear();
|
||||
getProposal("completionWithProject.cpp", proposal);
|
||||
|
||||
QVERIFY(proposal);
|
||||
QVERIFY(hasItem(proposal, " projectConfiguration1"));
|
||||
QVERIFY(!hasItem(proposal, " projectConfiguration2"));
|
||||
QVERIFY(!hasItem(proposal, " noProjectConfigurationDetected"));
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::startCollectingHighlightingInfo()
|
||||
{
|
||||
m_documentsWithHighlighting.clear();
|
||||
connect(client(), &ClangdClient::highlightingResultsReady, this,
|
||||
[this](const HighlightingResults &, const Utils::FilePath &file) {
|
||||
m_documentsWithHighlighting.insert(file);
|
||||
});
|
||||
}
|
||||
|
||||
void ClangdTestCompletion::getProposal(const QString &fileName,
|
||||
TextEditor::ProposalModelPtr &proposalModel,
|
||||
const QString &insertString,
|
||||
int *cursorPos)
|
||||
{
|
||||
const TextDocument * const doc = document(fileName);
|
||||
QVERIFY(doc);
|
||||
const int pos = doc->document()->toPlainText().indexOf(" /* COMPLETE HERE */");
|
||||
QVERIFY(pos != -1);
|
||||
if (cursorPos)
|
||||
*cursorPos = pos;
|
||||
int line, column;
|
||||
Utils::Text::convertPosition(doc->document(), pos, &line, &column);
|
||||
const auto editor = qobject_cast<BaseTextEditor *>(
|
||||
EditorManager::openEditorAt(doc->filePath().toString(), line, column - 1));
|
||||
QVERIFY(editor);
|
||||
QCOMPARE(EditorManager::currentEditor(), editor);
|
||||
QCOMPARE(editor->textDocument(), doc);
|
||||
|
||||
if (!insertString.isEmpty()) {
|
||||
m_documentsWithHighlighting.remove(doc->filePath());
|
||||
editor->insert(insertString);
|
||||
if (cursorPos)
|
||||
*cursorPos += insertString.length();
|
||||
}
|
||||
|
||||
// Once clangd has sent highlighting information for a file, we know it is also
|
||||
// ready for completion.
|
||||
QElapsedTimer highlightingTimer;
|
||||
highlightingTimer.start();
|
||||
while (!m_documentsWithHighlighting.contains(doc->filePath())
|
||||
&& highlightingTimer.elapsed() < timeOutInMs()) {
|
||||
waitForSignalOrTimeout(client(), &ClangdClient::highlightingResultsReady, 1000);
|
||||
};
|
||||
QVERIFY2(m_documentsWithHighlighting.contains(doc->filePath()), qPrintable(fileName));
|
||||
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
QEventLoop loop;
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
IAssistProposal *proposal = nullptr;
|
||||
connect(client(), &ClangdClient::proposalReady, &loop, [&proposal, &loop](IAssistProposal *p) {
|
||||
proposal = p;
|
||||
loop.quit();
|
||||
});
|
||||
editor->editorWidget()->invokeAssist(Completion, nullptr);
|
||||
timer.start(5000);
|
||||
loop.exec();
|
||||
QVERIFY(timer.isActive());
|
||||
QVERIFY(proposal);
|
||||
proposalModel = proposal->model();
|
||||
delete proposal;
|
||||
|
||||
// The "dot" test files are only used once.
|
||||
if (!insertString.isEmpty() && insertString != ".") {
|
||||
m_documentsWithHighlighting.remove(doc->filePath());
|
||||
editor->editorWidget()->undo();
|
||||
}
|
||||
}
|
||||
|
||||
bool ClangdTestCompletion::hasItem(TextEditor::ProposalModelPtr model, const QString &text,
|
||||
const QString &detail)
|
||||
{
|
||||
for (int i = 0; i < model->size(); ++i) {
|
||||
if (model->text(i) == text) {
|
||||
if (detail.isEmpty())
|
||||
return true;
|
||||
const auto genericModel = model.dynamicCast<GenericProposalModel>();
|
||||
return genericModel && genericModel->detail(i) == detail;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClangdTestCompletion::hasSnippet(TextEditor::ProposalModelPtr model, const QString &text)
|
||||
{
|
||||
const auto genericModel = model.dynamicCast<GenericProposalModel>();
|
||||
if (!genericModel)
|
||||
return false;
|
||||
for (int i = 0, size = genericModel->size(); i < size; ++i) {
|
||||
const TextEditor::AssistProposalItemInterface * const item = genericModel->proposalItem(i);
|
||||
if (item->text() == text)
|
||||
return item->isSnippet();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AssistProposalItemInterface *ClangdTestCompletion::getItem(
|
||||
TextEditor::ProposalModelPtr model, const QString &text, const QString &detail)
|
||||
{
|
||||
const auto genericModel = model.dynamicCast<GenericProposalModel>();
|
||||
if (!genericModel)
|
||||
return nullptr;
|
||||
for (int i = 0; i < genericModel->size(); ++i) {
|
||||
if (genericModel->text(i) == text &&
|
||||
(detail.isEmpty() || detail == genericModel->detail(i))) {
|
||||
return genericModel->proposalItem(i);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Tests
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
@@ -27,11 +27,13 @@
|
||||
|
||||
#include <cpptools/cpptoolstestcase.h>
|
||||
#include <coreplugin/find/searchresultitem.h>
|
||||
#include <texteditor/codeassist/genericproposal.h>
|
||||
#include <texteditor/semantichighlighter.h>
|
||||
#include <utils/fileutils.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
|
||||
namespace ProjectExplorer {
|
||||
@@ -63,6 +65,8 @@ protected:
|
||||
TextEditor::TextDocument *document(const QString &fileName) const {
|
||||
return m_sourceDocuments.value(fileName);
|
||||
}
|
||||
ProjectExplorer::Project *project() const { return m_project; }
|
||||
void waitForNewClient(bool withIndex = true);
|
||||
|
||||
protected slots:
|
||||
virtual void initTestCase();
|
||||
@@ -142,6 +146,54 @@ private:
|
||||
TextEditor::HighlightingResults m_results;
|
||||
};
|
||||
|
||||
class ClangdTestCompletion : public ClangdTest
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ClangdTestCompletion();
|
||||
|
||||
private slots:
|
||||
void initTestCase() override;
|
||||
|
||||
void testCompleteDoxygenKeywords();
|
||||
void testCompletePreprocessorKeywords();
|
||||
void testCompleteIncludeDirective();
|
||||
|
||||
void testCompleteGlobals();
|
||||
void testCompleteMembers();
|
||||
void testCompleteMembersFromInside();
|
||||
void testCompleteMembersFromOutside();
|
||||
void testCompleteMembersFromFriend();
|
||||
void testFunctionAddress();
|
||||
void testFunctionHints();
|
||||
void testFunctionHintsFiltered();
|
||||
void testFunctionHintConstructor();
|
||||
void testCompleteClassAndConstructor();
|
||||
|
||||
void testCompleteWithDotToArrowCorrection();
|
||||
void testDontCompleteWithDotToArrowCorrectionForFloats();
|
||||
|
||||
void testCompleteCodeInGeneratedUiFile();
|
||||
|
||||
void testSignalCompletion_data();
|
||||
void testSignalCompletion();
|
||||
|
||||
void testCompleteAfterProjectChange();
|
||||
|
||||
private:
|
||||
void startCollectingHighlightingInfo();
|
||||
void getProposal(const QString &fileName, TextEditor::ProposalModelPtr &proposalModel,
|
||||
const QString &insertString = {}, int *cursorPos = nullptr);
|
||||
static bool hasItem(TextEditor::ProposalModelPtr model, const QString &text,
|
||||
const QString &detail = {});
|
||||
static bool hasSnippet(TextEditor::ProposalModelPtr model, const QString &text);
|
||||
static int itemsWithText(TextEditor::ProposalModelPtr model, const QString &text);
|
||||
static TextEditor::AssistProposalItemInterface *getItem(
|
||||
TextEditor::ProposalModelPtr model, const QString &text, const QString &detail = {});
|
||||
|
||||
QSet<Utils::FilePath> m_documentsWithHighlighting;
|
||||
};
|
||||
|
||||
} // namespace Tests
|
||||
} // namespace Internal
|
||||
} // namespace ClangCodeModel
|
||||
|
@@ -5,30 +5,11 @@
|
||||
<file>qt-widgets-app/mainwindow.h</file>
|
||||
<file>qt-widgets-app/mainwindow.ui</file>
|
||||
<file>qt-widgets-app/qt-widgets-app.pro</file>
|
||||
<file>exampleIncludeDir/file.h</file>
|
||||
<file>exampleIncludeDir/mylib/mylib.h</file>
|
||||
<file>exampleIncludeDir/otherFile.h</file>
|
||||
<file>completionWithProject.cpp</file>
|
||||
<file>constructorCompletion.cpp</file>
|
||||
<file>doxygenKeywordsCompletion.cpp</file>
|
||||
<file>functionCompletion.cpp</file>
|
||||
<file>globalCompletion.cpp</file>
|
||||
<file>includeDirectiveCompletion.cpp</file>
|
||||
<file>memberCompletion.cpp</file>
|
||||
<file>myheader.h</file>
|
||||
<file>mysource.cpp</file>
|
||||
<file>objc_messages_1.mm</file>
|
||||
<file>objc_messages_2.mm</file>
|
||||
<file>objc_messages_3.mm</file>
|
||||
<file>preprocessorKeywordsCompletion.cpp</file>
|
||||
<file>dotToArrowCorrection.cpp</file>
|
||||
<file>noDotToArrowCorrectionForFloats.cpp</file>
|
||||
<file>classAndConstructorCompletion.cpp</file>
|
||||
<file>membercompletion-outside.cpp</file>
|
||||
<file>membercompletion-inside.cpp</file>
|
||||
<file>membercompletion-friend.cpp</file>
|
||||
<file>functionCompletionFiltered.cpp</file>
|
||||
<file>functionCompletionFiltered2.cpp</file>
|
||||
<file>find-usages/defs.h</file>
|
||||
<file>find-usages/main.cpp</file>
|
||||
<file>find-usages/find-usages.pro</file>
|
||||
@@ -39,10 +20,38 @@
|
||||
<file>follow-symbol/main.cpp</file>
|
||||
<file>local-references/local-references.pro</file>
|
||||
<file>local-references/references.cpp</file>
|
||||
<file>tooltips/subdir/tooltipinfo.h</file>
|
||||
<file>tooltips/tooltips.cpp</file>
|
||||
<file>tooltips/tooltips.pro</file>
|
||||
<file>highlighting/highlighting.cpp</file>
|
||||
<file>highlighting/highlighting.pro</file>
|
||||
<file>tooltips/subdir/tooltipinfo.h</file>
|
||||
<file>completion/completion.pro</file>
|
||||
<file>completion/classAndConstructorCompletion.cpp</file>
|
||||
<file>completion/completionWithProject.cpp</file>
|
||||
<file>completion/constructorCompletion.cpp</file>
|
||||
<file>completion/doxygenKeywordsCompletion.cpp</file>
|
||||
<file>completion/functionCompletion.cpp</file>
|
||||
<file>completion/functionCompletionFiltered.cpp</file>
|
||||
<file>completion/functionCompletionFiltered2.cpp</file>
|
||||
<file>completion/globalCompletion.cpp</file>
|
||||
<file>completion/includeDirectiveCompletion.cpp</file>
|
||||
<file>completion/membercompletion-friend.cpp</file>
|
||||
<file>completion/membercompletion-inside.cpp</file>
|
||||
<file>completion/membercompletion-outside.cpp</file>
|
||||
<file>completion/memberCompletion.cpp</file>
|
||||
<file>completion/preprocessorKeywordsCompletion.cpp</file>
|
||||
<file>completion/exampleIncludeDir/file.h</file>
|
||||
<file>completion/exampleIncludeDir/otherFile.h</file>
|
||||
<file>completion/exampleIncludeDir/mylib/mylib.h</file>
|
||||
<file>completion/dotToArrowCorrection.cpp</file>
|
||||
<file>completion/noDotToArrowCorrectionForFloats.cpp</file>
|
||||
<file>completion/main.cpp</file>
|
||||
<file>completion/mainwindow.cpp</file>
|
||||
<file>completion/mainwindow.h</file>
|
||||
<file>completion/mainwindow.ui</file>
|
||||
<file>completion/signalCompletion.cpp</file>
|
||||
<file>completion/functionAddress.cpp</file>
|
||||
<file>completion/preprocessorKeywordsCompletion2.cpp</file>
|
||||
<file>completion/preprocessorKeywordsCompletion3.cpp</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@@ -0,0 +1,30 @@
|
||||
QT += widgets
|
||||
|
||||
INCLUDEPATH += $$PWD/exampleIncludeDir
|
||||
|
||||
SOURCES = \
|
||||
classAndConstructorCompletion.cpp \
|
||||
completionWithProject.cpp \
|
||||
constructorCompletion.cpp \
|
||||
dotToArrowCorrection.cpp \
|
||||
doxygenKeywordsCompletion.cpp \
|
||||
functionAddress.cpp \
|
||||
functionCompletion.cpp \
|
||||
functionCompletionFiltered2.cpp \
|
||||
functionCompletionFiltered.cpp \
|
||||
globalCompletion.cpp \
|
||||
includeDirectiveCompletion.cpp \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
memberCompletion.cpp \
|
||||
membercompletion-friend.cpp \
|
||||
membercompletion-inside.cpp \
|
||||
membercompletion-outside.cpp \
|
||||
noDotToArrowCorrectionForFloats.cpp \
|
||||
preprocessorKeywordsCompletion.cpp \
|
||||
preprocessorKeywordsCompletion2.cpp \
|
||||
preprocessorKeywordsCompletion3.cpp \
|
||||
signalCompletion.cpp
|
||||
|
||||
HEADERS = mainwindow.h
|
||||
FORMS = mainwindow.ui
|
@@ -0,0 +1,8 @@
|
||||
struct S {
|
||||
void memberFunc();
|
||||
};
|
||||
|
||||
void func()
|
||||
{
|
||||
const auto p = &S::mem /* COMPLETE HERE */;
|
||||
}
|
36
src/plugins/clangcodemodel/test/data/completion/main.cpp
Normal file
36
src/plugins/clangcodemodel/test/data/completion/main.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui-> /* COMPLETE HERE */
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
44
src/plugins/clangcodemodel/test/data/completion/mainwindow.h
Normal file
44
src/plugins/clangcodemodel/test/data/completion/mainwindow.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://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 https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
Ui::MainWindow *ui;
|
||||
};
|
@@ -0,0 +1,20 @@
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenuBar" name="menuBar" />
|
||||
<widget class="QToolBar" name="mainToolBar" />
|
||||
<widget class="QWidget" name="centralWidget" />
|
||||
<widget class="QStatusBar" name="statusBar" />
|
||||
</widget>
|
||||
<layoutDefault spacing="6" margin="11" />
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@@ -0,0 +1,3 @@
|
||||
#if 1
|
||||
int x;
|
||||
#en /* COMPLETE HERE */
|
@@ -0,0 +1,3 @@
|
||||
#if 0
|
||||
int x;
|
||||
#en /* COMPLETE HERE */
|
@@ -0,0 +1,23 @@
|
||||
class QObject {
|
||||
public:
|
||||
void aSignal() __attribute__((annotate("qt_signal")));
|
||||
void anotherSignal() __attribute__((annotate("qt_signal")));
|
||||
void notASignal();
|
||||
static void connect();
|
||||
static void disconnect();
|
||||
};
|
||||
class DerivedFromQObject : public QObject {
|
||||
public:
|
||||
void myOwnSignal() __attribute__((annotate("qt_signal")));
|
||||
void alsoNotASignal();
|
||||
};
|
||||
class NotAQObject {
|
||||
public:
|
||||
void notASignal();
|
||||
void alsoNotASignal();
|
||||
static void connect();
|
||||
};
|
||||
|
||||
void someFunction()
|
||||
{
|
||||
/* COMPLETE HERE */
|
@@ -137,14 +137,28 @@ bool CppEditorDocument::isObjCEnabled() const
|
||||
return m_isObjCEnabled;
|
||||
}
|
||||
|
||||
CppTools::CppCompletionAssistProvider *CppEditorDocument::completionAssistProvider() const
|
||||
void CppEditorDocument::setCompletionAssistProvider(TextEditor::CompletionAssistProvider *provider)
|
||||
{
|
||||
return m_completionAssistProvider;
|
||||
TextDocument::setCompletionAssistProvider(provider);
|
||||
m_completionAssistProvider = nullptr;
|
||||
}
|
||||
|
||||
CppTools::CppCompletionAssistProvider *CppEditorDocument::functionHintAssistProvider() const
|
||||
void CppEditorDocument::setFunctionHintAssistProvider(TextEditor::CompletionAssistProvider *provider)
|
||||
{
|
||||
return m_functionHintAssistProvider;
|
||||
TextDocument::setFunctionHintAssistProvider(provider);
|
||||
m_functionHintAssistProvider = nullptr;
|
||||
}
|
||||
|
||||
CompletionAssistProvider *CppEditorDocument::completionAssistProvider() const
|
||||
{
|
||||
return m_completionAssistProvider
|
||||
? m_completionAssistProvider : TextDocument::completionAssistProvider();
|
||||
}
|
||||
|
||||
CompletionAssistProvider *CppEditorDocument::functionHintAssistProvider() const
|
||||
{
|
||||
return m_functionHintAssistProvider
|
||||
? m_functionHintAssistProvider : TextDocument::functionHintAssistProvider();
|
||||
}
|
||||
|
||||
TextEditor::IAssistProvider *CppEditorDocument::quickFixAssistProvider() const
|
||||
|
@@ -52,8 +52,10 @@ public:
|
||||
explicit CppEditorDocument();
|
||||
|
||||
bool isObjCEnabled() const;
|
||||
CppTools::CppCompletionAssistProvider *completionAssistProvider() const override;
|
||||
CppTools::CppCompletionAssistProvider *functionHintAssistProvider() const override;
|
||||
void setCompletionAssistProvider(TextEditor::CompletionAssistProvider *provider) override;
|
||||
void setFunctionHintAssistProvider(TextEditor::CompletionAssistProvider *provider) override;
|
||||
TextEditor::CompletionAssistProvider *completionAssistProvider() const override;
|
||||
TextEditor::CompletionAssistProvider *functionHintAssistProvider() const override;
|
||||
TextEditor::IAssistProvider *quickFixAssistProvider() const override;
|
||||
|
||||
void recalculateSemanticInfoDetached();
|
||||
|
@@ -1007,8 +1007,8 @@ AssistInterface *CppEditorWidget::createAssistInterface(AssistKind kind, AssistR
|
||||
{
|
||||
if (kind == Completion || kind == FunctionHint) {
|
||||
CppCompletionAssistProvider * const cap = kind == Completion
|
||||
? cppEditorDocument()->completionAssistProvider()
|
||||
: cppEditorDocument()->functionHintAssistProvider();
|
||||
? qobject_cast<CppCompletionAssistProvider *>(cppEditorDocument()->completionAssistProvider())
|
||||
: qobject_cast<CppCompletionAssistProvider *>(cppEditorDocument()->functionHintAssistProvider());
|
||||
if (cap) {
|
||||
LanguageFeatures features = LanguageFeatures::defaultFeatures();
|
||||
if (Document::Ptr doc = d->m_lastSemanticInfo.doc)
|
||||
@@ -1019,6 +1019,8 @@ AssistInterface *CppEditorWidget::createAssistInterface(AssistKind kind, AssistR
|
||||
features,
|
||||
position(),
|
||||
reason);
|
||||
} else {
|
||||
return TextEditorWidget::createAssistInterface(kind, reason);
|
||||
}
|
||||
} else if (kind == QuickFix) {
|
||||
if (isSemanticInfoValid())
|
||||
|
@@ -1017,6 +1017,13 @@ void Client::setSemanticTokensHandler(const SemanticTokensHandler &handler)
|
||||
m_tokenSupport.setTokensHandler(handler);
|
||||
}
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
void Client::forceHighlightingOnEmptyDelta()
|
||||
{
|
||||
m_tokenSupport.forceHighlightingOnEmptyDelta();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Client::setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier)
|
||||
{
|
||||
m_symbolStringifier = stringifier;
|
||||
@@ -1027,6 +1034,46 @@ SymbolStringifier Client::symbolStringifier() const
|
||||
return m_symbolStringifier;
|
||||
}
|
||||
|
||||
void Client::setCompletionItemsTransformer(const CompletionItemsTransformer &transformer)
|
||||
{
|
||||
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
||||
m_clientProviders.completionAssistProvider)) {
|
||||
provider->setItemsTransformer(transformer);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::setCompletionApplyHelper(const CompletionApplyHelper &applyHelper)
|
||||
{
|
||||
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
||||
m_clientProviders.completionAssistProvider)) {
|
||||
provider->setApplyHelper(applyHelper);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::setCompletionProposalHandler(const ProposalHandler &handler)
|
||||
{
|
||||
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
||||
m_clientProviders.completionAssistProvider)) {
|
||||
provider->setProposalHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::setFunctionHintProposalHandler(const ProposalHandler &handler)
|
||||
{
|
||||
if (const auto provider = qobject_cast<FunctionHintAssistProvider *>(
|
||||
m_clientProviders.functionHintProvider)) {
|
||||
provider->setProposalHandler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::setSnippetsGroup(const QString &group)
|
||||
{
|
||||
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
|
||||
m_clientProviders.completionAssistProvider)) {
|
||||
provider->setSnippetsGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::start()
|
||||
{
|
||||
if (m_clientInterface->start())
|
||||
|
@@ -183,6 +183,11 @@ public:
|
||||
void setSemanticTokensHandler(const SemanticTokensHandler &handler);
|
||||
void setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier);
|
||||
LanguageServerProtocol::SymbolStringifier symbolStringifier() const;
|
||||
void setCompletionItemsTransformer(const CompletionItemsTransformer &transformer);
|
||||
void setCompletionApplyHelper(const CompletionApplyHelper &applyHelper);
|
||||
void setCompletionProposalHandler(const ProposalHandler &handler);
|
||||
void setFunctionHintProposalHandler(const ProposalHandler &handler);
|
||||
void setSnippetsGroup(const QString &group);
|
||||
|
||||
// logging
|
||||
void log(const QString &message) const;
|
||||
@@ -190,6 +195,10 @@ public:
|
||||
void log(const LanguageServerProtocol::ResponseError<Error> &responseError) const
|
||||
{ log(responseError.toString()); }
|
||||
|
||||
#ifdef WITH_TESTS
|
||||
void forceHighlightingOnEmptyDelta();
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void initialized(const LanguageServerProtocol::ServerCapabilities &capabilities);
|
||||
void capabilitiesChanged(const DynamicCapabilities &capabilities);
|
||||
|
@@ -36,6 +36,7 @@
|
||||
#include <texteditor/codeassist/genericproposalmodel.h>
|
||||
#include <texteditor/codeassist/iassistprocessor.h>
|
||||
#include <texteditor/snippets/snippet.h>
|
||||
#include <texteditor/snippets/snippetassistcollector.h>
|
||||
#include <texteditor/texteditorsettings.h>
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/textutils.h>
|
||||
@@ -58,7 +59,7 @@ namespace LanguageClient {
|
||||
class LanguageClientCompletionItem : public AssistProposalItemInterface
|
||||
{
|
||||
public:
|
||||
LanguageClientCompletionItem(CompletionItem item);
|
||||
LanguageClientCompletionItem(CompletionItem item, const CompletionApplyHelper &applyHelper);
|
||||
|
||||
// AssistProposalItemInterface interface
|
||||
QString text() const override;
|
||||
@@ -80,13 +81,15 @@ public:
|
||||
|
||||
private:
|
||||
CompletionItem m_item;
|
||||
const CompletionApplyHelper m_applyHelper;
|
||||
mutable QChar m_triggeredCommitCharacter;
|
||||
mutable QString m_sortText;
|
||||
mutable QString m_filterText;
|
||||
};
|
||||
|
||||
LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item)
|
||||
: m_item(std::move(item))
|
||||
LanguageClientCompletionItem::LanguageClientCompletionItem(CompletionItem item,
|
||||
const CompletionApplyHelper &applyHelper)
|
||||
: m_item(std::move(item)), m_applyHelper(applyHelper)
|
||||
{ }
|
||||
|
||||
QString LanguageClientCompletionItem::text() const
|
||||
@@ -107,10 +110,15 @@ bool LanguageClientCompletionItem::prematurelyApplies(const QChar &typedCharacte
|
||||
void LanguageClientCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
|
||||
int /*basePosition*/) const
|
||||
{
|
||||
const int pos = manipulator.currentPosition();
|
||||
if (m_applyHelper) {
|
||||
m_applyHelper(m_item, manipulator, m_triggeredCommitCharacter);
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto edit = m_item.textEdit()) {
|
||||
applyTextEdit(manipulator, *edit, isSnippet());
|
||||
} else {
|
||||
const int pos = manipulator.currentPosition();
|
||||
const QString textToInsert(m_item.insertText().value_or(text()));
|
||||
int length = 0;
|
||||
for (auto it = textToInsert.crbegin(), end = textToInsert.crend(); it != end; ++it) {
|
||||
@@ -253,16 +261,22 @@ public:
|
||||
void sort(const QString &/*prefix*/) override;
|
||||
bool supportsPrefixExpansion() const override { return false; }
|
||||
|
||||
QList<LanguageClientCompletionItem *> items() const
|
||||
{ return Utils::static_container_cast<LanguageClientCompletionItem *>(m_currentItems); }
|
||||
QList<AssistProposalItemInterface *> items() const { return m_currentItems; }
|
||||
};
|
||||
|
||||
void LanguageClientCompletionModel::sort(const QString &/*prefix*/)
|
||||
{
|
||||
std::sort(m_currentItems.begin(), m_currentItems.end(),
|
||||
[] (AssistProposalItemInterface *a, AssistProposalItemInterface *b){
|
||||
return *(dynamic_cast<LanguageClientCompletionItem *>(a)) < *(
|
||||
dynamic_cast<LanguageClientCompletionItem *>(b));
|
||||
const auto lca = dynamic_cast<LanguageClientCompletionItem *>(a);
|
||||
const auto lcb = dynamic_cast<LanguageClientCompletionItem *>(b);
|
||||
if (!lca && !lcb)
|
||||
return a->text() < b->text();
|
||||
if (lca && lcb)
|
||||
return *lca < *lcb;
|
||||
if (lca && !lcb)
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -281,8 +295,10 @@ public:
|
||||
return false;
|
||||
|
||||
return m_model->keepPerfectMatch(reason)
|
||||
|| !Utils::anyOf(m_model->items(), [this](LanguageClientCompletionItem *item){
|
||||
return item->isPerfectMatch(m_pos, m_document);
|
||||
|| !Utils::anyOf(m_model->items(), [this](AssistProposalItemInterface *item){
|
||||
if (const auto lcItem = dynamic_cast<LanguageClientCompletionItem *>(item))
|
||||
return lcItem->isPerfectMatch(m_pos, m_document);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -295,7 +311,11 @@ public:
|
||||
class LanguageClientCompletionAssistProcessor final : public IAssistProcessor
|
||||
{
|
||||
public:
|
||||
LanguageClientCompletionAssistProcessor(Client *client);
|
||||
LanguageClientCompletionAssistProcessor(Client *client,
|
||||
const CompletionItemsTransformer &itemsTransformer,
|
||||
const CompletionApplyHelper &applyHelper,
|
||||
const ProposalHandler &proposalHandler,
|
||||
const QString &snippetsGroup);
|
||||
~LanguageClientCompletionAssistProcessor() override;
|
||||
IAssistProposal *perform(const AssistInterface *interface) override;
|
||||
bool running() override;
|
||||
@@ -306,15 +326,23 @@ private:
|
||||
void handleCompletionResponse(const CompletionRequest::Response &response);
|
||||
|
||||
QPointer<QTextDocument> m_document;
|
||||
Utils::FilePath m_filePath;
|
||||
QPointer<Client> m_client;
|
||||
Utils::optional<MessageId> m_currentRequest;
|
||||
QMetaObject::Connection m_postponedUpdateConnection;
|
||||
const CompletionItemsTransformer m_itemsTransformer;
|
||||
const CompletionApplyHelper m_applyHelper;
|
||||
const ProposalHandler m_proposalHandler;
|
||||
const QString m_snippetsGroup;
|
||||
int m_pos = -1;
|
||||
int m_basePos = -1;
|
||||
};
|
||||
|
||||
LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client)
|
||||
: m_client(client)
|
||||
LanguageClientCompletionAssistProcessor::LanguageClientCompletionAssistProcessor(Client *client,
|
||||
const CompletionItemsTransformer &itemsTransformer, const CompletionApplyHelper &applyHelper,
|
||||
const ProposalHandler &proposalHandler, const QString &snippetsGroup)
|
||||
: m_client(client), m_itemsTransformer(itemsTransformer), m_applyHelper(applyHelper),
|
||||
m_proposalHandler(proposalHandler), m_snippetsGroup(snippetsGroup)
|
||||
{ }
|
||||
|
||||
LanguageClientCompletionAssistProcessor::~LanguageClientCompletionAssistProcessor()
|
||||
@@ -384,6 +412,7 @@ IAssistProposal *LanguageClientCompletionAssistProcessor::perform(const AssistIn
|
||||
m_client->addAssistProcessor(this);
|
||||
m_currentRequest = completionRequest.id();
|
||||
m_document = interface->textDocument();
|
||||
m_filePath = interface->filePath();
|
||||
qCDebug(LOGLSPCOMPLETION) << QTime::currentTime()
|
||||
<< " : request completions at " << m_pos
|
||||
<< " by " << assistReasonString(interface->reason());
|
||||
@@ -430,16 +459,27 @@ void LanguageClientCompletionAssistProcessor::handleCompletionResponse(
|
||||
} else if (Utils::holds_alternative<QList<CompletionItem>>(*result)) {
|
||||
items = Utils::get<QList<CompletionItem>>(*result);
|
||||
}
|
||||
if (m_itemsTransformer && m_document)
|
||||
items = m_itemsTransformer(m_filePath, m_document->toPlainText(), m_basePos, items);
|
||||
auto model = new LanguageClientCompletionModel();
|
||||
model->loadContent(Utils::transform(items, [](const CompletionItem &item){
|
||||
return static_cast<AssistProposalItemInterface *>(new LanguageClientCompletionItem(item));
|
||||
}));
|
||||
auto proposalItems = Utils::transform<QList<AssistProposalItemInterface *>>(items,
|
||||
[this](const CompletionItem &item) {
|
||||
return new LanguageClientCompletionItem(item, m_applyHelper);
|
||||
});
|
||||
if (!m_snippetsGroup.isEmpty()) {
|
||||
proposalItems << TextEditor::SnippetAssistCollector(
|
||||
m_snippetsGroup, QIcon(":/texteditor/images/snippet.png")).collect();
|
||||
}
|
||||
model->loadContent(proposalItems);
|
||||
LanguageClientCompletionProposal *proposal = new LanguageClientCompletionProposal(m_basePos,
|
||||
model);
|
||||
proposal->m_document = m_document;
|
||||
proposal->m_pos = m_pos;
|
||||
proposal->setFragile(true);
|
||||
proposal->setSupportsPrefix(false);
|
||||
if (m_proposalHandler)
|
||||
m_proposalHandler(proposal);
|
||||
else
|
||||
setAsyncProposalAvailable(proposal);
|
||||
m_client->removeAssistProcessor(this);
|
||||
qCDebug(LOGLSPCOMPLETION) << QTime::currentTime() << " : "
|
||||
@@ -453,7 +493,8 @@ LanguageClientCompletionAssistProvider::LanguageClientCompletionAssistProvider(C
|
||||
|
||||
IAssistProcessor *LanguageClientCompletionAssistProvider::createProcessor() const
|
||||
{
|
||||
return new LanguageClientCompletionAssistProcessor(m_client);
|
||||
return new LanguageClientCompletionAssistProcessor(m_client, m_itemsTransformer, m_applyHelper,
|
||||
m_proposalHandler, m_snippetsGroup);
|
||||
}
|
||||
|
||||
IAssistProvider::RunType LanguageClientCompletionAssistProvider::runType() const
|
||||
@@ -484,4 +525,16 @@ void LanguageClientCompletionAssistProvider::setTriggerCharacters(
|
||||
}
|
||||
}
|
||||
|
||||
void LanguageClientCompletionAssistProvider::setItemsTransformer(
|
||||
const CompletionItemsTransformer &transformer)
|
||||
{
|
||||
m_itemsTransformer = transformer;
|
||||
}
|
||||
|
||||
void LanguageClientCompletionAssistProvider::setApplyHelper(
|
||||
const CompletionApplyHelper &applyHelper)
|
||||
{
|
||||
m_applyHelper = applyHelper;
|
||||
}
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -25,14 +25,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <languageserverprotocol/completion.h>
|
||||
#include <texteditor/codeassist/completionassistprovider.h>
|
||||
|
||||
#include <utils/optional.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace TextEditor {
|
||||
class IAssistProposal;
|
||||
class TextDocumentManipulatorInterface;
|
||||
}
|
||||
|
||||
namespace LanguageClient {
|
||||
|
||||
class Client;
|
||||
|
||||
using CompletionItemsTransformer = std::function<QList<LanguageServerProtocol::CompletionItem>(
|
||||
const Utils::FilePath &, const QString &, int,
|
||||
const QList<LanguageServerProtocol::CompletionItem> &)>;
|
||||
using CompletionApplyHelper = std::function<void(
|
||||
const LanguageServerProtocol::CompletionItem &,
|
||||
TextEditor::TextDocumentManipulatorInterface &, QChar)>;
|
||||
using ProposalHandler = std::function<void(TextEditor::IAssistProposal *)>;
|
||||
|
||||
class LanguageClientCompletionAssistProvider : public TextEditor::CompletionAssistProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -49,8 +65,17 @@ public:
|
||||
|
||||
void setTriggerCharacters(const Utils::optional<QList<QString>> triggerChars);
|
||||
|
||||
void setItemsTransformer(const CompletionItemsTransformer &transformer);
|
||||
void setApplyHelper(const CompletionApplyHelper &applyHelper);
|
||||
void setProposalHandler(const ProposalHandler &handler) { m_proposalHandler = handler; }
|
||||
void setSnippetsGroup(const QString &group) { m_snippetsGroup = group; }
|
||||
|
||||
private:
|
||||
QList<QString> m_triggerChars;
|
||||
CompletionItemsTransformer m_itemsTransformer;
|
||||
CompletionApplyHelper m_applyHelper;
|
||||
ProposalHandler m_proposalHandler;
|
||||
QString m_snippetsGroup;
|
||||
int m_activationCharSequenceLength = 0;
|
||||
Client *m_client = nullptr; // not owned
|
||||
};
|
||||
|
@@ -84,7 +84,8 @@ QString FunctionHintProposalModel::text(int index) const
|
||||
class FunctionHintProcessor : public IAssistProcessor
|
||||
{
|
||||
public:
|
||||
explicit FunctionHintProcessor(Client *client) : m_client(client) {}
|
||||
explicit FunctionHintProcessor(Client *client, const ProposalHandler &proposalHandler)
|
||||
: m_client(client), m_proposalHandler(proposalHandler) {}
|
||||
IAssistProposal *perform(const AssistInterface *interface) override;
|
||||
bool running() override { return m_currentRequest.has_value(); }
|
||||
bool needsRestart() const override { return true; }
|
||||
@@ -92,8 +93,10 @@ public:
|
||||
|
||||
private:
|
||||
void handleSignatureResponse(const SignatureHelpRequest::Response &response);
|
||||
void processProposal(TextEditor::IAssistProposal *proposal);
|
||||
|
||||
QPointer<Client> m_client;
|
||||
const ProposalHandler m_proposalHandler;
|
||||
Utils::optional<MessageId> m_currentRequest;
|
||||
int m_pos = -1;
|
||||
};
|
||||
@@ -129,18 +132,26 @@ void FunctionHintProcessor::handleSignatureResponse(const SignatureHelpRequest::
|
||||
m_client->removeAssistProcessor(this);
|
||||
auto result = response.result().value_or(LanguageClientValue<SignatureHelp>());
|
||||
if (result.isNull()) {
|
||||
setAsyncProposalAvailable(nullptr);
|
||||
processProposal(nullptr);
|
||||
return;
|
||||
}
|
||||
const SignatureHelp &signatureHelp = result.value();
|
||||
if (signatureHelp.signatures().isEmpty()) {
|
||||
setAsyncProposalAvailable(nullptr);
|
||||
processProposal(nullptr);
|
||||
} else {
|
||||
FunctionHintProposalModelPtr model(new FunctionHintProposalModel(signatureHelp));
|
||||
setAsyncProposalAvailable(new FunctionHintProposal(m_pos, model));
|
||||
processProposal(new FunctionHintProposal(m_pos, model));
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionHintProcessor::processProposal(IAssistProposal *proposal)
|
||||
{
|
||||
if (m_proposalHandler)
|
||||
m_proposalHandler(proposal);
|
||||
else
|
||||
setAsyncProposalAvailable(proposal);
|
||||
}
|
||||
|
||||
FunctionHintAssistProvider::FunctionHintAssistProvider(Client *client)
|
||||
: CompletionAssistProvider(client)
|
||||
, m_client(client)
|
||||
@@ -148,7 +159,7 @@ FunctionHintAssistProvider::FunctionHintAssistProvider(Client *client)
|
||||
|
||||
TextEditor::IAssistProcessor *FunctionHintAssistProvider::createProcessor() const
|
||||
{
|
||||
return new FunctionHintProcessor(m_client);
|
||||
return new FunctionHintProcessor(m_client, m_proposalHandler);
|
||||
}
|
||||
|
||||
IAssistProvider::RunType FunctionHintAssistProvider::runType() const
|
||||
|
@@ -28,10 +28,14 @@
|
||||
#include <texteditor/codeassist/completionassistprovider.h>
|
||||
#include <utils/optional.h>
|
||||
|
||||
namespace TextEditor { class IAssistProposal; }
|
||||
|
||||
namespace LanguageClient {
|
||||
|
||||
class Client;
|
||||
|
||||
using ProposalHandler = std::function<void(TextEditor::IAssistProposal *)>;
|
||||
|
||||
class FunctionHintAssistProvider : public TextEditor::CompletionAssistProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -47,8 +51,12 @@ public:
|
||||
bool isContinuationChar(const QChar &c) const override;
|
||||
|
||||
void setTriggerCharacters(const Utils::optional<QList<QString>> &triggerChars);
|
||||
|
||||
void setProposalHandler(const ProposalHandler &handler) { m_proposalHandler = handler; }
|
||||
|
||||
private:
|
||||
QList<QString> m_triggerChars;
|
||||
ProposalHandler m_proposalHandler;
|
||||
int m_activationCharSequenceLength = 0;
|
||||
Client *m_client = nullptr; // not owned
|
||||
};
|
||||
|
@@ -218,10 +218,12 @@ void LanguageClientManager::deleteClient(Client *client)
|
||||
managerInstance->m_clients.removeAll(client);
|
||||
for (QVector<Client *> &clients : managerInstance->m_clientsForSetting)
|
||||
clients.removeAll(client);
|
||||
if (managerInstance->m_shuttingDown)
|
||||
if (managerInstance->m_shuttingDown) {
|
||||
delete client;
|
||||
else
|
||||
} else {
|
||||
client->deleteLater();
|
||||
emit instance()->clientRemoved(client);
|
||||
}
|
||||
}
|
||||
|
||||
void LanguageClientManager::shutdown()
|
||||
|
@@ -99,6 +99,7 @@ public:
|
||||
static void showInspector();
|
||||
|
||||
signals:
|
||||
void clientRemoved(Client *client);
|
||||
void shutdownFinished();
|
||||
|
||||
private:
|
||||
|
@@ -386,8 +386,11 @@ void SemanticTokenSupport::handleSemanticTokensDelta(
|
||||
m_tokens[filePath] = *tokens;
|
||||
} else if (auto tokensDelta = Utils::get_if<SemanticTokensDelta>(&result)) {
|
||||
QList<SemanticTokensEdit> edits = tokensDelta->edits();
|
||||
if (edits.isEmpty())
|
||||
if (edits.isEmpty()) {
|
||||
if (m_highlightOnEmptyDelta)
|
||||
highlight(filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::sort(edits, &SemanticTokensEdit::start);
|
||||
|
||||
|
@@ -81,6 +81,7 @@ public:
|
||||
// void setAdditionalTokenModifierStyles(const QHash<int, TextEditor::TextStyle> &modifierStyles);
|
||||
|
||||
void setTokensHandler(const SemanticTokensHandler &handler) { m_tokensHandler = handler; }
|
||||
void forceHighlightingOnEmptyDelta() { m_highlightOnEmptyDelta = true; }
|
||||
|
||||
private:
|
||||
LanguageServerProtocol::SemanticRequestTypes supportedSemanticRequests(
|
||||
@@ -106,6 +107,7 @@ private:
|
||||
SemanticTokensHandler m_tokensHandler;
|
||||
QStringList m_tokenTypeStrings;
|
||||
QStringList m_tokenModifierStrings;
|
||||
bool m_highlightOnEmptyDelta = false;
|
||||
};
|
||||
|
||||
} // namespace LanguageClient
|
||||
|
@@ -142,9 +142,9 @@ public:
|
||||
|
||||
virtual void triggerPendingUpdates();
|
||||
|
||||
void setCompletionAssistProvider(CompletionAssistProvider *provider);
|
||||
virtual void setCompletionAssistProvider(CompletionAssistProvider *provider);
|
||||
virtual CompletionAssistProvider *completionAssistProvider() const;
|
||||
void setFunctionHintAssistProvider(CompletionAssistProvider *provider);
|
||||
virtual void setFunctionHintAssistProvider(CompletionAssistProvider *provider);
|
||||
virtual CompletionAssistProvider *functionHintAssistProvider() const;
|
||||
void setQuickFixAssistProvider(IAssistProvider *provider) const;
|
||||
virtual IAssistProvider *quickFixAssistProvider() const;
|
||||
|
@@ -781,6 +781,7 @@ public:
|
||||
TextMark* m_dragMark = nullptr;
|
||||
|
||||
QScopedPointer<ClipboardAssistProvider> m_clipboardAssistProvider;
|
||||
TextEditorWidget::AssistRequestHandler m_assistRequestHandler;
|
||||
|
||||
QScopedPointer<AutoCompleter> m_autoCompleter;
|
||||
CommentDefinition m_commentDefinition;
|
||||
@@ -3531,6 +3532,11 @@ void TextEditorWidget::showTextMarksToolTip(const QPoint &pos,
|
||||
d->showTextMarksToolTip(pos, marks, mainTextMark);
|
||||
}
|
||||
|
||||
void TextEditorWidget::setAssistRequestHandler(const AssistRequestHandler &handler)
|
||||
{
|
||||
d->m_assistRequestHandler = handler;
|
||||
}
|
||||
|
||||
void TextEditorWidgetPrivate::processTooltipRequest(const QTextCursor &c)
|
||||
{
|
||||
const QPoint toolTipPoint = q->toolTipPosition(c);
|
||||
@@ -8526,6 +8532,9 @@ QTextBlock TextEditorWidget::blockForVerticalOffset(int offset) const
|
||||
|
||||
void TextEditorWidget::invokeAssist(AssistKind kind, IAssistProvider *provider)
|
||||
{
|
||||
if (d->m_assistRequestHandler && d->m_assistRequestHandler(this, kind, provider))
|
||||
return;
|
||||
|
||||
if (kind == QuickFix && d->m_snippetOverlay->isVisible())
|
||||
d->m_snippetOverlay->accept();
|
||||
|
||||
|
@@ -288,6 +288,10 @@ public:
|
||||
const TextMarks &marks,
|
||||
const TextMark *mainTextMark = nullptr) const;
|
||||
|
||||
using AssistRequestHandler = std::function<bool(TextEditorWidget *editorWidget,
|
||||
AssistKind assistKind, IAssistProvider *provider)>;
|
||||
void setAssistRequestHandler(const AssistRequestHandler &handler);
|
||||
|
||||
void invokeAssist(AssistKind assistKind, IAssistProvider *provider = nullptr);
|
||||
|
||||
virtual TextEditor::AssistInterface *createAssistInterface(AssistKind assistKind,
|
||||
|
@@ -147,7 +147,7 @@ TEST(ActivationSequenceContextProcessor, TemplateFunctionLeftParen)
|
||||
TEST(ActivationSequenceContextProcessor, TemplateFunctionSecondParameter)
|
||||
{
|
||||
ClangCompletionAssistInterface interface("foo<X>(", 7);
|
||||
int startOfname = ContextProcessor::findStartOfName(&interface,
|
||||
int startOfname = ContextProcessor::findStartOfName(interface.textDocument(),
|
||||
6,
|
||||
ContextProcessor::NameCategory::Function);
|
||||
|
||||
|
Reference in New Issue
Block a user