#include "CodeComplete.h"
#include "AST.h"
#include "CodeCompletionStrings.h"
#include "Compiler.h"
#include "ExpectedTypes.h"
#include "Feature.h"
#include "FileDistance.h"
#include "FuzzyMatch.h"
#include "Headers.h"
#include "Hover.h"
#include "Preamble.h"
#include "Protocol.h"
#include "Quality.h"
#include "SourceCode.h"
#include "URI.h"
#include "index/Index.h"
#include "index/Symbol.h"
#include "index/SymbolOrigin.h"
#include "support/Logger.h"
#include "support/Markup.h"
#include "support/Threading.h"
#include "support/ThreadsafeFS.h"
#include "support/Trace.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/ExternalPreprocessorSource.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "clang/Sema/DeclSpec.h"
#include "clang/Sema/Sema.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/ScopedPrinter.h"
#include <algorithm>
#include <iterator>
#include <limits>
#include <optional>
#include <utility>
#define DEBUG_TYPE "CodeComplete"
namespace clang {
namespace clangd {
#if CLANGD_DECISION_FOREST
const CodeCompleteOptions::CodeCompletionRankingModel
CodeCompleteOptions::DefaultRankingModel =
CodeCompleteOptions::DecisionForest;
#else
const CodeCompleteOptions::CodeCompletionRankingModel
CodeCompleteOptions::DefaultRankingModel = CodeCompleteOptions::Heuristics;
#endif
namespace {
CompletionItemKind
toCompletionItemKind(index::SymbolKind Kind,
const llvm::StringRef *Signature = nullptr) {
using SK = index::SymbolKind;
switch (Kind) {
case SK::Unknown:
return CompletionItemKind::Missing;
case SK::Module:
case SK::Namespace:
case SK::NamespaceAlias:
return CompletionItemKind::Module;
case SK::Macro:
return Signature && Signature->contains('(') ? CompletionItemKind::Function
: CompletionItemKind::Constant;
case SK::Enum:
return CompletionItemKind::Enum;
case SK::Struct:
return CompletionItemKind::Struct;
case SK::Class:
case SK::Extension:
case SK::Union:
return CompletionItemKind::Class;
case SK::Protocol:
return CompletionItemKind::Interface;
case SK::TypeAlias:
return CompletionItemKind::Interface;
case SK::Using:
return CompletionItemKind::Reference;
case SK::Function:
case SK::ConversionFunction:
return CompletionItemKind::Function;
case SK::Variable:
case SK::Parameter:
case SK::NonTypeTemplateParm:
return CompletionItemKind::Variable;
case SK::Field:
return CompletionItemKind::Field;
case SK::EnumConstant:
return CompletionItemKind::EnumMember;
case SK::InstanceMethod:
case SK::ClassMethod:
case SK::StaticMethod:
case SK::Destructor:
return CompletionItemKind::Method;
case SK::InstanceProperty:
case SK::ClassProperty:
case SK::StaticProperty:
return CompletionItemKind::Property;
case SK::Constructor:
return CompletionItemKind::Constructor;
case SK::TemplateTypeParm:
case SK::TemplateTemplateParm:
return CompletionItemKind::TypeParameter;
case SK::Concept:
return CompletionItemKind::Interface;
}
llvm_unreachable("Unhandled clang::index::SymbolKind.");
}
CompletionItemKind toCompletionItemKind(const CodeCompletionResult &Res,
CodeCompletionContext::Kind CtxKind) {
if (Res.Declaration)
return toCompletionItemKind(index::getSymbolInfo(Res.Declaration).Kind);
if (CtxKind == CodeCompletionContext::CCC_IncludedFile)
return CompletionItemKind::File;
switch (Res.Kind) {
case CodeCompletionResult::RK_Declaration:
llvm_unreachable("RK_Declaration without Decl");
case CodeCompletionResult::RK_Keyword:
return CompletionItemKind::Keyword;
case CodeCompletionResult::RK_Macro:
return Res.MacroDefInfo && Res.MacroDefInfo->isFunctionLike()
? CompletionItemKind::Function
: CompletionItemKind::Constant;
case CodeCompletionResult::RK_Pattern:
return CompletionItemKind::Snippet;
}
llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
}
MarkupContent renderDoc(const markup::Document &Doc, MarkupKind Kind) {
MarkupContent Result;
Result.kind = Kind;
switch (Kind) {
case MarkupKind::PlainText:
Result.value.append(Doc.asPlainText());
break;
case MarkupKind::Markdown:
Result.value.append(Doc.asMarkdown());
break;
}
return Result;
}
Symbol::IncludeDirective insertionDirective(const CodeCompleteOptions &Opts) {
if (!Opts.ImportInsertions || !Opts.MainFileSignals)
return Symbol::IncludeDirective::Include;
return Opts.MainFileSignals->InsertionDirective;
}
struct RawIdentifier {
llvm::StringRef Name;
unsigned References;
};
struct CompletionCandidate {
llvm::StringRef Name;
const CodeCompletionResult *SemaResult = nullptr;
const Symbol *IndexResult = nullptr;
const RawIdentifier *IdentifierResult = nullptr;
llvm::SmallVector<SymbolInclude, 1> RankedIncludeHeaders;
size_t overloadSet(const CodeCompleteOptions &Opts, llvm::StringRef FileName,
IncludeInserter *Inserter,
CodeCompletionContext::Kind CCContextKind) const {
if (!Opts.BundleOverloads.value_or(false))
return 0;
std::string HeaderForHash;
if (Inserter) {
if (auto Header = headerToInsertIfAllowed(Opts, CCContextKind)) {
if (auto HeaderFile = toHeaderFile(*Header, FileName)) {
if (auto Spelled =
Inserter->calculateIncludePath(*HeaderFile, FileName))
HeaderForHash = *Spelled;
} else {
vlog("Code completion header path manipulation failed {0}",
HeaderFile.takeError());
}
}
}
llvm::SmallString<256> Scratch;
if (IndexResult) {
switch (IndexResult->SymInfo.Kind) {
case index::SymbolKind::ClassMethod:
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::StaticMethod:
#ifndef NDEBUG
llvm_unreachable("Don't expect members from index in code completion");
#else
[[fallthrough]];
#endif
case index::SymbolKind::Function:
return llvm::hash_combine(
(IndexResult->Scope + IndexResult->Name).toStringRef(Scratch),
HeaderForHash);
default:
return 0;
}
}
if (SemaResult) {
const NamedDecl *D = SemaResult->Declaration;
if (!D || !D->isFunctionOrFunctionTemplate())
return 0;
{
llvm::raw_svector_ostream OS(Scratch);
D->printQualifiedName(OS);
}
return llvm::hash_combine(Scratch, HeaderForHash);
}
assert(IdentifierResult);
return 0;
}
bool contextAllowsHeaderInsertion(CodeCompletionContext::Kind Kind) const {
if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl)
return false;
return true;
}
std::optional<llvm::StringRef>
headerToInsertIfAllowed(const CodeCompleteOptions &Opts,
CodeCompletionContext::Kind ContextKind) const {
if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert ||
RankedIncludeHeaders.empty() ||
!contextAllowsHeaderInsertion(ContextKind))
return std::nullopt;
if (SemaResult && SemaResult->Declaration) {
auto &SM = SemaResult->Declaration->getASTContext().getSourceManager();
for (const Decl *RD : SemaResult->Declaration->redecls())
if (SM.isInMainFile(SM.getExpansionLoc(RD->getBeginLoc())))
return std::nullopt;
}
Symbol::IncludeDirective Directive = insertionDirective(Opts);
for (const auto &Inc : RankedIncludeHeaders)
if ((Inc.Directive & Directive) != 0)
return Inc.Header;
return std::nullopt;
}
using Bundle = llvm::SmallVector<CompletionCandidate, 4>;
};
using ScoredBundle =
std::pair<CompletionCandidate::Bundle, CodeCompletion::Scores>;
struct ScoredBundleGreater {
bool operator()(const ScoredBundle &L, const ScoredBundle &R) {
if (L.second.Total != R.second.Total)
return L.second.Total > R.second.Total;
return L.first.front().Name <
R.first.front().Name;
}
};
std::string removeFirstTemplateArg(llvm::StringRef Signature) {
auto Rest = Signature.split(",").second;
if (Rest.empty())
return "";
return ("<" + Rest.ltrim()).str();
}
struct CodeCompletionBuilder {
CodeCompletionBuilder(ASTContext *ASTCtx, const CompletionCandidate &C,
CodeCompletionString *SemaCCS,
llvm::ArrayRef<std::string> AccessibleScopes,
const IncludeInserter &Includes,
llvm::StringRef FileName,
CodeCompletionContext::Kind ContextKind,
const CodeCompleteOptions &Opts,
bool IsUsingDeclaration, tok::TokenKind NextTokenKind)
: ASTCtx(ASTCtx),
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets),
IsUsingDeclaration(IsUsingDeclaration), NextTokenKind(NextTokenKind) {
Completion.Deprecated = true;
add(C, SemaCCS, ContextKind);
if (C.SemaResult) {
assert(ASTCtx);
Completion.Origin |= SymbolOrigin::AST;
Completion.Name = std::string(llvm::StringRef(SemaCCS->getTypedText()));
Completion.FilterText = SemaCCS->getAllTypedText();
if (Completion.Scope.empty()) {
if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) ||
(C.SemaResult->Kind == CodeCompletionResult::RK_Pattern))
if (const auto *D = C.SemaResult->getDeclaration())
if (const auto *ND = dyn_cast<NamedDecl>(D))
Completion.Scope = std::string(
splitQualifiedName(printQualifiedName(*ND)).first);
}
Completion.Kind = toCompletionItemKind(*C.SemaResult, ContextKind);
if (Completion.Kind == CompletionItemKind::File &&
Completion.Name.back() == '/')
Completion.Kind = CompletionItemKind::Folder;
for (const auto &FixIt : C.SemaResult->FixIts) {
Completion.FixIts.push_back(toTextEdit(
FixIt, ASTCtx->getSourceManager(), ASTCtx->getLangOpts()));
}
llvm::sort(Completion.FixIts, [](const TextEdit &X, const TextEdit &Y) {
return std::tie(X.range.start.line, X.range.start.character) <
std::tie(Y.range.start.line, Y.range.start.character);
});
}
if (C.IndexResult) {
Completion.Origin |= C.IndexResult->Origin;
if (Completion.Scope.empty())
Completion.Scope = std::string(C.IndexResult->Scope);
if (Completion.Kind == CompletionItemKind::Missing)
Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind,
&C.IndexResult->Signature);
if (Completion.Name.empty())
Completion.Name = std::string(C.IndexResult->Name);
if (Completion.FilterText.empty())
Completion.FilterText = Completion.Name;
if (Completion.RequiredQualifier.empty() && !C.SemaResult) {
llvm::StringRef ShortestQualifier = C.IndexResult->Scope;
for (llvm::StringRef Scope : AccessibleScopes) {
llvm::StringRef Qualifier = C.IndexResult->Scope;
if (Qualifier.consume_front(Scope) &&
Qualifier.size() < ShortestQualifier.size())
ShortestQualifier = Qualifier;
}
Completion.RequiredQualifier = std::string(ShortestQualifier);
}
}
if (C.IdentifierResult) {
Completion.Origin |= SymbolOrigin::Identifier;
Completion.Kind = CompletionItemKind::Text;
Completion.Name = std::string(C.IdentifierResult->Name);
Completion.FilterText = Completion.Name;
}
auto Inserted = [&](llvm::StringRef Header)
-> llvm::Expected<std::pair<std::string, bool>> {
auto ResolvedDeclaring =
URI::resolve(C.IndexResult->CanonicalDeclaration.FileURI, FileName);
if (!ResolvedDeclaring)
return ResolvedDeclaring.takeError();
auto ResolvedInserted = toHeaderFile(Header, FileName);
if (!ResolvedInserted)
return ResolvedInserted.takeError();
auto Spelled = Includes.calculateIncludePath(*ResolvedInserted, FileName);
if (!Spelled)
return error("Header not on include path");
return std::make_pair(
std::move(*Spelled),
Includes.shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted));
};
bool ShouldInsert =
C.headerToInsertIfAllowed(Opts, ContextKind).has_value();
Symbol::IncludeDirective Directive = insertionDirective(Opts);
for (const auto &Inc : C.RankedIncludeHeaders) {
if ((Inc.Directive & Directive) == 0)
continue;
if (auto ToInclude = Inserted(Inc.Header)) {
CodeCompletion::IncludeCandidate Include;
Include.Header = ToInclude->first;
if (ToInclude->second && ShouldInsert)
Include.Insertion = Includes.insert(
ToInclude->first, Directive == Symbol::Import
? tooling::IncludeDirective::Import
: tooling::IncludeDirective::Include);
Completion.Includes.push_back(std::move(Include));
} else
log("Failed to generate include insertion edits for adding header "
"(FileURI='{0}', IncludeHeader='{1}') into {2}: {3}",
C.IndexResult->CanonicalDeclaration.FileURI, Inc.Header, FileName,
ToInclude.takeError());
}
std::stable_partition(Completion.Includes.begin(),
Completion.Includes.end(),
[](const CodeCompletion::IncludeCandidate &I) {
return !I.Insertion.has_value();
});
}
void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS,
CodeCompletionContext::Kind ContextKind) {
assert(bool(C.SemaResult) == bool(SemaCCS));
Bundled.emplace_back();
BundledEntry &S = Bundled.back();
bool IsConcept = false;
if (C.SemaResult) {
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
C.SemaResult->CursorKind,
C.SemaResult->FunctionCanBeCall,
&Completion.RequiredQualifier);
S.ReturnType = getReturnType(*SemaCCS);
if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration)
if (const auto *D = C.SemaResult->getDeclaration())
if (isa<ConceptDecl>(D))
IsConcept = true;
} else if (C.IndexResult) {
S.Signature = std::string(C.IndexResult->Signature);
S.SnippetSuffix = std::string(C.IndexResult->CompletionSnippetSuffix);
S.ReturnType = std::string(C.IndexResult->ReturnType);
if (C.IndexResult->SymInfo.Kind == index::SymbolKind::Concept)
IsConcept = true;
}
if (IsConcept && ContextKind == CodeCompletionContext::CCC_TopLevel) {
S.Signature = removeFirstTemplateArg(S.Signature);
S.SnippetSuffix = removeFirstTemplateArg(S.SnippetSuffix);
}
if (!Completion.Documentation) {
auto SetDoc = [&](llvm::StringRef Doc) {
if (!Doc.empty()) {
Completion.Documentation.emplace();
parseDocumentation(Doc, *Completion.Documentation);
}
};
if (C.IndexResult) {
SetDoc(C.IndexResult->Documentation);
} else if (C.SemaResult) {
const auto DocComment = getDocComment(*ASTCtx, *C.SemaResult,
false);
SetDoc(formatDocumentation(*SemaCCS, DocComment));
}
}
if (Completion.Deprecated) {
if (C.SemaResult)
Completion.Deprecated &=
C.SemaResult->Availability == CXAvailability_Deprecated;
if (C.IndexResult)
Completion.Deprecated &=
bool(C.IndexResult->Flags & Symbol::Deprecated);
}
}
CodeCompletion build() {
Completion.ReturnType = summarizeReturnType();
Completion.Signature = summarizeSignature();
Completion.SnippetSuffix = summarizeSnippet();
Completion.BundleSize = Bundled.size();
return std::move(Completion);
}
private:
struct BundledEntry {
std::string SnippetSuffix;
std::string Signature;
std::string ReturnType;
};
template <std::string BundledEntry::*Member>
const std::string *onlyValue() const {
auto B = Bundled.begin(), E = Bundled.end();
for (auto *I = B + 1; I != E; ++I)
if (I->*Member != B->*Member)
return nullptr;
return &(B->*Member);
}
template <bool BundledEntry::*Member> const bool *onlyValue() const {
auto B = Bundled.begin(), E = Bundled.end();
for (auto *I = B + 1; I != E; ++I)
if (I->*Member != B->*Member)
return nullptr;
return &(B->*Member);
}
std::string summarizeReturnType() const {
if (auto *RT = onlyValue<&BundledEntry::ReturnType>())
return *RT;
return "";
}
std::string summarizeSnippet() const {
if (IsUsingDeclaration)
return "";
auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>();
if (!Snippet)
return "($0)";
if (Snippet->empty())
return "";
bool MayHaveArgList = Completion.Kind == CompletionItemKind::Function ||
Completion.Kind == CompletionItemKind::Method ||
Completion.Kind == CompletionItemKind::Constructor ||
Completion.Kind == CompletionItemKind::Text ;
if (MayHaveArgList) {
if (NextTokenKind == tok::less && Snippet->front() == '<')
return "";
if (NextTokenKind == tok::l_paren) {
if (Snippet->front() == '<') {
int Balance = 0;
size_t I = 0;
do {
if (Snippet->at(I) == '>')
--Balance;
else if (Snippet->at(I) == '<')
++Balance;
++I;
} while (Balance > 0);
return Snippet->substr(0, I);
}
return "";
}
}
if (EnableFunctionArgSnippets)
return *Snippet;
if (MayHaveArgList) {
bool EmptyArgs = llvm::StringRef(*Snippet).ends_with("()");
if (Snippet->front() == '<')
return EmptyArgs ? "<$1>()$0" : "<$1>($0)";
if (Snippet->front() == '(')
return EmptyArgs ? "()" : "($0)";
return *Snippet;
}
if (Completion.Kind == CompletionItemKind::Interface ||
Completion.Kind == CompletionItemKind::Class ||
Completion.Kind == CompletionItemKind::Variable) {
if (Snippet->front() != '<')
return *Snippet;
if (llvm::StringRef(*Snippet).ends_with("<>"))
return "<>";
return "<$0>";
}
return *Snippet;
}
std::string summarizeSignature() const {
if (auto *Signature = onlyValue<&BundledEntry::Signature>())
return *Signature;
return "(…)";
}
ASTContext *ASTCtx;
CodeCompletion Completion;
llvm::SmallVector<BundledEntry, 1> Bundled;
bool EnableFunctionArgSnippets;
bool IsUsingDeclaration;
tok::TokenKind NextTokenKind;
};
SymbolID getSymbolID(const CodeCompletionResult &R, const SourceManager &SM) {
switch (R.Kind) {
case CodeCompletionResult::RK_Declaration:
case CodeCompletionResult::RK_Pattern: {
if (hasUnstableLinkage(R.Declaration))
return {};
return clang::clangd::getSymbolID(R.Declaration);
}
case CodeCompletionResult::RK_Macro:
return clang::clangd::getSymbolID(R.Macro->getName(), R.MacroDefInfo, SM);
case CodeCompletionResult::RK_Keyword:
return {};
}
llvm_unreachable("unknown CodeCompletionResult kind");
}
struct SpecifiedScope {
std::vector<std::string> AccessibleScopes;
std::vector<std::string> QueryScopes;
std::optional<std::string> UnresolvedQualifier;
std::optional<std::string> EnclosingNamespace;
bool AllowAllScopes = false;
std::vector<std::string> scopesForQualification() {
std::set<std::string> Results;
for (llvm::StringRef AS : AccessibleScopes)
Results.insert(
(AS + (UnresolvedQualifier ? *UnresolvedQualifier : "")).str());
return {Results.begin(), Results.end()};
}
std::vector<std::string> scopesForIndexQuery() {
std::vector<std::string> EnclosingAtFront;
if (EnclosingNamespace.has_value())
EnclosingAtFront.push_back(*EnclosingNamespace);
std::set<std::string> Deduplicated;
for (llvm::StringRef S : QueryScopes)
if (S != EnclosingNamespace)
Deduplicated.insert((S + UnresolvedQualifier.value_or("")).str());
EnclosingAtFront.reserve(EnclosingAtFront.size() + Deduplicated.size());
llvm::copy(Deduplicated, std::back_inserter(EnclosingAtFront));
return EnclosingAtFront;
}
};
SpecifiedScope getQueryScopes(CodeCompletionContext &CCContext,
const Sema &CCSema,
const CompletionPrefix &HeuristicPrefix,
const CodeCompleteOptions &Opts) {
SpecifiedScope Scopes;
for (auto *Context : CCContext.getVisitedContexts()) {
if (isa<TranslationUnitDecl>(Context)) {
Scopes.QueryScopes.push_back("");
Scopes.AccessibleScopes.push_back("");
} else if (const auto *ND = dyn_cast<NamespaceDecl>(Context)) {
Scopes.QueryScopes.push_back(printNamespaceScope(*Context));
Scopes.AccessibleScopes.push_back(printQualifiedName(*ND) + "::");
}
}
const CXXScopeSpec *SemaSpecifier =
CCContext.getCXXScopeSpecifier().value_or(nullptr);
if (!SemaSpecifier) {
if (!HeuristicPrefix.Qualifier.empty()) {
vlog("Sema said no scope specifier, but we saw {0} in the source code",
HeuristicPrefix.Qualifier);
StringRef SpelledSpecifier = HeuristicPrefix.Qualifier;
if (SpelledSpecifier.consume_front("::")) {
Scopes.AccessibleScopes = {""};
Scopes.QueryScopes = {""};
}
Scopes.UnresolvedQualifier = std::string(SpelledSpecifier);
return Scopes;
}
Scopes.EnclosingNamespace = printNamespaceScope(*CCSema.CurContext);
Scopes.AllowAllScopes = Opts.AllScopes;
return Scopes;
}
if (SemaSpecifier && SemaSpecifier->isValid())
return Scopes;
Scopes.QueryScopes.push_back("");
llvm::StringRef SpelledSpecifier = Lexer::getSourceText(
CharSourceRange::getCharRange(SemaSpecifier->getRange()),
CCSema.SourceMgr, clang::LangOptions());
if (SpelledSpecifier.consume_front("::"))
Scopes.QueryScopes = {""};
Scopes.UnresolvedQualifier = std::string(SpelledSpecifier);
if (!Scopes.UnresolvedQualifier->empty())
*Scopes.UnresolvedQualifier += "::";
Scopes.AccessibleScopes = Scopes.QueryScopes;
return Scopes;
}
bool contextAllowsIndex(enum CodeCompletionContext::Kind K) {
switch (K) {
case CodeCompletionContext::CCC_TopLevel:
case CodeCompletionContext::CCC_ObjCInterface:
case CodeCompletionContext::CCC_ObjCImplementation:
case CodeCompletionContext::CCC_ObjCIvarList:
case CodeCompletionContext::CCC_ClassStructUnion:
case CodeCompletionContext::CCC_Statement:
case CodeCompletionContext::CCC_Expression:
case CodeCompletionContext::CCC_ObjCMessageReceiver:
case CodeCompletionContext::CCC_EnumTag:
case CodeCompletionContext::CCC_UnionTag:
case CodeCompletionContext::CCC_ClassOrStructTag:
case CodeCompletionContext::CCC_ObjCProtocolName:
case CodeCompletionContext::CCC_Namespace:
case CodeCompletionContext::CCC_Type:
case CodeCompletionContext::CCC_ParenthesizedExpression:
case CodeCompletionContext::CCC_ObjCInterfaceName:
case CodeCompletionContext::CCC_Symbol:
case CodeCompletionContext::CCC_SymbolOrNewName:
case CodeCompletionContext::CCC_ObjCClassForwardDecl:
case CodeCompletionContext::CCC_TopLevelOrExpression:
return true;
case CodeCompletionContext::CCC_OtherWithMacros:
case CodeCompletionContext::CCC_DotMemberAccess:
case CodeCompletionContext::CCC_ArrowMemberAccess:
case CodeCompletionContext::CCC_ObjCCategoryName:
case CodeCompletionContext::CCC_ObjCPropertyAccess:
case CodeCompletionContext::CCC_MacroName:
case CodeCompletionContext::CCC_MacroNameUse:
case CodeCompletionContext::CCC_PreprocessorExpression:
case CodeCompletionContext::CCC_PreprocessorDirective:
case CodeCompletionContext::CCC_SelectorName:
case CodeCompletionContext::CCC_TypeQualifiers:
case CodeCompletionContext::CCC_ObjCInstanceMessage:
case CodeCompletionContext::CCC_ObjCClassMessage:
case CodeCompletionContext::CCC_IncludedFile:
case CodeCompletionContext::CCC_Attribute:
case CodeCompletionContext::CCC_Other:
case CodeCompletionContext::CCC_NaturalLanguage:
case CodeCompletionContext::CCC_Recovery:
case CodeCompletionContext::CCC_NewName:
return false;
}
llvm_unreachable("unknown code completion context");
}
static bool isInjectedClass(const NamedDecl &D) {
if (auto *R = dyn_cast_or_null<RecordDecl>(&D))
if (R->isInjectedClassName())
return true;
return false;
}
static bool isExcludedMember(const NamedDecl &D) {
if (D.getKind() == Decl::CXXDestructor)
return true;
if (isInjectedClass(D))
return true;
auto NameKind = D.getDeclName().getNameKind();
if (NameKind == DeclarationName::CXXOperatorName ||
NameKind == DeclarationName::CXXLiteralOperatorName ||
NameKind == DeclarationName::CXXConversionFunctionName)
return true;
return false;
}
struct CompletionRecorder : public CodeCompleteConsumer {
CompletionRecorder(const CodeCompleteOptions &Opts,
llvm::unique_function<void()> ResultsCallback)
: CodeCompleteConsumer(Opts.getClangCompleteOpts()),
CCContext(CodeCompletionContext::CCC_Other), Opts(Opts),
CCAllocator(std::make_shared<GlobalCodeCompletionAllocator>()),
CCTUInfo(CCAllocator), ResultsCallback(std::move(ResultsCallback)) {
assert(this->ResultsCallback);
}
std::vector<CodeCompletionResult> Results;
CodeCompletionContext CCContext;
Sema *CCSema = nullptr;
void ProcessCodeCompleteResults(class Sema &S, CodeCompletionContext Context,
CodeCompletionResult *InResults,
unsigned NumResults) final {
if (Context.getKind() == CodeCompletionContext::CCC_Recovery) {
log("Code complete: Ignoring sema code complete callback with Recovery "
"context.");
return;
}
if (NumResults == 0 && !contextAllowsIndex(Context.getKind()))
return;
if (CCSema) {
log("Multiple code complete callbacks (parser backtracked?). "
"Dropping results from context {0}, keeping results from {1}.",
getCompletionKindString(Context.getKind()),
getCompletionKindString(this->CCContext.getKind()));
return;
}
CCSema = &S;
CCContext = Context;
for (unsigned I = 0; I < NumResults; ++I) {
auto &Result = InResults[I];
if (Result.Hidden && Result.Declaration &&
Result.Declaration->isCXXClassMember())
continue;
if (!Opts.IncludeIneligibleResults &&
(Result.Availability == CXAvailability_NotAvailable ||
Result.Availability == CXAvailability_NotAccessible))
continue;
if (Result.Declaration &&
!Context.getBaseType().isNull()
&& isExcludedMember(*Result.Declaration))
continue;
if (Result.Declaration && !Context.getCXXScopeSpecifier() &&
isInjectedClass(*Result.Declaration))
continue;
Result.StartsNestedNameSpecifier = false;
Results.push_back(Result);
}
ResultsCallback();
}
CodeCompletionAllocator &getAllocator() override { return *CCAllocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
llvm::StringRef getName(const CodeCompletionResult &Result) {
switch (Result.Kind) {
case CodeCompletionResult::RK_Declaration:
if (auto *ID = Result.Declaration->getIdentifier())
return ID->getName();
break;
case CodeCompletionResult::RK_Keyword:
return Result.Keyword;
case CodeCompletionResult::RK_Macro:
return Result.Macro->getName();
case CodeCompletionResult::RK_Pattern:
break;
}
auto *CCS = codeCompletionString(Result);
const CodeCompletionString::Chunk *OnlyText = nullptr;
for (auto &C : *CCS) {
if (C.Kind != CodeCompletionString::CK_TypedText)
continue;
if (OnlyText)
return CCAllocator->CopyString(CCS->getAllTypedText());
OnlyText = &C;
}
return OnlyText ? OnlyText->Text : llvm::StringRef();
}
CodeCompletionString *codeCompletionString(const CodeCompletionResult &R) {
return const_cast<CodeCompletionResult &>(R).CreateCodeCompletionString(
*CCSema, CCContext, *CCAllocator, CCTUInfo,
false);
}
private:
CodeCompleteOptions Opts;
std::shared_ptr<GlobalCodeCompletionAllocator> CCAllocator;
CodeCompletionTUInfo CCTUInfo;
llvm::unique_function<void()> ResultsCallback;
};
struct ScoredSignature {
SymbolID IDForDoc;
SignatureInformation Signature;
SignatureQualitySignals Quality;
};
int paramIndexForArg(const CodeCompleteConsumer::OverloadCandidate &Candidate,
int Arg) {
int NumParams = Candidate.getNumParams();
if (auto *T = Candidate.getFunctionType()) {
if (auto *Proto = T->getAs<FunctionProtoType>()) {
if (Proto->isVariadic())
++NumParams;
}
}
return std::min(Arg, std::max(NumParams - 1, 0));
}
class SignatureHelpCollector final : public CodeCompleteConsumer {
public:
SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
MarkupKind DocumentationFormat,
const SymbolIndex *Index, SignatureHelp &SigHelp)
: CodeCompleteConsumer(CodeCompleteOpts), SigHelp(SigHelp),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator), Index(Index),
DocumentationFormat(DocumentationFormat) {}
void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
OverloadCandidate *Candidates,
unsigned NumCandidates,
SourceLocation OpenParLoc,
bool Braced) override {
assert(!OpenParLoc.isInvalid());
SourceManager &SrcMgr = S.getSourceManager();
OpenParLoc = SrcMgr.getFileLoc(OpenParLoc);
if (SrcMgr.isInMainFile(OpenParLoc))
SigHelp.argListStart = sourceLocToPosition(SrcMgr, OpenParLoc);
else
elog("Location oustide main file in signature help: {0}",
OpenParLoc.printToString(SrcMgr));
std::vector<ScoredSignature> ScoredSignatures;
SigHelp.signatures.reserve(NumCandidates);
ScoredSignatures.reserve(NumCandidates);
SigHelp.activeSignature = 0;
assert(CurrentArg <= (unsigned)std::numeric_limits<int>::max() &&
"too many arguments");
SigHelp.activeParameter = static_cast<int>(CurrentArg);
for (unsigned I = 0; I < NumCandidates; ++I) {
OverloadCandidate Candidate = Candidates[I];
if (auto *Func = Candidate.getFunction()) {
if (auto *Pattern = Func->getTemplateInstantiationPattern())
Candidate = OverloadCandidate(Pattern);
}
if (static_cast<int>(I) == SigHelp.activeSignature) {
SigHelp.activeParameter =
paramIndexForArg(Candidate, SigHelp.activeParameter);
}
const auto *CCS = Candidate.CreateSignatureString(
CurrentArg, S, *Allocator, CCTUInfo,
true, Braced);
assert(CCS && "Expected the CodeCompletionString to be non-null");
ScoredSignatures.push_back(processOverloadCandidate(
Candidate, *CCS,
Candidate.getFunction()
? getDeclComment(S.getASTContext(), *Candidate.getFunction())
: ""));
}
llvm::DenseMap<SymbolID, std::string> FetchedDocs;
if (Index) {
LookupRequest IndexRequest;
for (const auto &S : ScoredSignatures) {
if (!S.IDForDoc)
continue;
IndexRequest.IDs.insert(S.IDForDoc);
}
Index->lookup(IndexRequest, [&](const Symbol &S) {
if (!S.Documentation.empty())
FetchedDocs[S.ID] = std::string(S.Documentation);
});
vlog("SigHelp: requested docs for {0} symbols from the index, got {1} "
"symbols with non-empty docs in the response",
IndexRequest.IDs.size(), FetchedDocs.size());
}
llvm::sort(ScoredSignatures, [](const ScoredSignature &L,
const ScoredSignature &R) {
if (L.Quality.NumberOfParameters != R.Quality.NumberOfParameters)
return L.Quality.NumberOfParameters < R.Quality.NumberOfParameters;
if (L.Quality.NumberOfOptionalParameters !=
R.Quality.NumberOfOptionalParameters)
return L.Quality.NumberOfOptionalParameters <
R.Quality.NumberOfOptionalParameters;
if (L.Quality.Kind != R.Quality.Kind) {
using OC = CodeCompleteConsumer::OverloadCandidate;
auto KindPriority = [&](OC::CandidateKind K) {
switch (K) {
case OC::CK_Aggregate:
return 0;
case OC::CK_Function:
return 1;
case OC::CK_FunctionType:
return 2;
case OC::CK_FunctionProtoTypeLoc:
return 3;
case OC::CK_FunctionTemplate:
return 4;
case OC::CK_Template:
return 5;
}
llvm_unreachable("Unknown overload candidate type.");
};
return KindPriority(L.Quality.Kind) < KindPriority(R.Quality.Kind);
}
if (L.Signature.label.size() != R.Signature.label.size())
return L.Signature.label.size() < R.Signature.label.size();
return L.Signature.label < R.Signature.label;
});
for (auto &SS : ScoredSignatures) {
auto IndexDocIt =
SS.IDForDoc ? FetchedDocs.find(SS.IDForDoc) : FetchedDocs.end();
if (IndexDocIt != FetchedDocs.end()) {
markup::Document SignatureComment;
parseDocumentation(IndexDocIt->second, SignatureComment);
SS.Signature.documentation =
renderDoc(SignatureComment, DocumentationFormat);
}
SigHelp.signatures.push_back(std::move(SS.Signature));
}
}
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
private:
void processParameterChunk(llvm::StringRef ChunkText,
SignatureInformation &Signature) const {
unsigned ParamStartOffset = lspLength(Signature.label);
unsigned ParamEndOffset = ParamStartOffset + lspLength(ChunkText);
Signature.label += ChunkText;
ParameterInformation Info;
Info.labelOffsets.emplace(ParamStartOffset, ParamEndOffset);
Info.labelString = std::string(ChunkText);
Signature.parameters.push_back(std::move(Info));
}
void processOptionalChunk(const CodeCompletionString &CCS,
SignatureInformation &Signature,
SignatureQualitySignals &Signal) const {
for (const auto &Chunk : CCS) {
switch (Chunk.Kind) {
case CodeCompletionString::CK_Optional:
assert(Chunk.Optional &&
"Expected the optional code completion string to be non-null.");
processOptionalChunk(*Chunk.Optional, Signature, Signal);
break;
case CodeCompletionString::CK_VerticalSpace:
break;
case CodeCompletionString::CK_CurrentParameter:
case CodeCompletionString::CK_Placeholder:
processParameterChunk(Chunk.Text, Signature);
Signal.NumberOfOptionalParameters++;
break;
default:
Signature.label += Chunk.Text;
break;
}
}
}
ScoredSignature processOverloadCandidate(const OverloadCandidate &Candidate,
const CodeCompletionString &CCS,
llvm::StringRef DocComment) const {
SignatureInformation Signature;
SignatureQualitySignals Signal;
const char *ReturnType = nullptr;
markup::Document OverloadComment;
parseDocumentation(formatDocumentation(CCS, DocComment), OverloadComment);
Signature.documentation = renderDoc(OverloadComment, DocumentationFormat);
Signal.Kind = Candidate.getKind();
for (const auto &Chunk : CCS) {
switch (Chunk.Kind) {
case CodeCompletionString::CK_ResultType:
assert(!ReturnType && "Unexpected CK_ResultType");
ReturnType = Chunk.Text;
break;
case CodeCompletionString::CK_CurrentParameter:
case CodeCompletionString::CK_Placeholder:
processParameterChunk(Chunk.Text, Signature);
Signal.NumberOfParameters++;
break;
case CodeCompletionString::CK_Optional: {
assert(Chunk.Optional &&
"Expected the optional code completion string to be non-null.");
processOptionalChunk(*Chunk.Optional, Signature, Signal);
break;
}
case CodeCompletionString::CK_VerticalSpace:
break;
default:
Signature.label += Chunk.Text;
break;
}
}
if (ReturnType) {
Signature.label += " -> ";
Signature.label += ReturnType;
}
dlog("Signal for {0}: {1}", Signature, Signal);
ScoredSignature Result;
Result.Signature = std::move(Signature);
Result.Quality = Signal;
const FunctionDecl *Func = Candidate.getFunction();
if (Func && Result.Signature.documentation.value.empty()) {
if (!hasUnstableLinkage(Func))
Result.IDForDoc = clangd::getSymbolID(Func);
}
return Result;
}
SignatureHelp &SigHelp;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
const SymbolIndex *Index;
MarkupKind DocumentationFormat;
};
class ParamNameCollector final : public CodeCompleteConsumer {
public:
ParamNameCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
std::set<std::string> &ParamNames)
: CodeCompleteConsumer(CodeCompleteOpts),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator), ParamNames(ParamNames) {}
void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
OverloadCandidate *Candidates,
unsigned NumCandidates,
SourceLocation OpenParLoc,
bool Braced) override {
assert(CurrentArg <= (unsigned)std::numeric_limits<int>::max() &&
"too many arguments");
for (unsigned I = 0; I < NumCandidates; ++I) {
if (const NamedDecl *ND = Candidates[I].getParamDecl(CurrentArg))
if (const auto *II = ND->getIdentifier())
ParamNames.emplace(II->getName());
}
}
private:
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
std::set<std::string> &ParamNames;
};
struct SemaCompleteInput {
PathRef FileName;
size_t Offset;
const PreambleData &Preamble;
const std::optional<PreamblePatch> Patch;
const ParseInputs &ParseInput;
};
void loadMainFilePreambleMacros(const Preprocessor &PP,
const PreambleData &Preamble) {
ExternalPreprocessorSource *PreambleMacros = PP.getExternalSource();
const auto &ITable = PP.getIdentifierTable();
IdentifierInfoLookup *PreambleIdentifiers =
ITable.getExternalIdentifierLookup();
if (!PreambleIdentifiers || !PreambleMacros)
return;
for (const auto &MacroName : Preamble.Macros.Names) {
if (ITable.find(MacroName.getKey()) != ITable.end())
continue;
if (auto *II = PreambleIdentifiers->get(MacroName.getKey()))
if (II->isOutOfDate())
PreambleMacros->updateOutOfDateIdentifier(*II);
}
}
bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
const clang::CodeCompleteOptions &Options,
const SemaCompleteInput &Input,
IncludeStructure *Includes = nullptr) {
trace::Span Tracer("Sema completion");
IgnoreDiagnostics IgnoreDiags;
auto CI = buildCompilerInvocation(Input.ParseInput, IgnoreDiags);
if (!CI) {
elog("Couldn't create CompilerInvocation");
return false;
}
auto &FrontendOpts = CI->getFrontendOpts();
FrontendOpts.SkipFunctionBodies = true;
CI->getLangOpts().SpellChecking = false;
CI->getLangOpts().DelayedTemplateParsing = false;
FrontendOpts.CodeCompleteOpts = Options;
FrontendOpts.CodeCompletionAt.FileName = std::string(Input.FileName);
std::tie(FrontendOpts.CodeCompletionAt.Line,
FrontendOpts.CodeCompletionAt.Column) =
offsetToClangLineColumn(Input.ParseInput.Contents, Input.Offset);
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
llvm::MemoryBuffer::getMemBuffer(Input.ParseInput.Contents,
Input.FileName);
CI->getDiagnosticOpts().IgnoreWarnings = true;
PreambleBounds PreambleRegion =
ComputePreambleBounds(CI->getLangOpts(), *ContentsBuffer, 0);
bool CompletingInPreamble = Input.Offset < PreambleRegion.Size ||
(!PreambleRegion.PreambleEndsAtStartOfLine &&
Input.Offset == PreambleRegion.Size);
if (Input.Patch)
Input.Patch->apply(*CI);
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS =
Input.ParseInput.TFS->view(Input.ParseInput.CompileCommand.Directory);
if (Input.Preamble.StatCache)
VFS = Input.Preamble.StatCache->getConsumingFS(std::move(VFS));
auto Clang = prepareCompilerInstance(
std::move(CI), !CompletingInPreamble ? &Input.Preamble.Preamble : nullptr,
std::move(ContentsBuffer), std::move(VFS), IgnoreDiags);
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());
SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
log("BeginSourceFile() failed when running codeComplete for {0}",
Input.FileName);
return false;
}
loadMainFilePreambleMacros(Clang->getPreprocessor(), Input.Preamble);
if (Includes)
Includes->collect(*Clang);
if (llvm::Error Err = Action.Execute()) {
log("Execute() failed when running codeComplete for {0}: {1}",
Input.FileName, toString(std::move(Err)));
return false;
}
Action.EndSourceFile();
return true;
}
bool allowIndex(CodeCompletionContext &CC) {
if (!contextAllowsIndex(CC.getKind()))
return false;
auto Scope = CC.getCXXScopeSpecifier();
if (!Scope)
return true;
NestedNameSpecifier *NameSpec = (*Scope)->getScopeRep();
if (!NameSpec)
return true;
switch (NameSpec->getKind()) {
case NestedNameSpecifier::Global:
case NestedNameSpecifier::Namespace:
case NestedNameSpecifier::NamespaceAlias:
return true;
case NestedNameSpecifier::Super:
case NestedNameSpecifier::TypeSpec:
case NestedNameSpecifier::TypeSpecWithTemplate:
case NestedNameSpecifier::Identifier:
return false;
}
llvm_unreachable("invalid NestedNameSpecifier kind");
}
bool includeSymbolFromIndex(CodeCompletionContext::Kind Kind,
const Symbol &Sym) {
if (Sym.SymInfo.Kind == index::SymbolKind::Protocol &&
Sym.SymInfo.Lang == index::SymbolLanguage::ObjC)
return Kind == CodeCompletionContext::CCC_ObjCProtocolName;
else if (Kind == CodeCompletionContext::CCC_ObjCProtocolName)
return false;
if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl)
return Sym.SymInfo.Kind == index::SymbolKind::Class &&
Sym.SymInfo.Lang == index::SymbolLanguage::ObjC;
return true;
}
std::future<std::pair<bool, SymbolSlab>>
startAsyncFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req) {
return runAsync<std::pair<bool, SymbolSlab>>([&Index, Req]() {
trace::Span Tracer("Async fuzzyFind");
SymbolSlab::Builder Syms;
bool Incomplete =
Index.fuzzyFind(Req, [&Syms](const Symbol &Sym) { Syms.insert(Sym); });
return std::make_pair(Incomplete, std::move(Syms).build());
});
}
FuzzyFindRequest speculativeFuzzyFindRequestForCompletion(
FuzzyFindRequest CachedReq, const CompletionPrefix &HeuristicPrefix) {
CachedReq.Query = std::string(HeuristicPrefix.Name);
return CachedReq;
}
std::optional<Token>
findTokenAfterCompletionPoint(SourceLocation CompletionPoint,
const SourceManager &SM,
const LangOptions &LangOpts) {
SourceLocation Loc = CompletionPoint;
if (Loc.isMacroID()) {
if (!Lexer::isAtEndOfMacroExpansion(Loc, SM, LangOpts, &Loc))
return std::nullopt;
}
Loc = Loc.getLocWithOffset(1);
std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
bool InvalidTemp = false;
StringRef File = SM.getBufferData(LocInfo.first, &InvalidTemp);
if (InvalidTemp)
return std::nullopt;
const char *TokenBegin = File.data() + LocInfo.second;
Lexer TheLexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts, File.begin(),
TokenBegin, File.end());
Token Tok;
TheLexer.LexFromRawLexer(Tok);
return Tok;
}
class CodeCompleteFlow {
PathRef FileName;
IncludeStructure Includes;
SpeculativeFuzzyFind *SpecFuzzyFind;
const CodeCompleteOptions &Opts;
CompletionRecorder *Recorder = nullptr;
CodeCompletionContext::Kind CCContextKind = CodeCompletionContext::CCC_Other;
bool IsUsingDeclaration = false;
tok::TokenKind NextTokenKind = tok::eof;
int NSema = 0, NIndex = 0, NSemaAndIndex = 0, NIdent = 0;
bool Incomplete = false;
CompletionPrefix HeuristicPrefix;
std::optional<FuzzyMatcher> Filter;
Range ReplacedRange;
std::vector<std::string> QueryScopes;
std::vector<std::string> AccessibleScopes;
std::optional<ScopeDistance> ScopeProximity;
std::optional<OpaqueType> PreferredType;
bool AllScopes = false;
llvm::StringSet<> ContextWords;
std::optional<IncludeInserter> Inserter;
std::optional<URIDistance> FileProximity;
std::optional<FuzzyFindRequest> SpecReq;
public:
CodeCompleteFlow(PathRef FileName, const IncludeStructure &Includes,
SpeculativeFuzzyFind *SpecFuzzyFind,
const CodeCompleteOptions &Opts)
: FileName(FileName), Includes(Includes), SpecFuzzyFind(SpecFuzzyFind),
Opts(Opts) {}
CodeCompleteResult run(const SemaCompleteInput &SemaCCInput) && {
trace::Span Tracer("CodeCompleteFlow");
HeuristicPrefix = guessCompletionPrefix(SemaCCInput.ParseInput.Contents,
SemaCCInput.Offset);
populateContextWords(SemaCCInput.ParseInput.Contents);
if (Opts.Index && SpecFuzzyFind && SpecFuzzyFind->CachedReq) {
assert(!SpecFuzzyFind->Result.valid());
SpecReq = speculativeFuzzyFindRequestForCompletion(
*SpecFuzzyFind->CachedReq, HeuristicPrefix);
SpecFuzzyFind->Result = startAsyncFuzzyFind(*Opts.Index, *SpecReq);
}
CodeCompleteResult Output;
auto RecorderOwner = std::make_unique<CompletionRecorder>(Opts, [&]() {
assert(Recorder && "Recorder is not set");
CCContextKind = Recorder->CCContext.getKind();
IsUsingDeclaration = Recorder->CCContext.isUsingDeclaration();
auto Style = getFormatStyleForFile(SemaCCInput.FileName,
SemaCCInput.ParseInput.Contents,
*SemaCCInput.ParseInput.TFS, false);
const auto NextToken = findTokenAfterCompletionPoint(
Recorder->CCSema->getPreprocessor().getCodeCompletionLoc(),
Recorder->CCSema->getSourceManager(), Recorder->CCSema->LangOpts);
if (NextToken)
NextTokenKind = NextToken->getKind();
Inserter.emplace(
SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, Style,
SemaCCInput.ParseInput.CompileCommand.Directory,
&Recorder->CCSema->getPreprocessor().getHeaderSearchInfo());
for (const auto &Inc : Includes.MainFileIncludes)
Inserter->addExisting(Inc);
FileDistanceOptions ProxOpts{};
const auto &SM = Recorder->CCSema->getSourceManager();
llvm::StringMap<SourceParams> ProxSources;
auto MainFileID =
Includes.getID(SM.getFileEntryForID(SM.getMainFileID()));
assert(MainFileID);
for (auto &HeaderIDAndDepth : Includes.includeDepth(*MainFileID)) {
auto &Source =
ProxSources[Includes.getRealPath(HeaderIDAndDepth.getFirst())];
Source.Cost = HeaderIDAndDepth.getSecond() * ProxOpts.IncludeCost;
if (HeaderIDAndDepth.getSecond() > 0)
Source.MaxUpTraversals = 1;
}
FileProximity.emplace(ProxSources, ProxOpts);
Output = runWithSema();
Inserter.reset();
SPAN_ATTACH(Tracer, "sema_completion_kind",
getCompletionKindString(CCContextKind));
log("Code complete: sema context {0}, query scopes [{1}] (AnyScope={2}), "
"expected type {3}{4}",
getCompletionKindString(CCContextKind),
llvm::join(QueryScopes.begin(), QueryScopes.end(), ","), AllScopes,
PreferredType ? Recorder->CCContext.getPreferredType().getAsString()
: "<none>",
IsUsingDeclaration ? ", inside using declaration" : "");
});
Recorder = RecorderOwner.get();
semaCodeComplete(std::move(RecorderOwner), Opts.getClangCompleteOpts(),
SemaCCInput, &Includes);
logResults(Output, Tracer);
return Output;
}
void logResults(const CodeCompleteResult &Output, const trace::Span &Tracer) {
SPAN_ATTACH(Tracer, "sema_results", NSema);
SPAN_ATTACH(Tracer, "index_results", NIndex);
SPAN_ATTACH(Tracer, "merged_results", NSemaAndIndex);
SPAN_ATTACH(Tracer, "identifier_results", NIdent);
SPAN_ATTACH(Tracer, "returned_results", int64_t(Output.Completions.size()));
SPAN_ATTACH(Tracer, "incomplete", Output.HasMore);
log("Code complete: {0} results from Sema, {1} from Index, "
"{2} matched, {3} from identifiers, {4} returned{5}.",
NSema, NIndex, NSemaAndIndex, NIdent, Output.Completions.size(),
Output.HasMore ? " (incomplete)" : "");
assert(!Opts.Limit || Output.Completions.size() <= Opts.Limit);
}
CodeCompleteResult runWithoutSema(llvm::StringRef Content, size_t Offset,
const ThreadsafeFS &TFS) && {
trace::Span Tracer("CodeCompleteWithoutSema");
HeuristicPrefix = guessCompletionPrefix(Content, Offset);
populateContextWords(Content);
CCContextKind = CodeCompletionContext::CCC_Recovery;
IsUsingDeclaration = false;
Filter = FuzzyMatcher(HeuristicPrefix.Name);
auto Pos = offsetToPosition(Content, Offset);
ReplacedRange.start = ReplacedRange.end = Pos;
ReplacedRange.start.character -= HeuristicPrefix.Name.size();
llvm::StringMap<SourceParams> ProxSources;
ProxSources[FileName].Cost = 0;
FileProximity.emplace(ProxSources);
auto Style = getFormatStyleForFile(FileName, Content, TFS, false);
Inserter.emplace(FileName, Content, Style,
"", nullptr);
auto Identifiers = collectIdentifiers(Content, Style);
std::vector<RawIdentifier> IdentifierResults;
for (const auto &IDAndCount : Identifiers) {
RawIdentifier ID;
ID.Name = IDAndCount.first();
ID.References = IDAndCount.second;
if (ID.Name == HeuristicPrefix.Name)
--ID.References;
if (ID.References > 0)
IdentifierResults.push_back(std::move(ID));
}
SpecifiedScope Scopes;
Scopes.QueryScopes = visibleNamespaces(
Content.take_front(Offset), format::getFormattingLangOpts(Style));
for (std::string &S : Scopes.QueryScopes)
if (!S.empty())
S.append("::");
if (HeuristicPrefix.Qualifier.empty())
AllScopes = Opts.AllScopes;
else if (HeuristicPrefix.Qualifier.starts_with("::")) {
Scopes.QueryScopes = {""};
Scopes.UnresolvedQualifier =
std::string(HeuristicPrefix.Qualifier.drop_front(2));
} else
Scopes.UnresolvedQualifier = std::string(HeuristicPrefix.Qualifier);
QueryScopes = Scopes.scopesForIndexQuery();
AccessibleScopes = QueryScopes;
ScopeProximity.emplace(QueryScopes);
SymbolSlab IndexResults = Opts.Index ? queryIndex() : SymbolSlab();
CodeCompleteResult Output = toCodeCompleteResult(mergeResults(
{}, IndexResults, IdentifierResults));
Output.RanParser = false;
logResults(Output, Tracer);
return Output;
}
private:
void populateContextWords(llvm::StringRef Content) {
unsigned RangeEnd = HeuristicPrefix.Qualifier.begin() - Content.data(),
RangeBegin = RangeEnd;
for (size_t I = 0; I < 3 && RangeBegin > 0; ++I) {
auto PrevNL = Content.rfind('\n', RangeBegin);
if (PrevNL == StringRef::npos) {
RangeBegin = 0;
break;
}
RangeBegin = PrevNL;
}
ContextWords = collectWords(Content.slice(RangeBegin, RangeEnd));
dlog("Completion context words: {0}",
llvm::join(ContextWords.keys(), ", "));
}
CodeCompleteResult runWithSema() {
const auto &CodeCompletionRange = CharSourceRange::getCharRange(
Recorder->CCSema->getPreprocessor().getCodeCompletionTokenRange());
if (CodeCompletionRange.isValid()) {
ReplacedRange = halfOpenToRange(Recorder->CCSema->getSourceManager(),
CodeCompletionRange);
} else {
const auto &Pos = sourceLocToPosition(
Recorder->CCSema->getSourceManager(),
Recorder->CCSema->getPreprocessor().getCodeCompletionLoc());
ReplacedRange.start = ReplacedRange.end = Pos;
}
Filter = FuzzyMatcher(
Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
auto SpecifiedScopes = getQueryScopes(
Recorder->CCContext, *Recorder->CCSema, HeuristicPrefix, Opts);
QueryScopes = SpecifiedScopes.scopesForIndexQuery();
AccessibleScopes = SpecifiedScopes.scopesForQualification();
AllScopes = SpecifiedScopes.AllowAllScopes;
if (!QueryScopes.empty())
ScopeProximity.emplace(QueryScopes);
PreferredType =
OpaqueType::fromType(Recorder->CCSema->getASTContext(),
Recorder->CCContext.getPreferredType());
auto IndexResults = (Opts.Index && allowIndex(Recorder->CCContext))
? queryIndex()
: SymbolSlab();
trace::Span Tracer("Populate CodeCompleteResult");
auto Top =
mergeResults(Recorder->Results, IndexResults, {});
return toCodeCompleteResult(Top);
}
CodeCompleteResult
toCodeCompleteResult(const std::vector<ScoredBundle> &Scored) {
CodeCompleteResult Output;
for (auto &C : Scored) {
Output.Completions.push_back(toCodeCompletion(C.first));
Output.Completions.back().Score = C.second;
Output.Completions.back().CompletionTokenRange = ReplacedRange;
}
Output.HasMore = Incomplete;
Output.Context = CCContextKind;
Output.CompletionRange = ReplacedRange;
return Output;
}
SymbolSlab queryIndex() {
trace::Span Tracer("Query index");
SPAN_ATTACH(Tracer, "limit", int64_t(Opts.Limit));
FuzzyFindRequest Req;
if (Opts.Limit)
Req.Limit = Opts.Limit;
Req.Query = std::string(Filter->pattern());
Req.RestrictForCodeCompletion = true;
Req.Scopes = QueryScopes;
Req.AnyScope = AllScopes;
Req.ProximityPaths.push_back(std::string(FileName));
if (PreferredType)
Req.PreferredTypes.push_back(std::string(PreferredType->raw()));
vlog("Code complete: fuzzyFind({0:2})", toJSON(Req));
if (SpecFuzzyFind)
SpecFuzzyFind->NewReq = Req;
if (SpecFuzzyFind && SpecFuzzyFind->Result.valid() && (*SpecReq == Req)) {
vlog("Code complete: speculative fuzzy request matches the actual index "
"request. Waiting for the speculative index results.");
SPAN_ATTACH(Tracer, "Speculative results", true);
trace::Span WaitSpec("Wait speculative results");
auto SpecRes = SpecFuzzyFind->Result.get();
Incomplete |= SpecRes.first;
return std::move(SpecRes.second);
}
SPAN_ATTACH(Tracer, "Speculative results", false);
SymbolSlab::Builder ResultsBuilder;
Incomplete |= Opts.Index->fuzzyFind(
Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); });
return std::move(ResultsBuilder).build();
}
std::vector<ScoredBundle>
mergeResults(const std::vector<CodeCompletionResult> &SemaResults,
const SymbolSlab &IndexResults,
const std::vector<RawIdentifier> &IdentifierResults) {
trace::Span Tracer("Merge and score results");
std::vector<CompletionCandidate::Bundle> Bundles;
llvm::DenseMap<size_t, size_t> BundleLookup;
auto AddToBundles = [&](const CodeCompletionResult *SemaResult,
const Symbol *IndexResult,
const RawIdentifier *IdentifierResult) {
CompletionCandidate C;
C.SemaResult = SemaResult;
C.IndexResult = IndexResult;
C.IdentifierResult = IdentifierResult;
if (C.IndexResult) {
C.Name = IndexResult->Name;
C.RankedIncludeHeaders = getRankedIncludes(*C.IndexResult);
} else if (C.SemaResult) {
C.Name = Recorder->getName(*SemaResult);
} else {
assert(IdentifierResult);
C.Name = IdentifierResult->Name;
}
if (auto OverloadSet = C.overloadSet(
Opts, FileName, Inserter ? &*Inserter : nullptr, CCContextKind)) {
auto Ret = BundleLookup.try_emplace(OverloadSet, Bundles.size());
if (Ret.second)
Bundles.emplace_back();
Bundles[Ret.first->second].push_back(std::move(C));
} else {
Bundles.emplace_back();
Bundles.back().push_back(std::move(C));
}
};
llvm::DenseSet<const Symbol *> UsedIndexResults;
auto CorrespondingIndexResult =
[&](const CodeCompletionResult &SemaResult) -> const Symbol * {
if (auto SymID =
getSymbolID(SemaResult, Recorder->CCSema->getSourceManager())) {
auto I = IndexResults.find(SymID);
if (I != IndexResults.end()) {
UsedIndexResults.insert(&*I);
return &*I;
}
}
return nullptr;
};
for (auto &SemaResult : SemaResults)
AddToBundles(&SemaResult, CorrespondingIndexResult(SemaResult), nullptr);
for (const auto &IndexResult : IndexResults) {
if (UsedIndexResults.count(&IndexResult))
continue;
if (!includeSymbolFromIndex(CCContextKind, IndexResult))
continue;
AddToBundles(nullptr, &IndexResult, nullptr);
}
for (const auto &Ident : IdentifierResults)
AddToBundles(nullptr, nullptr, &Ident);
TopN<ScoredBundle, ScoredBundleGreater> Top(
Opts.Limit == 0 ? std::numeric_limits<size_t>::max() : Opts.Limit);
for (auto &Bundle : Bundles)
addCandidate(Top, std::move(Bundle));
return std::move(Top).items();
}
std::optional<float> fuzzyScore(const CompletionCandidate &C) {
if (((C.SemaResult &&
C.SemaResult->Kind == CodeCompletionResult::RK_Macro) ||
(C.IndexResult &&
C.IndexResult->SymInfo.Kind == index::SymbolKind::Macro)) &&
!C.Name.starts_with_insensitive(Filter->pattern()))
return std::nullopt;
return Filter->match(C.Name);
}
CodeCompletion::Scores
evaluateCompletion(const SymbolQualitySignals &Quality,
const SymbolRelevanceSignals &Relevance) {
using RM = CodeCompleteOptions::CodeCompletionRankingModel;
CodeCompletion::Scores Scores;
switch (Opts.RankingModel) {
case RM::Heuristics:
Scores.Quality = Quality.evaluateHeuristics();
Scores.Relevance = Relevance.evaluateHeuristics();
Scores.Total =
evaluateSymbolAndRelevance(Scores.Quality, Scores.Relevance);
Scores.ExcludingName =
Relevance.NameMatch > std::numeric_limits<float>::epsilon()
? Scores.Total / Relevance.NameMatch
: Scores.Quality;
return Scores;
case RM::DecisionForest:
DecisionForestScores DFScores = Opts.DecisionForestScorer(
Quality, Relevance, Opts.DecisionForestBase);
Scores.ExcludingName = DFScores.ExcludingName;
Scores.Total = DFScores.Total;
return Scores;
}
llvm_unreachable("Unhandled CodeCompletion ranking model.");
}
void addCandidate(TopN<ScoredBundle, ScoredBundleGreater> &Candidates,
CompletionCandidate::Bundle Bundle) {
SymbolQualitySignals Quality;
SymbolRelevanceSignals Relevance;
Relevance.Context = CCContextKind;
Relevance.Name = Bundle.front().Name;
Relevance.FilterLength = HeuristicPrefix.Name.size();
Relevance.Query = SymbolRelevanceSignals::CodeComplete;
Relevance.FileProximityMatch = &*FileProximity;
if (ScopeProximity)
Relevance.ScopeProximityMatch = &*ScopeProximity;
if (PreferredType)
Relevance.HadContextType = true;
Relevance.ContextWords = &ContextWords;
Relevance.MainFileSignals = Opts.MainFileSignals;
auto &First = Bundle.front();
if (auto FuzzyScore = fuzzyScore(First))
Relevance.NameMatch = *FuzzyScore;
else
return;
SymbolOrigin Origin = SymbolOrigin::Unknown;
bool FromIndex = false;
for (const auto &Candidate : Bundle) {
if (Candidate.IndexResult) {
Quality.merge(*Candidate.IndexResult);
Relevance.merge(*Candidate.IndexResult);
Origin |= Candidate.IndexResult->Origin;
FromIndex = true;
if (!Candidate.IndexResult->Type.empty())
Relevance.HadSymbolType |= true;
if (PreferredType &&
PreferredType->raw() == Candidate.IndexResult->Type) {
Relevance.TypeMatchesPreferred = true;
}
}
if (Candidate.SemaResult) {
Quality.merge(*Candidate.SemaResult);
Relevance.merge(*Candidate.SemaResult);
if (PreferredType) {
if (auto CompletionType = OpaqueType::fromCompletionResult(
Recorder->CCSema->getASTContext(), *Candidate.SemaResult)) {
Relevance.HadSymbolType |= true;
if (PreferredType == CompletionType)
Relevance.TypeMatchesPreferred = true;
}
}
Origin |= SymbolOrigin::AST;
}
if (Candidate.IdentifierResult) {
Quality.References = Candidate.IdentifierResult->References;
Relevance.Scope = SymbolRelevanceSignals::FileScope;
Origin |= SymbolOrigin::Identifier;
}
}
CodeCompletion::Scores Scores = evaluateCompletion(Quality, Relevance);
if (Opts.RecordCCResult)
Opts.RecordCCResult(toCodeCompletion(Bundle), Quality, Relevance,
Scores.Total);
dlog("CodeComplete: {0} ({1}) = {2}\n{3}{4}\n", First.Name,
llvm::to_string(Origin), Scores.Total, llvm::to_string(Quality),
llvm::to_string(Relevance));
NSema += bool(Origin & SymbolOrigin::AST);
NIndex += FromIndex;
NSemaAndIndex += bool(Origin & SymbolOrigin::AST) && FromIndex;
NIdent += bool(Origin & SymbolOrigin::Identifier);
if (Candidates.push({std::move(Bundle), Scores}))
Incomplete = true;
}
CodeCompletion toCodeCompletion(const CompletionCandidate::Bundle &Bundle) {
std::optional<CodeCompletionBuilder> Builder;
for (const auto &Item : Bundle) {
CodeCompletionString *SemaCCS =
Item.SemaResult ? Recorder->codeCompletionString(*Item.SemaResult)
: nullptr;
if (!Builder)
Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr,
Item, SemaCCS, AccessibleScopes, *Inserter, FileName,
CCContextKind, Opts, IsUsingDeclaration, NextTokenKind);
else
Builder->add(Item, SemaCCS, CCContextKind);
}
return Builder->build();
}
};
}
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
clang::CodeCompleteOptions Result;
Result.IncludeCodePatterns = EnableSnippets;
Result.IncludeMacros = true;
Result.IncludeGlobals = true;
Result.IncludeBriefComments = false;
Result.LoadExternal = !Index;
Result.IncludeFixIts = IncludeFixIts;
return Result;
}
CompletionPrefix guessCompletionPrefix(llvm::StringRef Content,
unsigned Offset) {
assert(Offset <= Content.size());
StringRef Rest = Content.take_front(Offset);
CompletionPrefix Result;
while (!Rest.empty() && isAsciiIdentifierContinue(Rest.back()))
Rest = Rest.drop_back();
Result.Name = Content.slice(Rest.size(), Offset);
while (Rest.consume_back("::") && !Rest.ends_with(":"))
while (!Rest.empty() && isAsciiIdentifierContinue(Rest.back()))
Rest = Rest.drop_back();
Result.Qualifier =
Content.slice(Rest.size(), Result.Name.begin() - Content.begin());
return Result;
}
CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset,
llvm::StringRef Prefix,
const PreambleData *Preamble,
const ParseInputs &ParseInput) {
if (Preamble == nullptr)
return CodeCompleteResult();
clang::CodeCompleteOptions Options;
Options.IncludeGlobals = false;
Options.IncludeMacros = false;
Options.IncludeCodePatterns = false;
Options.IncludeBriefComments = false;
std::set<std::string> ParamNames;
semaCodeComplete(
std::make_unique<ParamNameCollector>(Options, ParamNames), Options,
{FileName, Offset, *Preamble,
PreamblePatch::createFullPatch(FileName, ParseInput, *Preamble),
ParseInput});
if (ParamNames.empty())
return CodeCompleteResult();
CodeCompleteResult Result;
Range CompletionRange;
Offset += 2;
CompletionRange.start = offsetToPosition(ParseInput.Contents, Offset);
CompletionRange.end =
offsetToPosition(ParseInput.Contents, Offset + Prefix.size());
Result.CompletionRange = CompletionRange;
Result.Context = CodeCompletionContext::CCC_NaturalLanguage;
for (llvm::StringRef Name : ParamNames) {
if (!Name.starts_with(Prefix))
continue;
CodeCompletion Item;
Item.Name = Name.str() + "=*/";
Item.FilterText = Item.Name;
Item.Kind = CompletionItemKind::Text;
Item.CompletionTokenRange = CompletionRange;
Item.Origin = SymbolOrigin::AST;
Result.Completions.push_back(Item);
}
return Result;
}
std::optional<unsigned>
maybeFunctionArgumentCommentStart(llvm::StringRef Content) {
while (!Content.empty() && isAsciiIdentifierContinue(Content.back()))
Content = Content.drop_back();
Content = Content.rtrim();
if (Content.ends_with("/*"))
return Content.size() - 2;
return std::nullopt;
}
CodeCompleteResult codeComplete(PathRef FileName, Position Pos,
const PreambleData *Preamble,
const ParseInputs &ParseInput,
CodeCompleteOptions Opts,
SpeculativeFuzzyFind *SpecFuzzyFind) {
auto Offset = positionToOffset(ParseInput.Contents, Pos);
if (!Offset) {
elog("Code completion position was invalid {0}", Offset.takeError());
return CodeCompleteResult();
}
auto Content = llvm::StringRef(ParseInput.Contents).take_front(*Offset);
if (auto OffsetBeforeComment = maybeFunctionArgumentCommentStart(Content)) {
auto CommentPrefix = Content.substr(*OffsetBeforeComment + 2).trim();
return codeCompleteComment(FileName, *OffsetBeforeComment, CommentPrefix,
Preamble, ParseInput);
}
auto Flow = CodeCompleteFlow(
FileName, Preamble ? Preamble->Includes : IncludeStructure(),
SpecFuzzyFind, Opts);
return (!Preamble || Opts.RunParser == CodeCompleteOptions::NeverParse)
? std::move(Flow).runWithoutSema(ParseInput.Contents, *Offset,
*ParseInput.TFS)
: std::move(Flow).run({FileName, *Offset, *Preamble,
PreamblePatch::createMacroPatch(
FileName, ParseInput, *Preamble),
ParseInput});
}
SignatureHelp signatureHelp(PathRef FileName, Position Pos,
const PreambleData &Preamble,
const ParseInputs &ParseInput,
MarkupKind DocumentationFormat) {
auto Offset = positionToOffset(ParseInput.Contents, Pos);
if (!Offset) {
elog("Signature help position was invalid {0}", Offset.takeError());
return SignatureHelp();
}
SignatureHelp Result;
clang::CodeCompleteOptions Options;
Options.IncludeGlobals = false;
Options.IncludeMacros = false;
Options.IncludeCodePatterns = false;
Options.IncludeBriefComments = false;
semaCodeComplete(
std::make_unique<SignatureHelpCollector>(Options, DocumentationFormat,
ParseInput.Index, Result),
Options,
{FileName, *Offset, Preamble,
PreamblePatch::createFullPatch(FileName, ParseInput, Preamble),
ParseInput});
return Result;
}
bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) {
auto InTopLevelScope = [](const NamedDecl &ND) {
switch (ND.getDeclContext()->getDeclKind()) {
case Decl::TranslationUnit:
case Decl::Namespace:
case Decl::LinkageSpec:
return true;
default:
break;
};
return false;
};
auto InClassScope = [](const NamedDecl &ND) {
return ND.getDeclContext()->getDeclKind() == Decl::CXXRecord;
};
if (isExplicitTemplateSpecialization(&ND))
return false;
if (llvm::isa<ObjCCategoryDecl>(&ND) || llvm::isa<ObjCCategoryImplDecl>(&ND))
return false;
if (InTopLevelScope(ND))
return true;
if (const auto *EnumDecl = dyn_cast<clang::EnumDecl>(ND.getDeclContext()))
return (InTopLevelScope(*EnumDecl) || InClassScope(*EnumDecl));
return false;
}
CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
CompletionItem LSP;
const auto *InsertInclude = Includes.empty() ? nullptr : &Includes[0];
LSP.label = ((InsertInclude && InsertInclude->Insertion)
? Opts.IncludeIndicator.Insert
: Opts.IncludeIndicator.NoInsert) +
(Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") +
RequiredQualifier + Name;
LSP.labelDetails.emplace();
LSP.labelDetails->detail = Signature;
LSP.kind = Kind;
LSP.detail = BundleSize > 1
? std::string(llvm::formatv("[{0} overloads]", BundleSize))
: ReturnType;
LSP.deprecated = Deprecated;
if (InsertInclude || Documentation) {
markup::Document Doc;
if (InsertInclude)
Doc.addParagraph().appendText("From ").appendCode(InsertInclude->Header);
if (Documentation)
Doc.append(*Documentation);
LSP.documentation = renderDoc(Doc, Opts.DocumentationFormat);
}
LSP.sortText = sortText(Score.Total, FilterText);
LSP.filterText = FilterText;
LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name, ""};
for (const auto &FixIt : FixIts) {
if (FixIt.range.end == LSP.textEdit->range.start) {
LSP.textEdit->newText = FixIt.newText + LSP.textEdit->newText;
LSP.textEdit->range.start = FixIt.range.start;
} else {
LSP.additionalTextEdits.push_back(FixIt);
}
}
if (Opts.EnableSnippets)
LSP.textEdit->newText += SnippetSuffix;
LSP.insertText = LSP.textEdit->newText;
LSP.insertTextFormat = (Opts.EnableSnippets && !SnippetSuffix.empty())
? InsertTextFormat::Snippet
: InsertTextFormat::PlainText;
if (InsertInclude && InsertInclude->Insertion)
LSP.additionalTextEdits.push_back(*InsertInclude->Insertion);
LSP.score = Score.ExcludingName;
return LSP;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CodeCompletion &C) {
return OS << C.render(CodeCompleteOptions());
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
const CodeCompleteResult &R) {
OS << "CodeCompleteResult: " << R.Completions.size() << (R.HasMore ? "+" : "")
<< " (" << getCompletionKindString(R.Context) << ")"
<< " items:\n";
for (const auto &C : R.Completions)
OS << C << "\n";
return OS;
}
bool isIncludeFile(llvm::StringRef Line) {
Line = Line.ltrim();
if (!Line.consume_front("#"))
return false;
Line = Line.ltrim();
if (!(Line.consume_front("include_next") || Line.consume_front("include") ||
Line.consume_front("import")))
return false;
Line = Line.ltrim();
if (Line.consume_front("<"))
return Line.count('>') == 0;
if (Line.consume_front("\""))
return Line.count('"') == 0;
return false;
}
bool allowImplicitCompletion(llvm::StringRef Content, unsigned Offset) {
Content = Content.take_front(Offset);
auto Pos = Content.rfind('\n');
if (Pos != llvm::StringRef::npos)
Content = Content.substr(Pos + 1);
if (Content.ends_with(".") || Content.ends_with("->") ||
Content.ends_with("::") || Content.ends_with("/*"))
return true;
if ((Content.ends_with("<") || Content.ends_with("\"") ||
Content.ends_with("/")) &&
isIncludeFile(Content))
return true;
return !Content.empty() && (isAsciiIdentifierContinue(Content.back()) ||
!llvm::isASCII(Content.back()));
}
}
}