forked from qt-creator/qt-creator
ClangCodeModel: Move completion code to its own set of files
Change-Id: Iad856881ace80896f4aceab3fbf129d7635de642 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
@@ -17,6 +17,7 @@ add_qtc_plugin(ClangCodeModel
|
|||||||
clangconstants.h
|
clangconstants.h
|
||||||
clangdast.cpp clangdast.h
|
clangdast.cpp clangdast.h
|
||||||
clangdclient.cpp clangdclient.h
|
clangdclient.cpp clangdclient.h
|
||||||
|
clangdcompletion.cpp clangdcompletion.h
|
||||||
clangdfollowsymbol.cpp clangdfollowsymbol.h
|
clangdfollowsymbol.cpp clangdfollowsymbol.h
|
||||||
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h
|
||||||
clangdquickfixes.cpp clangdquickfixes.h
|
clangdquickfixes.cpp clangdquickfixes.h
|
||||||
|
@@ -34,6 +34,8 @@ QtcPlugin {
|
|||||||
"clangdast.h",
|
"clangdast.h",
|
||||||
"clangdclient.cpp",
|
"clangdclient.cpp",
|
||||||
"clangdclient.h",
|
"clangdclient.h",
|
||||||
|
"clangdcompletion.cpp",
|
||||||
|
"clangdcompletion.h",
|
||||||
"clangdfollowsymbol.cpp",
|
"clangdfollowsymbol.cpp",
|
||||||
"clangdfollowsymbol.h",
|
"clangdfollowsymbol.h",
|
||||||
"clangdiagnostictooltipwidget.cpp",
|
"clangdiagnostictooltipwidget.cpp",
|
||||||
|
@@ -25,14 +25,13 @@
|
|||||||
|
|
||||||
#include "clangdclient.h"
|
#include "clangdclient.h"
|
||||||
|
|
||||||
#include "clangcompletioncontextanalyzer.h"
|
|
||||||
#include "clangconstants.h"
|
#include "clangconstants.h"
|
||||||
#include "clangdast.h"
|
#include "clangdast.h"
|
||||||
|
#include "clangdcompletion.h"
|
||||||
#include "clangdfollowsymbol.h"
|
#include "clangdfollowsymbol.h"
|
||||||
#include "clangdlocatorfilters.h"
|
#include "clangdlocatorfilters.h"
|
||||||
#include "clangdquickfixes.h"
|
#include "clangdquickfixes.h"
|
||||||
#include "clangdswitchdecldef.h"
|
#include "clangdswitchdecldef.h"
|
||||||
#include "clangpreprocessorassistproposalitem.h"
|
|
||||||
#include "clangtextmark.h"
|
#include "clangtextmark.h"
|
||||||
#include "clangutils.h"
|
#include "clangutils.h"
|
||||||
#include "clangdsemantichighlighting.h"
|
#include "clangdsemantichighlighting.h"
|
||||||
@@ -45,12 +44,7 @@
|
|||||||
#include <cplusplus/ASTPath.h>
|
#include <cplusplus/ASTPath.h>
|
||||||
#include <cplusplus/FindUsages.h>
|
#include <cplusplus/FindUsages.h>
|
||||||
#include <cplusplus/Icons.h>
|
#include <cplusplus/Icons.h>
|
||||||
#include <cplusplus/MatchingText.h>
|
|
||||||
#include <cppeditor/cppeditorconstants.h>
|
|
||||||
#include <cppeditor/cppcodemodelsettings.h>
|
#include <cppeditor/cppcodemodelsettings.h>
|
||||||
#include <cppeditor/cppcompletionassistprocessor.h>
|
|
||||||
#include <cppeditor/cppcompletionassistprovider.h>
|
|
||||||
#include <cppeditor/cppdoxygen.h>
|
|
||||||
#include <cppeditor/cppeditorwidget.h>
|
#include <cppeditor/cppeditorwidget.h>
|
||||||
#include <cppeditor/cppfindreferences.h>
|
#include <cppeditor/cppfindreferences.h>
|
||||||
#include <cppeditor/cppmodelmanager.h>
|
#include <cppeditor/cppmodelmanager.h>
|
||||||
@@ -61,8 +55,6 @@
|
|||||||
#include <cppeditor/semantichighlighter.h>
|
#include <cppeditor/semantichighlighter.h>
|
||||||
#include <cppeditor/cppsemanticinfo.h>
|
#include <cppeditor/cppsemanticinfo.h>
|
||||||
#include <languageclient/diagnosticmanager.h>
|
#include <languageclient/diagnosticmanager.h>
|
||||||
#include <languageclient/languageclientcompletionassist.h>
|
|
||||||
#include <languageclient/languageclientfunctionhint.h>
|
|
||||||
#include <languageclient/languageclienthoverhandler.h>
|
#include <languageclient/languageclienthoverhandler.h>
|
||||||
#include <languageclient/languageclientinterface.h>
|
#include <languageclient/languageclientinterface.h>
|
||||||
#include <languageclient/languageclientmanager.h>
|
#include <languageclient/languageclientmanager.h>
|
||||||
@@ -79,7 +71,6 @@
|
|||||||
#include <texteditor/codeassist/iassistprocessor.h>
|
#include <texteditor/codeassist/iassistprocessor.h>
|
||||||
#include <texteditor/codeassist/iassistprovider.h>
|
#include <texteditor/codeassist/iassistprovider.h>
|
||||||
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
|
||||||
#include <texteditor/texteditorsettings.h>
|
|
||||||
#include <texteditor/texteditor.h>
|
#include <texteditor/texteditor.h>
|
||||||
#include <utils/algorithm.h>
|
#include <utils/algorithm.h>
|
||||||
#include <utils/fileutils.h>
|
#include <utils/fileutils.h>
|
||||||
@@ -119,8 +110,6 @@ namespace Internal {
|
|||||||
Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
|
Q_LOGGING_CATEGORY(clangdLog, "qtc.clangcodemodel.clangd", QtWarningMsg);
|
||||||
Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg);
|
Q_LOGGING_CATEGORY(clangdLogAst, "qtc.clangcodemodel.clangd.ast", QtWarningMsg);
|
||||||
static Q_LOGGING_CATEGORY(clangdLogServer, "qtc.clangcodemodel.clangd.server", 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 QString indexingToken() { return "backgroundIndexProgress"; }
|
||||||
|
|
||||||
static Usage::Type getUsageType(const ClangdAstPath &path)
|
static Usage::Type getUsageType(const ClangdAstPath &path)
|
||||||
@@ -350,178 +339,6 @@ public:
|
|||||||
{ insert("publishDiagnostics", caps); }
|
{ 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)
|
static qint64 getRevision(const TextDocument *doc)
|
||||||
{
|
{
|
||||||
return doc->document()->revision();
|
return doc->document()->revision();
|
||||||
@@ -663,118 +480,6 @@ public:
|
|||||||
bool isTesting = false;
|
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,
|
static void addToCompilationDb(QJsonObject &cdb,
|
||||||
const CppEditor::ProjectPart &projectPart,
|
const CppEditor::ProjectPart &projectPart,
|
||||||
CppEditor::UsePrecompiledHeaders usePch,
|
CppEditor::UsePrecompiledHeaders usePch,
|
||||||
@@ -2118,303 +1823,6 @@ QString ClangdDiagnostic::category() const
|
|||||||
return typedValue<QString>("category");
|
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,
|
MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc,
|
||||||
const AstHandler &astHandler,
|
const AstHandler &astHandler,
|
||||||
AstCallbackMode callbackMode, const Range &range)
|
AstCallbackMode callbackMode, const Range &range)
|
||||||
|
@@ -149,9 +149,6 @@ private:
|
|||||||
class Private;
|
class Private;
|
||||||
class VirtualFunctionAssistProcessor;
|
class VirtualFunctionAssistProcessor;
|
||||||
class VirtualFunctionAssistProvider;
|
class VirtualFunctionAssistProvider;
|
||||||
class ClangdFunctionHintProcessor;
|
|
||||||
class ClangdCompletionAssistProcessor;
|
|
||||||
class ClangdCompletionAssistProvider;
|
|
||||||
Private * const d;
|
Private * const d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
643
src/plugins/clangcodemodel/clangdcompletion.cpp
Normal file
643
src/plugins/clangcodemodel/clangdcompletion.cpp
Normal file
@@ -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
|
62
src/plugins/clangcodemodel/clangdcompletion.h
Normal file
62
src/plugins/clangcodemodel/clangdcompletion.h
Normal file
@@ -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
|
Reference in New Issue
Block a user