summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/clangcodemodel/CMakeLists.txt1
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.qbs2
-rw-r--r--src/plugins/clangcodemodel/clangdclient.cpp594
-rw-r--r--src/plugins/clangcodemodel/clangdclient.h3
-rw-r--r--src/plugins/clangcodemodel/clangdcompletion.cpp643
-rw-r--r--src/plugins/clangcodemodel/clangdcompletion.h62
6 files changed, 709 insertions, 596 deletions
diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt
index 435299a20d..ae8558b23e 100644
--- a/src/plugins/clangcodemodel/CMakeLists.txt
+++ b/src/plugins/clangcodemodel/CMakeLists.txt
@@ -17,6 +17,7 @@ add_qtc_plugin(ClangCodeModel
clangconstants.h
clangdast.cpp clangdast.h
clangdclient.cpp clangdclient.h
+ clangdcompletion.cpp clangdcompletion.h
clangdfollowsymbol.cpp clangdfollowsymbol.h
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
clangdquickfixes.cpp clangdquickfixes.h
diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs
index ae4ebeb487..8551f63777 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.qbs
+++ b/src/plugins/clangcodemodel/clangcodemodel.qbs
@@ -34,6 +34,8 @@ QtcPlugin {
"clangdast.h",
"clangdclient.cpp",
"clangdclient.h",
+ "clangdcompletion.cpp",
+ "clangdcompletion.h",
"clangdfollowsymbol.cpp",
"clangdfollowsymbol.h",
"clangdiagnostictooltipwidget.cpp",
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp
index e6681a49e3..27d3f57b1b 100644
--- a/src/plugins/clangcodemodel/clangdclient.cpp
+++ b/src/plugins/clangcodemodel/clangdclient.cpp
@@ -25,14 +25,13 @@
#include "clangdclient.h"
-#include "clangcompletioncontextanalyzer.h"
#include "clangconstants.h"
#include "clangdast.h"
+#include "clangdcompletion.h"
#include "clangdfollowsymbol.h"
#include "clangdlocatorfilters.h"
#include "clangdquickfixes.h"
#include "clangdswitchdecldef.h"
-#include "clangpreprocessorassistproposalitem.h"
#include "clangtextmark.h"
#include "clangutils.h"
#include "clangdsemantichighlighting.h"
@@ -45,12 +44,7 @@
#include <cplusplus/ASTPath.h>
#include <cplusplus/FindUsages.h>
#include <cplusplus/Icons.h>
-#include <cplusplus/MatchingText.h>
-#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppcodemodelsettings.h>
-#include <cppeditor/cppcompletionassistprocessor.h>
-#include <cppeditor/cppcompletionassistprovider.h>
-#include <cppeditor/cppdoxygen.h>
#include <cppeditor/cppeditorwidget.h>
#include <cppeditor/cppfindreferences.h>
#include <cppeditor/cppmodelmanager.h>
@@ -61,8 +55,6 @@
#include <cppeditor/semantichighlighter.h>
#include <cppeditor/cppsemanticinfo.h>
#include <languageclient/diagnosticmanager.h>
-#include <languageclient/languageclientcompletionassist.h>
-#include <languageclient/languageclientfunctionhint.h>
#include <languageclient/languageclienthoverhandler.h>
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
@@ -79,7 +71,6 @@
#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/fileutils.h>
@@ -119,8 +110,6 @@ namespace Internal {
Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg);
static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", QtWarningMsg);
-static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion",
- QtWarningMsg);
static QString indexingToken() { return "backgroundIndexProgress"; }
static Usage::Type getUsageType(const ClangdAstPath &path)
@@ -350,178 +339,6 @@ public:
{ insert("publishDiagnostics", caps); }
};
-
-enum class CustomAssistMode { Doxygen, Preprocessor, IncludePath };
-class CustomAssistProcessor : public IAssistProcessor
-{
-public:
- CustomAssistProcessor(ClangdClient *client, int position, int endPos,
- unsigned completionOperator, CustomAssistMode mode)
- : m_client(client)
- , m_position(position)
- , m_endPos(endPos)
- , m_completionOperator(completionOperator)
- , m_mode(mode)
- {}
-
-private:
- IAssistProposal *perform(const AssistInterface *interface) override
- {
- QList<AssistProposalItemInterface *> completions;
- switch (m_mode) {
- case CustomAssistMode::Doxygen:
- for (int i = 1; i < CppEditor::T_DOXY_LAST_TAG; ++i) {
- completions << createItem(QLatin1String(CppEditor::doxygenTagSpell(i)),
- CPlusPlus::Icons::keywordIcon());
- }
- break;
- case CustomAssistMode::Preprocessor: {
- static QIcon macroIcon = Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Macro);
- for (const QString &completion
- : CppEditor::CppCompletionAssistProcessor::preprocessorCompletions()) {
- completions << createItem(completion, macroIcon);
- }
- if (CppEditor::ProjectFile::isObjC(interface->filePath().toString()))
- completions << createItem("import", macroIcon);
- break;
- }
- case ClangCodeModel::Internal::CustomAssistMode::IncludePath: {
- HeaderPaths headerPaths;
- const CppEditor::ProjectPart::ConstPtr projectPart
- = projectPartForFile(interface->filePath().toString());
- if (projectPart)
- headerPaths = projectPart->headerPaths;
- completions = completeInclude(m_endPos, m_completionOperator, interface, headerPaths);
- break;
- }
- }
- GenericProposalModelPtr model(new GenericProposalModel);
- model->loadContent(completions);
- const auto proposal = new GenericProposal(m_position, model);
- if (m_client->testingEnabled()) {
- emit m_client->proposalReady(proposal);
- return nullptr;
- }
- return proposal;
- }
-
- AssistProposalItemInterface *createItem(const QString &text, const QIcon &icon) const
- {
- const auto item = new ClangPreprocessorAssistProposalItem;
- item->setText(text);
- item->setIcon(icon);
- item->setCompletionOperator(m_completionOperator);
- return item;
- }
-
- /**
- * @brief Creates completion proposals for #include and given cursor
- * @param position - cursor placed after opening bracked or quote
- * @param completionOperator - the type of token
- * @param interface - relevant document data
- * @param headerPaths - the include paths
- * @return the list of completion items
- */
- static QList<AssistProposalItemInterface *> completeInclude(
- int position, unsigned completionOperator, const TextEditor::AssistInterface *interface,
- const ProjectExplorer::HeaderPaths &headerPaths)
- {
- QTextCursor cursor(interface->textDocument());
- cursor.setPosition(position);
- QString directoryPrefix;
- if (completionOperator == T_SLASH) {
- QTextCursor c = cursor;
- c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
- QString sel = c.selectedText();
- int startCharPos = sel.indexOf(QLatin1Char('"'));
- if (startCharPos == -1) {
- startCharPos = sel.indexOf(QLatin1Char('<'));
- completionOperator = T_ANGLE_STRING_LITERAL;
- } else {
- completionOperator = T_STRING_LITERAL;
- }
- if (startCharPos != -1)
- directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
- }
-
- // Make completion for all relevant includes
- ProjectExplorer::HeaderPaths allHeaderPaths = headerPaths;
- const auto currentFilePath = ProjectExplorer::HeaderPath::makeUser(
- interface->filePath().toFileInfo().path());
- if (!allHeaderPaths.contains(currentFilePath))
- allHeaderPaths.append(currentFilePath);
-
- const ::Utils::MimeType mimeType = ::Utils::mimeTypeForName("text/x-c++hdr");
- const QStringList suffixes = mimeType.suffixes();
-
- QList<AssistProposalItemInterface *> completions;
- for (const ProjectExplorer::HeaderPath &headerPath : qAsConst(allHeaderPaths)) {
- QString realPath = headerPath.path;
- if (!directoryPrefix.isEmpty()) {
- realPath += QLatin1Char('/');
- realPath += directoryPrefix;
- if (headerPath.type == ProjectExplorer::HeaderPathType::Framework)
- realPath += QLatin1String(".framework/Headers");
- }
- completions << completeIncludePath(realPath, suffixes, completionOperator);
- }
-
- QList<QPair<AssistProposalItemInterface *, QString>> completionsForSorting;
- for (AssistProposalItemInterface * const item : qAsConst(completions)) {
- QString s = item->text();
- s.replace('/', QChar(0)); // The dir separator should compare less than anything else.
- completionsForSorting << qMakePair(item, s);
- }
- Utils::sort(completionsForSorting, [](const auto &left, const auto &right) {
- return left.second < right.second;
- });
- for (int i = 0; i < completionsForSorting.count(); ++i)
- completions[i] = completionsForSorting[i].first;
-
- return completions;
- }
-
- /**
- * @brief Finds #include completion proposals using given include path
- * @param realPath - one of directories where compiler searches includes
- * @param suffixes - file suffixes for C/C++ header files
- * @return a list of matching completion items
- */
- static QList<AssistProposalItemInterface *> completeIncludePath(
- const QString &realPath, const QStringList &suffixes, unsigned completionOperator)
- {
- QList<AssistProposalItemInterface *> completions;
- QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
- //: Parent folder for proposed #include completion
- const QString hint = ClangdClient::tr("Location: %1")
- .arg(QDir::toNativeSeparators(QDir::cleanPath(realPath)));
- while (i.hasNext()) {
- const QString fileName = i.next();
- const QFileInfo fileInfo = i.fileInfo();
- const QString suffix = fileInfo.suffix();
- if (suffix.isEmpty() || suffixes.contains(suffix)) {
- QString text = fileName.mid(realPath.length() + 1);
- if (fileInfo.isDir())
- text += QLatin1Char('/');
-
- auto *item = new ClangPreprocessorAssistProposalItem;
- item->setText(text);
- item->setDetail(hint);
- item->setIcon(CPlusPlus::Icons::keywordIcon());
- item->setCompletionOperator(completionOperator);
- completions.append(item);
- }
- }
- return completions;
- }
-
- ClangdClient * const m_client;
- const int m_position;
- const int m_endPos;
- const unsigned m_completionOperator;
- const CustomAssistMode m_mode;
-};
-
static qint64 getRevision(const TextDocument *doc)
{
return doc->document()->revision();
@@ -663,118 +480,6 @@ 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.
- if (Utils::optional<CompletionItemCapbilities> completionItemCaps = completionItem()) {
- completionItemCaps->setSnippetSupport(false);
- setCompletionItem(*completionItemCaps);
- }
- }
-};
-
-class ClangdCompletionItem : public LanguageClientCompletionItem
-{
-public:
- using LanguageClientCompletionItem::LanguageClientCompletionItem;
- void apply(TextDocumentManipulatorInterface &manipulator,
- int basePosition) const override;
-
- enum class SpecialQtType { Signal, Slot, None };
- static SpecialQtType getQtType(const CompletionItem &item);
-
-private:
- QIcon icon() const override;
- QString text() const override;
-};
-
-class ClangdClient::ClangdCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor
-{
-public:
- ClangdCompletionAssistProcessor(ClangdClient *client, const QString &snippetsGroup)
- : LanguageClientCompletionAssistProcessor(client, snippetsGroup)
- , m_client(client)
- {
- m_timer.start();
- }
-
- ~ClangdCompletionAssistProcessor()
- {
- qCDebug(clangdLogTiming).noquote().nospace()
- << "ClangdCompletionAssistProcessor took: " << m_timer.elapsed() << " ms";
- }
-
-private:
- IAssistProposal *perform(const AssistInterface *interface) override
- {
- if (m_client->d->isTesting) {
- setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
- emit m_client->proposalReady(proposal);
- });
- }
- return LanguageClientCompletionAssistProcessor::perform(interface);
- }
-
- QList<AssistProposalItemInterface *> generateCompletionItems(
- const QList<LanguageServerProtocol::CompletionItem> &items) const override;
-
- ClangdClient * const m_client;
- QElapsedTimer m_timer;
-};
-
-QList<AssistProposalItemInterface *>
-ClangdClient::ClangdCompletionAssistProcessor::generateCompletionItems(
- const QList<LanguageServerProtocol::CompletionItem> &items) const
-{
- qCDebug(clangdLog) << "received" << items.count() << "completions";
-
- auto itemGenerator = [](const QList<LanguageServerProtocol::CompletionItem> &items) {
- return Utils::transform<QList<AssistProposalItemInterface *>>(
- items, [](const LanguageServerProtocol::CompletionItem &item) {
- return new ClangdCompletionItem(item);
- });
- };
-
- // 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) {
- return ClangdCompletionItem::getQtType(ci) == ClangdCompletionItem::SpecialQtType::Signal;
- };
- const QTextDocument *doc = document();
- const int pos = basePos();
- if (!doc || pos < 0 || !Utils::anyOf(items, criterion))
- return itemGenerator(items);
- const QString content = doc->toPlainText();
- const bool requiresSignal = CppEditor::CppModelManager::instance()
- ->getSignalSlotType(filePath().toString(), content.toUtf8(), pos)
- == CppEditor::SignalSlotType::NewStyleSignal;
- if (requiresSignal)
- return itemGenerator(Utils::filtered(items, criterion));
- return itemGenerator(items);
-}
-
-class ClangdClient::ClangdCompletionAssistProvider : public LanguageClientCompletionAssistProvider
-{
-public:
- ClangdCompletionAssistProvider(ClangdClient *client);
-
-private:
- IAssistProcessor *createProcessor(const AssistInterface *interface) const override;
-
- int activationCharSequenceLength() const override { return 3; }
- bool isActivationCharSequence(const QString &sequence) const override;
- bool isContinuationChar(const QChar &c) const override;
-
- bool isInCommentOrString(const AssistInterface *interface) const;
-
- ClangdClient * const m_client;
-};
-
static void addToCompilationDb(QJsonObject &cdb,
const CppEditor::ProjectPart &projectPart,
CppEditor::UsePrecompiledHeaders usePch,
@@ -2118,303 +1823,6 @@ QString ClangdDiagnostic::category() const
return typedValue<QString>("category");
}
-class ClangdClient::ClangdFunctionHintProcessor : public FunctionHintProcessor
-{
-public:
- ClangdFunctionHintProcessor(ClangdClient *client)
- : FunctionHintProcessor(client)
- , m_client(client)
- {}
-
-private:
- IAssistProposal *perform(const AssistInterface *interface) override
- {
- if (m_client->d->isTesting) {
- setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
- emit m_client->proposalReady(proposal);
- });
- }
- return FunctionHintProcessor::perform(interface);
- }
-
- ClangdClient * const m_client;
-};
-
-ClangdClient::ClangdCompletionAssistProvider::ClangdCompletionAssistProvider(ClangdClient *client)
- : LanguageClientCompletionAssistProvider(client)
- , m_client(client)
-{}
-
-IAssistProcessor *ClangdClient::ClangdCompletionAssistProvider::createProcessor(
- const AssistInterface *interface) const
-{
- qCDebug(clangdLogCompletion) << "completion processor requested for" << interface->filePath();
- qCDebug(clangdLogCompletion) << "text before cursor is"
- << interface->textAt(interface->position(), -10);
- qCDebug(clangdLogCompletion) << "text after cursor is"
- << interface->textAt(interface->position(), 10);
- ClangCompletionContextAnalyzer contextAnalyzer(interface->textDocument(),
- interface->position(), false, {});
- contextAnalyzer.analyze();
- switch (contextAnalyzer.completionAction()) {
- case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen:
- qCDebug(clangdLogCompletion) << "creating function hint processor";
- return new ClangdFunctionHintProcessor(m_client);
- case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
- qCDebug(clangdLogCompletion) << "creating doxygen processor";
- return new CustomAssistProcessor(m_client,
- contextAnalyzer.positionForProposal(),
- contextAnalyzer.positionEndOfExpression(),
- contextAnalyzer.completionOperator(),
- CustomAssistMode::Doxygen);
- case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
- qCDebug(clangdLogCompletion) << "creating macro processor";
- return new CustomAssistProcessor(m_client,
- contextAnalyzer.positionForProposal(),
- contextAnalyzer.positionEndOfExpression(),
- contextAnalyzer.completionOperator(),
- CustomAssistMode::Preprocessor);
- case ClangCompletionContextAnalyzer::CompleteSignal:
- case ClangCompletionContextAnalyzer::CompleteSlot:
- if (!interface->isBaseObject())
- return CppEditor::getCppCompletionAssistProcessor();
- default:
- break;
- }
- const QString snippetsGroup = contextAnalyzer.addSnippets() && !isInCommentOrString(interface)
- ? CppEditor::Constants::CPP_SNIPPETS_GROUP_ID
- : QString();
- qCDebug(clangdLogCompletion) << "creating proper completion processor"
- << (snippetsGroup.isEmpty() ? "without" : "with") << "snippets";
- return new ClangdCompletionAssistProcessor(m_client, snippetsGroup);
-}
-
-bool ClangdClient::ClangdCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
-{
- const QChar &ch = sequence.at(2);
- const QChar &ch2 = sequence.at(1);
- const QChar &ch3 = sequence.at(0);
- unsigned kind = T_EOF_SYMBOL;
- const int pos = CppEditor::CppCompletionAssistProvider::activationSequenceChar(
- ch, ch2, ch3, &kind, false, false);
- if (pos == 0)
- return false;
-
- // We want to minimize unneeded completion requests, as those trigger document updates,
- // which trigger re-highlighting and diagnostics, which we try to delay.
- // Therefore, we do not trigger on syntax elements that often occur in non-applicable
- // contexts, such as '(', '<' or '/'.
- switch (kind) {
- case T_DOT: case T_COLON_COLON: case T_ARROW: case T_DOT_STAR: case T_ARROW_STAR: case T_POUND:
- qCDebug(clangdLogCompletion) << "detected" << sequence << "as activation char sequence";
- return true;
- }
- return false;
-}
-
-bool ClangdClient::ClangdCompletionAssistProvider::isContinuationChar(const QChar &c) const
-{
- return CppEditor::isValidIdentifierChar(c);
-}
-
-bool ClangdClient::ClangdCompletionAssistProvider::isInCommentOrString(
- const AssistInterface *interface) const
-{
- LanguageFeatures features = LanguageFeatures::defaultFeatures();
- features.objCEnabled = CppEditor::ProjectFile::isObjC(interface->filePath().toString());
- return CppEditor::isInCommentOrString(interface, features);
-}
-
-void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
- int /*basePosition*/) const
-{
- const LanguageServerProtocol::CompletionItem item = this->item();
- QChar typedChar = triggeredCommitCharacter();
- const auto edit = item.textEdit();
- if (!edit)
- return;
-
- const int labelOpenParenOffset = item.label().indexOf('(');
- const int labelClosingParenOffset = item.label().indexOf(')');
- const auto kind = static_cast<CompletionItemKind::Kind>(
- item.kind().value_or(CompletionItemKind::Text));
- const bool isMacroCall = kind == CompletionItemKind::Text && labelOpenParenOffset != -1
- && labelClosingParenOffset > labelOpenParenOffset; // Heuristic
- const bool isFunctionLike = kind == CompletionItemKind::Function
- || kind == CompletionItemKind::Method || kind == CompletionItemKind::Constructor
- || isMacroCall;
-
- QString rawInsertText = edit->newText();
-
- // Some preparation for our magic involving (non-)insertion of parentheses and
- // cursor placement.
- if (isFunctionLike && !rawInsertText.contains('(')) {
- if (labelOpenParenOffset != -1) {
- if (labelClosingParenOffset == labelOpenParenOffset + 1) // function takes no arguments
- rawInsertText += "()";
- else // function takes arguments
- rawInsertText += "( )";
- }
- }
-
- const int firstParenOffset = rawInsertText.indexOf('(');
- const int lastParenOffset = rawInsertText.lastIndexOf(')');
- const QString detail = item.detail().value_or(QString());
- const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
- QString textToBeInserted = rawInsertText.left(firstParenOffset);
- QString extraCharacters;
- int extraLength = 0;
- int cursorOffset = 0;
- bool setAutoCompleteSkipPos = false;
- int currentPos = manipulator.currentPosition();
- const QTextDocument * const doc = manipulator.textCursorAt(currentPos).document();
- const Range range = edit->range();
- const int rangeStart = range.start().toPositionInDocument(doc);
- if (isFunctionLike && 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 && !isMacroCall && matchPreviousWord(manipulator, cursor, detail))
- abandonParen = true; // function definition
- 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(currentPos);
- 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(currentPos + 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;
- }
-
- // Avoid inserting characters that are already there
- QTextCursor cursor = manipulator.textCursorAt(rangeStart);
- cursor.movePosition(QTextCursor::EndOfWord);
- const QString textAfterCursor = manipulator.textAt(currentPos, cursor.position() - currentPos);
- if (currentPos < cursor.position()
- && textToBeInserted != textAfterCursor
- && textToBeInserted.indexOf(textAfterCursor, currentPos - rangeStart) >= 0) {
- currentPos = cursor.position();
- }
- for (int i = 0; i < extraCharacters.length(); ++i) {
- const QChar a = extraCharacters.at(i);
- const QChar b = manipulator.characterAt(currentPos + i);
- if (a == b)
- ++extraLength;
- else
- break;
- }
-
- textToBeInserted += extraCharacters;
- const int length = currentPos - rangeStart + extraLength;
- const bool isReplaced = manipulator.replace(rangeStart, length, 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);
- }
-}
-
-ClangdCompletionItem::SpecialQtType ClangdCompletionItem::getQtType(const CompletionItem &item)
-{
- const Utils::optional<MarkupOrString> doc = item.documentation();
- if (!doc)
- return SpecialQtType::None;
- 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();
- if (docText.contains("Annotation: qt_signal"))
- return SpecialQtType::Signal;
- if (docText.contains("Annotation: qt_slot"))
- return SpecialQtType::Slot;
- return SpecialQtType::None;
-}
-
-QIcon ClangdCompletionItem::icon() const
-{
- if (isDeprecated())
- return Utils::Icons::WARNING.icon();
- const SpecialQtType qtType = getQtType(item());
- switch (qtType) {
- case SpecialQtType::Signal:
- return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Signal);
- case SpecialQtType::Slot:
- // FIXME: Add visibility info to completion item tags in clangd?
- return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::SlotPublic);
- case SpecialQtType::None:
- break;
- }
- if (item().kind().value_or(CompletionItemKind::Text) == CompletionItemKind::Property)
- return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::VarPublicStatic);
- return LanguageClientCompletionItem::icon();
-}
-
-QString ClangdCompletionItem::text() const
-{
- const QString clangdValue = LanguageClientCompletionItem::text();
- if (isDeprecated())
- return "[[deprecated]]" + clangdValue;
- return clangdValue;
-}
-
MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
const AstHandler &astHandler,
AstCallbackMode callbackMode, const Range &range)
diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h
index 2aec6f8b57..5f2329b594 100644
--- a/src/plugins/clangcodemodel/clangdclient.h
+++ b/src/plugins/clangcodemodel/clangdclient.h
@@ -149,9 +149,6 @@ private:
class Private;
class VirtualFunctionAssistProcessor;
class VirtualFunctionAssistProvider;
- class ClangdFunctionHintProcessor;
- class ClangdCompletionAssistProcessor;
- class ClangdCompletionAssistProvider;
Private * const d;
};
diff --git a/src/plugins/clangcodemodel/clangdcompletion.cpp b/src/plugins/clangcodemodel/clangdcompletion.cpp
new file mode 100644
index 0000000000..6109477270
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangdcompletion.cpp
@@ -0,0 +1,643 @@
+/****************************************************************************
+**
+** Copyright (C) 2022 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 "clangdcompletion.h"
+
+#include "clangcompletioncontextanalyzer.h"
+#include "clangdclient.h"
+#include "clangpreprocessorassistproposalitem.h"
+#include "clangutils.h"
+#include "tasktimers.h"
+
+#include <cppeditor/cppcompletionassistprocessor.h>
+#include <cppeditor/cppcompletionassistprovider.h>
+#include <cppeditor/cppdoxygen.h>
+#include <cppeditor/cppeditorconstants.h>
+#include <cppeditor/cppmodelmanager.h>
+#include <cppeditor/cppprojectfile.h>
+#include <cppeditor/projectpart.h>
+#include <cplusplus/Icons.h>
+#include <cplusplus/MatchingText.h>
+#include <languageclient/languageclientfunctionhint.h>
+#include <projectexplorer/headerpath.h>
+#include <texteditor/codeassist/assistinterface.h>
+#include <texteditor/codeassist/genericproposal.h>
+#include <texteditor/codeassist/genericproposalmodel.h>
+#include <texteditor/texteditorsettings.h>
+#include <utils/utilsicons.h>
+
+using namespace CppEditor;
+using namespace CPlusPlus;
+using namespace LanguageClient;
+using namespace LanguageServerProtocol;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace ClangCodeModel::Internal {
+static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.completion",
+ QtWarningMsg);
+
+enum class CustomAssistMode { Doxygen, Preprocessor, IncludePath };
+class CustomAssistProcessor : public IAssistProcessor
+{
+public:
+ CustomAssistProcessor(ClangdClient *client, int position, int endPos,
+ unsigned completionOperator, CustomAssistMode mode);
+
+private:
+ IAssistProposal *perform(const AssistInterface *interface) override;
+
+ AssistProposalItemInterface *createItem(const QString &text, const QIcon &icon) const;
+
+ static QList<AssistProposalItemInterface *> completeInclude(
+ int position, unsigned completionOperator, const AssistInterface *interface,
+ const HeaderPaths &headerPaths);
+ static QList<AssistProposalItemInterface *> completeIncludePath(
+ const QString &realPath, const QStringList &suffixes, unsigned completionOperator);
+
+ ClangdClient * const m_client;
+ const int m_position;
+ const int m_endPos;
+ const unsigned m_completionOperator;
+ const CustomAssistMode m_mode;
+};
+
+class ClangdCompletionItem : public LanguageClientCompletionItem
+{
+public:
+ using LanguageClientCompletionItem::LanguageClientCompletionItem;
+ void apply(TextDocumentManipulatorInterface &manipulator,
+ int basePosition) const override;
+
+ enum class SpecialQtType { Signal, Slot, None };
+ static SpecialQtType getQtType(const CompletionItem &item);
+
+private:
+ QIcon icon() const override;
+ QString text() const override;
+};
+
+class ClangdCompletionAssistProcessor : public LanguageClientCompletionAssistProcessor
+{
+public:
+ ClangdCompletionAssistProcessor(ClangdClient *client, const QString &snippetsGroup);
+ ~ClangdCompletionAssistProcessor();
+
+private:
+ IAssistProposal *perform(const AssistInterface *interface) override;
+ QList<AssistProposalItemInterface *> generateCompletionItems(
+ const QList<LanguageServerProtocol::CompletionItem> &items) const override;
+
+ ClangdClient * const m_client;
+ QElapsedTimer m_timer;
+};
+
+class ClangdFunctionHintProcessor : public FunctionHintProcessor
+{
+public:
+ ClangdFunctionHintProcessor(ClangdClient *client);
+
+private:
+ IAssistProposal *perform(const AssistInterface *interface) override;
+
+ ClangdClient * const m_client;
+};
+
+ClangdCompletionAssistProvider::ClangdCompletionAssistProvider(ClangdClient *client)
+ : LanguageClientCompletionAssistProvider(client)
+ , m_client(client)
+{}
+
+IAssistProcessor *ClangdCompletionAssistProvider::createProcessor(
+ const AssistInterface *interface) const
+{
+ qCDebug(clangdLogCompletion) << "completion processor requested for" << interface->filePath();
+ qCDebug(clangdLogCompletion) << "text before cursor is"
+ << interface->textAt(interface->position(), -10);
+ qCDebug(clangdLogCompletion) << "text after cursor is"
+ << interface->textAt(interface->position(), 10);
+ ClangCompletionContextAnalyzer contextAnalyzer(interface->textDocument(),
+ interface->position(), false, {});
+ contextAnalyzer.analyze();
+ switch (contextAnalyzer.completionAction()) {
+ case ClangCompletionContextAnalyzer::PassThroughToLibClangAfterLeftParen:
+ qCDebug(clangdLogCompletion) << "creating function hint processor";
+ return new ClangdFunctionHintProcessor(m_client);
+ case ClangCompletionContextAnalyzer::CompleteDoxygenKeyword:
+ qCDebug(clangdLogCompletion) << "creating doxygen processor";
+ return new CustomAssistProcessor(m_client,
+ contextAnalyzer.positionForProposal(),
+ contextAnalyzer.positionEndOfExpression(),
+ contextAnalyzer.completionOperator(),
+ CustomAssistMode::Doxygen);
+ case ClangCompletionContextAnalyzer::CompletePreprocessorDirective:
+ qCDebug(clangdLogCompletion) << "creating macro processor";
+ return new CustomAssistProcessor(m_client,
+ contextAnalyzer.positionForProposal(),
+ contextAnalyzer.positionEndOfExpression(),
+ contextAnalyzer.completionOperator(),
+ CustomAssistMode::Preprocessor);
+ case ClangCompletionContextAnalyzer::CompleteSignal:
+ case ClangCompletionContextAnalyzer::CompleteSlot:
+ if (!interface->isBaseObject())
+ return CppEditor::getCppCompletionAssistProcessor();
+ default:
+ break;
+ }
+ const QString snippetsGroup = contextAnalyzer.addSnippets() && !isInCommentOrString(interface)
+ ? CppEditor::Constants::CPP_SNIPPETS_GROUP_ID
+ : QString();
+ qCDebug(clangdLogCompletion) << "creating proper completion processor"
+ << (snippetsGroup.isEmpty() ? "without" : "with") << "snippets";
+ return new ClangdCompletionAssistProcessor(m_client, snippetsGroup);
+}
+
+bool ClangdCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const
+{
+ const QChar &ch = sequence.at(2);
+ const QChar &ch2 = sequence.at(1);
+ const QChar &ch3 = sequence.at(0);
+ unsigned kind = T_EOF_SYMBOL;
+ const int pos = CppCompletionAssistProvider::activationSequenceChar(
+ ch, ch2, ch3, &kind, false, false);
+ if (pos == 0)
+ return false;
+
+ // We want to minimize unneeded completion requests, as those trigger document updates,
+ // which trigger re-highlighting and diagnostics, which we try to delay.
+ // Therefore, we do not trigger on syntax elements that often occur in non-applicable
+ // contexts, such as '(', '<' or '/'.
+ switch (kind) {
+ case T_DOT: case T_COLON_COLON: case T_ARROW: case T_DOT_STAR: case T_ARROW_STAR: case T_POUND:
+ qCDebug(clangdLogCompletion) << "detected" << sequence << "as activation char sequence";
+ return true;
+ }
+ return false;
+}
+
+bool ClangdCompletionAssistProvider::isContinuationChar(const QChar &c) const
+{
+ return isValidIdentifierChar(c);
+}
+
+bool ClangdCompletionAssistProvider::isInCommentOrString(const AssistInterface *interface) const
+{
+ LanguageFeatures features = LanguageFeatures::defaultFeatures();
+ features.objCEnabled = ProjectFile::isObjC(interface->filePath().toString());
+ return CppEditor::isInCommentOrString(interface, features);
+}
+
+void ClangdCompletionItem::apply(TextDocumentManipulatorInterface &manipulator,
+ int /*basePosition*/) const
+{
+ const CompletionItem item = this->item();
+ QChar typedChar = triggeredCommitCharacter();
+ const auto edit = item.textEdit();
+ if (!edit)
+ return;
+
+ const int labelOpenParenOffset = item.label().indexOf('(');
+ const int labelClosingParenOffset = item.label().indexOf(')');
+ const auto kind = static_cast<CompletionItemKind::Kind>(
+ item.kind().value_or(CompletionItemKind::Text));
+ const bool isMacroCall = kind == CompletionItemKind::Text && labelOpenParenOffset != -1
+ && labelClosingParenOffset > labelOpenParenOffset; // Heuristic
+ const bool isFunctionLike = kind == CompletionItemKind::Function
+ || kind == CompletionItemKind::Method || kind == CompletionItemKind::Constructor
+ || isMacroCall;
+
+ QString rawInsertText = edit->newText();
+
+ // Some preparation for our magic involving (non-)insertion of parentheses and
+ // cursor placement.
+ if (isFunctionLike && !rawInsertText.contains('(')) {
+ if (labelOpenParenOffset != -1) {
+ if (labelClosingParenOffset == labelOpenParenOffset + 1) // function takes no arguments
+ rawInsertText += "()";
+ else // function takes arguments
+ rawInsertText += "( )";
+ }
+ }
+
+ const int firstParenOffset = rawInsertText.indexOf('(');
+ const int lastParenOffset = rawInsertText.lastIndexOf(')');
+ const QString detail = item.detail().value_or(QString());
+ const CompletionSettings &completionSettings = TextEditorSettings::completionSettings();
+ QString textToBeInserted = rawInsertText.left(firstParenOffset);
+ QString extraCharacters;
+ int extraLength = 0;
+ int cursorOffset = 0;
+ bool setAutoCompleteSkipPos = false;
+ int currentPos = manipulator.currentPosition();
+ const QTextDocument * const doc = manipulator.textCursorAt(currentPos).document();
+ const Range range = edit->range();
+ const int rangeStart = range.start().toPositionInDocument(doc);
+ if (isFunctionLike && 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 && !isMacroCall && matchPreviousWord(manipulator, cursor, detail))
+ abandonParen = true; // function definition
+ 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(currentPos);
+ 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(currentPos + 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;
+ }
+
+ // Avoid inserting characters that are already there
+ QTextCursor cursor = manipulator.textCursorAt(rangeStart);
+ cursor.movePosition(QTextCursor::EndOfWord);
+ const QString textAfterCursor = manipulator.textAt(currentPos, cursor.position() - currentPos);
+ if (currentPos < cursor.position()
+ && textToBeInserted != textAfterCursor
+ && textToBeInserted.indexOf(textAfterCursor, currentPos - rangeStart) >= 0) {
+ currentPos = cursor.position();
+ }
+ for (int i = 0; i < extraCharacters.length(); ++i) {
+ const QChar a = extraCharacters.at(i);
+ const QChar b = manipulator.characterAt(currentPos + i);
+ if (a == b)
+ ++extraLength;
+ else
+ break;
+ }
+
+ textToBeInserted += extraCharacters;
+ const int length = currentPos - rangeStart + extraLength;
+ const bool isReplaced = manipulator.replace(rangeStart, length, 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);
+ }
+}
+
+ClangdCompletionItem::SpecialQtType ClangdCompletionItem::getQtType(const CompletionItem &item)
+{
+ const Utils::optional<MarkupOrString> doc = item.documentation();
+ if (!doc)
+ return SpecialQtType::None;
+ 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();
+ if (docText.contains("Annotation: qt_signal"))
+ return SpecialQtType::Signal;
+ if (docText.contains("Annotation: qt_slot"))
+ return SpecialQtType::Slot;
+ return SpecialQtType::None;
+}
+
+QIcon ClangdCompletionItem::icon() const
+{
+ if (isDeprecated())
+ return Utils::Icons::WARNING.icon();
+ const SpecialQtType qtType = getQtType(item());
+ switch (qtType) {
+ case SpecialQtType::Signal:
+ return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::Signal);
+ case SpecialQtType::Slot:
+ // FIXME: Add visibility info to completion item tags in clangd?
+ return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::SlotPublic);
+ case SpecialQtType::None:
+ break;
+ }
+ if (item().kind().value_or(CompletionItemKind::Text) == CompletionItemKind::Property)
+ return Utils::CodeModelIcon::iconForType(Utils::CodeModelIcon::VarPublicStatic);
+ return LanguageClientCompletionItem::icon();
+}
+
+QString ClangdCompletionItem::text() const
+{
+ const QString clangdValue = LanguageClientCompletionItem::text();
+ if (isDeprecated())
+ return "[[deprecated]]" + clangdValue;
+ return clangdValue;
+}
+
+CustomAssistProcessor::CustomAssistProcessor(ClangdClient *client, int position, int endPos,
+ unsigned completionOperator, CustomAssistMode mode)
+ : m_client(client)
+ , m_position(position)
+ , m_endPos(endPos)
+ , m_completionOperator(completionOperator)
+ , m_mode(mode)
+{}
+
+IAssistProposal *CustomAssistProcessor::perform(const AssistInterface *interface)
+{
+ QList<AssistProposalItemInterface *> completions;
+ switch (m_mode) {
+ case CustomAssistMode::Doxygen:
+ for (int i = 1; i < T_DOXY_LAST_TAG; ++i) {
+ completions << createItem(QLatin1String(doxygenTagSpell(i)),
+ CPlusPlus::Icons::keywordIcon());
+ }
+ break;
+ case CustomAssistMode::Preprocessor: {
+ static QIcon macroIcon = Utils::CodeModelIcon::iconForType(CodeModelIcon::Macro);
+ for (const QString &completion
+ : CppCompletionAssistProcessor::preprocessorCompletions()) {
+ completions << createItem(completion, macroIcon);
+ }
+ if (ProjectFile::isObjC(interface->filePath().toString()))
+ completions << createItem("import", macroIcon);
+ break;
+ }
+ case CustomAssistMode::IncludePath: {
+ HeaderPaths headerPaths;
+ const ProjectPart::ConstPtr projectPart
+ = projectPartForFile(interface->filePath().toString());
+ if (projectPart)
+ headerPaths = projectPart->headerPaths;
+ completions = completeInclude(m_endPos, m_completionOperator, interface, headerPaths);
+ break;
+ }
+ }
+ GenericProposalModelPtr model(new GenericProposalModel);
+ model->loadContent(completions);
+ const auto proposal = new GenericProposal(m_position, model);
+ if (m_client->testingEnabled()) {
+ emit m_client->proposalReady(proposal);
+ return nullptr;
+ }
+ return proposal;
+}
+
+AssistProposalItemInterface *CustomAssistProcessor::createItem(const QString &text,
+ const QIcon &icon) const
+{
+ const auto item = new ClangPreprocessorAssistProposalItem;
+ item->setText(text);
+ item->setIcon(icon);
+ item->setCompletionOperator(m_completionOperator);
+ return item;
+}
+
+/**
+ * @brief Creates completion proposals for #include and given cursor
+ * @param position - cursor placed after opening bracked or quote
+ * @param completionOperator - the type of token
+ * @param interface - relevant document data
+ * @param headerPaths - the include paths
+ * @return the list of completion items
+ */
+QList<AssistProposalItemInterface *> CustomAssistProcessor::completeInclude(
+ int position, unsigned completionOperator, const AssistInterface *interface,
+ const HeaderPaths &headerPaths)
+{
+ QTextCursor cursor(interface->textDocument());
+ cursor.setPosition(position);
+ QString directoryPrefix;
+ if (completionOperator == T_SLASH) {
+ QTextCursor c = cursor;
+ c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
+ QString sel = c.selectedText();
+ int startCharPos = sel.indexOf(QLatin1Char('"'));
+ if (startCharPos == -1) {
+ startCharPos = sel.indexOf(QLatin1Char('<'));
+ completionOperator = T_ANGLE_STRING_LITERAL;
+ } else {
+ completionOperator = T_STRING_LITERAL;
+ }
+ if (startCharPos != -1)
+ directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1);
+ }
+
+ // Make completion for all relevant includes
+ HeaderPaths allHeaderPaths = headerPaths;
+ const auto currentFilePath = HeaderPath::makeUser(
+ interface->filePath().toFileInfo().path());
+ if (!allHeaderPaths.contains(currentFilePath))
+ allHeaderPaths.append(currentFilePath);
+
+ const MimeType mimeType = mimeTypeForName("text/x-c++hdr");
+ const QStringList suffixes = mimeType.suffixes();
+
+ QList<AssistProposalItemInterface *> completions;
+ for (const HeaderPath &headerPath : qAsConst(allHeaderPaths)) {
+ QString realPath = headerPath.path;
+ if (!directoryPrefix.isEmpty()) {
+ realPath += QLatin1Char('/');
+ realPath += directoryPrefix;
+ if (headerPath.type == HeaderPathType::Framework)
+ realPath += QLatin1String(".framework/Headers");
+ }
+ completions << completeIncludePath(realPath, suffixes, completionOperator);
+ }
+
+ QList<QPair<AssistProposalItemInterface *, QString>> completionsForSorting;
+ for (AssistProposalItemInterface * const item : qAsConst(completions)) {
+ QString s = item->text();
+ s.replace('/', QChar(0)); // The dir separator should compare less than anything else.
+ completionsForSorting << qMakePair(item, s);
+ }
+ Utils::sort(completionsForSorting, [](const auto &left, const auto &right) {
+ return left.second < right.second;
+ });
+ for (int i = 0; i < completionsForSorting.count(); ++i)
+ completions[i] = completionsForSorting[i].first;
+
+ return completions;
+}
+
+/**
+ * @brief Finds #include completion proposals using given include path
+ * @param realPath - one of directories where compiler searches includes
+ * @param suffixes - file suffixes for C/C++ header files
+ * @return a list of matching completion items
+ */
+QList<AssistProposalItemInterface *> CustomAssistProcessor::completeIncludePath(
+ const QString &realPath, const QStringList &suffixes, unsigned completionOperator)
+{
+ QList<AssistProposalItemInterface *> completions;
+ QDirIterator i(realPath, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+ //: Parent folder for proposed #include completion
+ const QString hint = ClangdClient::tr("Location: %1")
+ .arg(QDir::toNativeSeparators(QDir::cleanPath(realPath)));
+ while (i.hasNext()) {
+ const QString fileName = i.next();
+ const QFileInfo fileInfo = i.fileInfo();
+ const QString suffix = fileInfo.suffix();
+ if (suffix.isEmpty() || suffixes.contains(suffix)) {
+ QString text = fileName.mid(realPath.length() + 1);
+ if (fileInfo.isDir())
+ text += QLatin1Char('/');
+
+ auto *item = new ClangPreprocessorAssistProposalItem;
+ item->setText(text);
+ item->setDetail(hint);
+ item->setIcon(CPlusPlus::Icons::keywordIcon());
+ item->setCompletionOperator(completionOperator);
+ completions.append(item);
+ }
+ }
+ return completions;
+}
+
+ClangdCompletionAssistProcessor::ClangdCompletionAssistProcessor(ClangdClient *client,
+ const QString &snippetsGroup)
+ : LanguageClientCompletionAssistProcessor(client, snippetsGroup)
+ , m_client(client)
+{
+ m_timer.start();
+}
+
+ClangdCompletionAssistProcessor::~ClangdCompletionAssistProcessor()
+{
+ qCDebug(clangdLogTiming).noquote().nospace()
+ << "ClangdCompletionAssistProcessor took: " << m_timer.elapsed() << " ms";
+}
+
+IAssistProposal *ClangdCompletionAssistProcessor::perform(const AssistInterface *interface)
+{
+ if (m_client->testingEnabled()) {
+ setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
+ emit m_client->proposalReady(proposal);
+ });
+ }
+ return LanguageClientCompletionAssistProcessor::perform(interface);
+}
+
+QList<AssistProposalItemInterface *> ClangdCompletionAssistProcessor::generateCompletionItems(
+ const QList<CompletionItem> &items) const
+{
+ qCDebug(clangdLog) << "received" << items.count() << "completions";
+
+ auto itemGenerator = [](const QList<LanguageServerProtocol::CompletionItem> &items) {
+ return Utils::transform<QList<AssistProposalItemInterface *>>(items,
+ [](const LanguageServerProtocol::CompletionItem &item) {
+ return new ClangdCompletionItem(item);
+ });
+ };
+
+ // 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) {
+ return ClangdCompletionItem::getQtType(ci) == ClangdCompletionItem::SpecialQtType::Signal;
+ };
+ const QTextDocument *doc = document();
+ const int pos = basePos();
+ if (!doc || pos < 0 || !Utils::anyOf(items, criterion))
+ return itemGenerator(items);
+ const QString content = doc->toPlainText();
+ const bool requiresSignal = CppModelManager::instance()->getSignalSlotType(
+ filePath().toString(), content.toUtf8(), pos)
+ == SignalSlotType::NewStyleSignal;
+ if (requiresSignal)
+ return itemGenerator(Utils::filtered(items, criterion));
+ return itemGenerator(items);
+}
+
+ClangdFunctionHintProcessor::ClangdFunctionHintProcessor(ClangdClient *client)
+ : FunctionHintProcessor(client)
+ , m_client(client)
+{}
+
+IAssistProposal *ClangdFunctionHintProcessor::perform(const AssistInterface *interface)
+{
+ if (m_client->testingEnabled()) {
+ setAsyncCompletionAvailableHandler([this](IAssistProposal *proposal) {
+ emit m_client->proposalReady(proposal);
+ });
+ }
+ return FunctionHintProcessor::perform(interface);
+}
+
+ClangdCompletionCapabilities::ClangdCompletionCapabilities(const JsonObject &object)
+ : TextDocumentClientCapabilities::CompletionCapabilities(object)
+{
+ insert("editsNearCursor", true); // For dot-to-arrow correction.
+ if (Utils::optional<CompletionItemCapbilities> completionItemCaps = completionItem()) {
+ completionItemCaps->setSnippetSupport(false);
+ setCompletionItem(*completionItemCaps);
+ }
+}
+
+} // namespace ClangCodeModel::Internal
diff --git a/src/plugins/clangcodemodel/clangdcompletion.h b/src/plugins/clangcodemodel/clangdcompletion.h
new file mode 100644
index 0000000000..de3c1b39bd
--- /dev/null
+++ b/src/plugins/clangcodemodel/clangdcompletion.h
@@ -0,0 +1,62 @@
+
+#include <languageclient/languageclientcompletionassist.h>
+/****************************************************************************
+**
+** Copyright (C) 2022 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 <languageclient/languageclientcompletionassist.h>
+#include <languageserverprotocol/clientcapabilities.h>
+
+namespace TextEditor { class IAssistProcessor; }
+namespace ClangCodeModel::Internal {
+class ClangdClient;
+
+class ClangdCompletionAssistProvider : public LanguageClient::LanguageClientCompletionAssistProvider
+{
+public:
+ ClangdCompletionAssistProvider(ClangdClient *client);
+
+private:
+ TextEditor::IAssistProcessor *createProcessor(
+ const TextEditor::AssistInterface *interface) const override;
+
+ int activationCharSequenceLength() const override { return 3; }
+ bool isActivationCharSequence(const QString &sequence) const override;
+ bool isContinuationChar(const QChar &c) const override;
+
+ bool isInCommentOrString(const TextEditor::AssistInterface *interface) const;
+
+ ClangdClient * const m_client;
+};
+
+class ClangdCompletionCapabilities
+ : public LanguageServerProtocol::TextDocumentClientCapabilities::CompletionCapabilities
+{
+public:
+ explicit ClangdCompletionCapabilities(const JsonObject &object);
+};
+
+} // namespace ClangCodeModel::Internal