2017-12-04 21:49:59 +08:00
|
|
|
//===--- CodeComplete.cpp ---------------------------------------*- C++-*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
//
|
|
|
|
// AST-based completions are provided using the completion hooks in Sema.
|
|
|
|
//
|
|
|
|
// Signature help works in a similar way as code completion, but it is simpler
|
|
|
|
// as there are typically fewer candidates.
|
|
|
|
//
|
|
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "CodeComplete.h"
|
2017-12-21 01:24:31 +08:00
|
|
|
#include "CodeCompletionStrings.h"
|
2017-12-04 21:49:59 +08:00
|
|
|
#include "Compiler.h"
|
2018-01-13 00:16:09 +08:00
|
|
|
#include "FuzzyMatch.h"
|
2017-12-20 00:50:37 +08:00
|
|
|
#include "Logger.h"
|
|
|
|
#include "index/Index.h"
|
2017-12-04 21:49:59 +08:00
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
|
|
#include "clang/Frontend/FrontendActions.h"
|
|
|
|
#include "clang/Sema/CodeCompleteConsumer.h"
|
|
|
|
#include "clang/Sema/Sema.h"
|
2018-01-10 22:44:34 +08:00
|
|
|
#include "llvm/Support/Format.h"
|
2017-12-04 21:49:59 +08:00
|
|
|
#include <queue>
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace clangd {
|
|
|
|
namespace {
|
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionItemKind toCompletionItemKind(CXCursorKind CursorKind) {
|
2017-12-04 21:49:59 +08:00
|
|
|
switch (CursorKind) {
|
|
|
|
case CXCursor_MacroInstantiation:
|
|
|
|
case CXCursor_MacroDefinition:
|
|
|
|
return CompletionItemKind::Text;
|
|
|
|
case CXCursor_CXXMethod:
|
2017-12-20 00:50:37 +08:00
|
|
|
case CXCursor_Destructor:
|
2017-12-04 21:49:59 +08:00
|
|
|
return CompletionItemKind::Method;
|
|
|
|
case CXCursor_FunctionDecl:
|
|
|
|
case CXCursor_FunctionTemplate:
|
|
|
|
return CompletionItemKind::Function;
|
|
|
|
case CXCursor_Constructor:
|
|
|
|
return CompletionItemKind::Constructor;
|
|
|
|
case CXCursor_FieldDecl:
|
|
|
|
return CompletionItemKind::Field;
|
|
|
|
case CXCursor_VarDecl:
|
|
|
|
case CXCursor_ParmDecl:
|
|
|
|
return CompletionItemKind::Variable;
|
2017-12-20 00:50:37 +08:00
|
|
|
// FIXME(ioeric): use LSP struct instead of class when it is suppoted in the
|
|
|
|
// protocol.
|
2017-12-04 21:49:59 +08:00
|
|
|
case CXCursor_StructDecl:
|
2017-12-20 00:50:37 +08:00
|
|
|
case CXCursor_ClassDecl:
|
2017-12-04 21:49:59 +08:00
|
|
|
case CXCursor_UnionDecl:
|
|
|
|
case CXCursor_ClassTemplate:
|
|
|
|
case CXCursor_ClassTemplatePartialSpecialization:
|
|
|
|
return CompletionItemKind::Class;
|
|
|
|
case CXCursor_Namespace:
|
|
|
|
case CXCursor_NamespaceAlias:
|
|
|
|
case CXCursor_NamespaceRef:
|
|
|
|
return CompletionItemKind::Module;
|
|
|
|
case CXCursor_EnumConstantDecl:
|
|
|
|
return CompletionItemKind::Value;
|
|
|
|
case CXCursor_EnumDecl:
|
|
|
|
return CompletionItemKind::Enum;
|
2017-12-20 00:50:37 +08:00
|
|
|
// FIXME(ioeric): figure out whether reference is the right type for aliases.
|
2017-12-04 21:49:59 +08:00
|
|
|
case CXCursor_TypeAliasDecl:
|
|
|
|
case CXCursor_TypeAliasTemplateDecl:
|
|
|
|
case CXCursor_TypedefDecl:
|
|
|
|
case CXCursor_MemberRef:
|
|
|
|
case CXCursor_TypeRef:
|
|
|
|
return CompletionItemKind::Reference;
|
|
|
|
default:
|
|
|
|
return CompletionItemKind::Missing;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionItemKind
|
|
|
|
toCompletionItemKind(CodeCompletionResult::ResultKind ResKind,
|
|
|
|
CXCursorKind CursorKind) {
|
2017-12-04 21:49:59 +08:00
|
|
|
switch (ResKind) {
|
|
|
|
case CodeCompletionResult::RK_Declaration:
|
2017-12-20 00:50:37 +08:00
|
|
|
return toCompletionItemKind(CursorKind);
|
2017-12-04 21:49:59 +08:00
|
|
|
case CodeCompletionResult::RK_Keyword:
|
|
|
|
return CompletionItemKind::Keyword;
|
|
|
|
case CodeCompletionResult::RK_Macro:
|
|
|
|
return CompletionItemKind::Text; // unfortunately, there's no 'Macro'
|
|
|
|
// completion items in LSP.
|
|
|
|
case CodeCompletionResult::RK_Pattern:
|
|
|
|
return CompletionItemKind::Snippet;
|
|
|
|
}
|
|
|
|
llvm_unreachable("Unhandled CodeCompletionResult::ResultKind.");
|
|
|
|
}
|
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) {
|
|
|
|
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 CompletionItemKind::Text;
|
|
|
|
case SK::Enum:
|
|
|
|
return CompletionItemKind::Enum;
|
|
|
|
// FIXME(ioeric): use LSP struct instead of class when it is suppoted in the
|
|
|
|
// protocol.
|
|
|
|
case SK::Struct:
|
|
|
|
case SK::Class:
|
|
|
|
case SK::Protocol:
|
|
|
|
case SK::Extension:
|
|
|
|
case SK::Union:
|
|
|
|
return CompletionItemKind::Class;
|
|
|
|
// FIXME(ioeric): figure out whether reference is the right type for aliases.
|
|
|
|
case SK::TypeAlias:
|
|
|
|
case SK::Using:
|
|
|
|
return CompletionItemKind::Reference;
|
|
|
|
case SK::Function:
|
|
|
|
// FIXME(ioeric): this should probably be an operator. This should be fixed
|
|
|
|
// when `Operator` is support type in the protocol.
|
|
|
|
case SK::ConversionFunction:
|
|
|
|
return CompletionItemKind::Function;
|
|
|
|
case SK::Variable:
|
|
|
|
case SK::Parameter:
|
|
|
|
return CompletionItemKind::Variable;
|
|
|
|
case SK::Field:
|
|
|
|
return CompletionItemKind::Field;
|
|
|
|
// FIXME(ioeric): use LSP enum constant when it is supported in the protocol.
|
|
|
|
case SK::EnumConstant:
|
|
|
|
return CompletionItemKind::Value;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
llvm_unreachable("Unhandled clang::index::SymbolKind.");
|
|
|
|
}
|
|
|
|
|
2017-12-04 21:49:59 +08:00
|
|
|
/// Get the optional chunk as a string. This function is possibly recursive.
|
|
|
|
///
|
|
|
|
/// The parameter info for each parameter is appended to the Parameters.
|
|
|
|
std::string
|
|
|
|
getOptionalParameters(const CodeCompletionString &CCS,
|
|
|
|
std::vector<ParameterInformation> &Parameters) {
|
|
|
|
std::string Result;
|
|
|
|
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.");
|
|
|
|
Result += getOptionalParameters(*Chunk.Optional, Parameters);
|
|
|
|
break;
|
|
|
|
case CodeCompletionString::CK_VerticalSpace:
|
|
|
|
break;
|
|
|
|
case CodeCompletionString::CK_Placeholder:
|
|
|
|
// A string that acts as a placeholder for, e.g., a function call
|
|
|
|
// argument.
|
|
|
|
// Intentional fallthrough here.
|
|
|
|
case CodeCompletionString::CK_CurrentParameter: {
|
|
|
|
// A piece of text that describes the parameter that corresponds to
|
|
|
|
// the code-completion location within a function call, message send,
|
|
|
|
// macro invocation, etc.
|
|
|
|
Result += Chunk.Text;
|
|
|
|
ParameterInformation Info;
|
|
|
|
Info.label = Chunk.Text;
|
|
|
|
Parameters.push_back(std::move(Info));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
Result += Chunk.Text;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A scored code completion result.
|
|
|
|
/// It may be promoted to a CompletionItem if it's among the top-ranked results.
|
2018-01-13 00:16:09 +08:00
|
|
|
///
|
|
|
|
/// We score candidates by multiplying the symbolScore ("quality" of the result)
|
|
|
|
/// with the filterScore (how well it matched the query).
|
|
|
|
/// This is sensitive to the distribution of both component scores!
|
2017-12-04 21:49:59 +08:00
|
|
|
struct CompletionCandidate {
|
2018-01-13 00:16:09 +08:00
|
|
|
CompletionCandidate(CodeCompletionResult &Result, float FilterScore)
|
|
|
|
: Result(&Result) {
|
|
|
|
Scores.symbolScore = score(Result); // Higher is better.
|
|
|
|
Scores.filterScore = FilterScore; // 0-1, higher is better.
|
|
|
|
Scores.finalScore = Scores.symbolScore * Scores.filterScore;
|
|
|
|
}
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
CodeCompletionResult *Result;
|
2018-01-13 00:16:09 +08:00
|
|
|
CompletionItemScores Scores;
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
// Comparison reflects rank: better candidates are smaller.
|
|
|
|
bool operator<(const CompletionCandidate &C) const {
|
2018-01-13 00:16:09 +08:00
|
|
|
if (Scores.finalScore != C.Scores.finalScore)
|
|
|
|
return Scores.finalScore > C.Scores.finalScore;
|
2017-12-04 21:49:59 +08:00
|
|
|
return *Result < *C.Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a string that sorts in the same order as operator<, for LSP.
|
|
|
|
// Conceptually, this is [-Score, Name]. We convert -Score to an integer, and
|
|
|
|
// hex-encode it for readability. Example: [0.5, "foo"] -> "41000000foo"
|
|
|
|
std::string sortText() const {
|
|
|
|
std::string S, NameStorage;
|
|
|
|
llvm::raw_string_ostream OS(S);
|
2018-01-13 00:16:09 +08:00
|
|
|
write_hex(OS, encodeFloat(-Scores.finalScore), llvm::HexPrintStyle::Lower,
|
|
|
|
/*Width=*/2 * sizeof(Scores.finalScore));
|
2017-12-04 21:49:59 +08:00
|
|
|
OS << Result->getOrderedName(NameStorage);
|
|
|
|
return OS.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
static float score(const CodeCompletionResult &Result) {
|
|
|
|
// Priority 80 is a really bad score.
|
|
|
|
float Score = 1 - std::min<float>(80, Result.Priority) / 80;
|
|
|
|
|
|
|
|
switch (static_cast<CXAvailabilityKind>(Result.Availability)) {
|
|
|
|
case CXAvailability_Available:
|
|
|
|
// No penalty.
|
|
|
|
break;
|
|
|
|
case CXAvailability_Deprecated:
|
|
|
|
Score *= 0.1f;
|
|
|
|
break;
|
|
|
|
case CXAvailability_NotAccessible:
|
|
|
|
case CXAvailability_NotAvailable:
|
|
|
|
Score = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return Score;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Produces an integer that sorts in the same order as F.
|
|
|
|
// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
|
|
|
|
static uint32_t encodeFloat(float F) {
|
|
|
|
static_assert(std::numeric_limits<float>::is_iec559, "");
|
|
|
|
static_assert(sizeof(float) == sizeof(uint32_t), "");
|
|
|
|
constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
|
|
|
|
|
|
|
|
// Get the bits of the float. Endianness is the same as for integers.
|
|
|
|
uint32_t U;
|
|
|
|
memcpy(&U, &F, sizeof(float));
|
|
|
|
// IEEE 754 floats compare like sign-magnitude integers.
|
|
|
|
if (U & TopBit) // Negative float.
|
|
|
|
return 0 - U; // Map onto the low half of integers, order reversed.
|
|
|
|
return U + TopBit; // Positive floats map onto the high half of integers.
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
/// \brief Information about the scope specifier in the qualified-id code
|
|
|
|
/// completion (e.g. "ns::ab?").
|
|
|
|
struct SpecifiedScope {
|
|
|
|
/// The scope specifier as written. For example, for completion "ns::ab?", the
|
|
|
|
/// written scope specifier is "ns".
|
|
|
|
std::string Written;
|
|
|
|
// If this scope specifier is recognized in Sema (e.g. as a namespace
|
|
|
|
// context), this will be set to the fully qualfied name of the corresponding
|
|
|
|
// context.
|
|
|
|
std::string Resolved;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// \brief Information from sema about (parital) symbol names to be completed.
|
|
|
|
/// For example, for completion "ns::ab^", this stores the scope specifier
|
|
|
|
/// "ns::" and the completion filter text "ab".
|
|
|
|
struct NameToComplete {
|
|
|
|
// The partial identifier being completed, without qualifier.
|
|
|
|
std::string Filter;
|
|
|
|
|
|
|
|
/// This is set if the completion is for qualified IDs, e.g. "abc::x^".
|
|
|
|
llvm::Optional<SpecifiedScope> SSInfo;
|
|
|
|
};
|
|
|
|
|
|
|
|
SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS);
|
|
|
|
|
2017-12-04 21:49:59 +08:00
|
|
|
class CompletionItemsCollector : public CodeCompleteConsumer {
|
|
|
|
public:
|
|
|
|
CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionList &Items, NameToComplete &CompletedName)
|
2017-12-04 21:49:59 +08:00
|
|
|
: CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(),
|
|
|
|
/*OutputIsBinary=*/false),
|
|
|
|
ClangdOpts(CodeCompleteOpts), Items(Items),
|
|
|
|
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
|
2017-12-21 01:24:31 +08:00
|
|
|
CCTUInfo(Allocator), CompletedName(CompletedName),
|
|
|
|
EnableSnippets(CodeCompleteOpts.EnableSnippets) {}
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
|
|
|
|
CodeCompletionResult *Results,
|
|
|
|
unsigned NumResults) override final {
|
2018-01-13 00:16:09 +08:00
|
|
|
FuzzyMatcher Filter(S.getPreprocessor().getCodeCompletionFilter());
|
2017-12-20 00:50:37 +08:00
|
|
|
if (auto SS = Context.getCXXScopeSpecifier())
|
|
|
|
CompletedName.SSInfo = extraCompletionScope(S, **SS);
|
|
|
|
|
|
|
|
CompletedName.Filter = S.getPreprocessor().getCodeCompletionFilter();
|
2017-12-04 21:49:59 +08:00
|
|
|
std::priority_queue<CompletionCandidate> Candidates;
|
|
|
|
for (unsigned I = 0; I < NumResults; ++I) {
|
|
|
|
auto &Result = Results[I];
|
2018-01-10 21:51:09 +08:00
|
|
|
// We drop hidden items, as they cannot be found by the lookup after
|
|
|
|
// inserting the corresponding completion item and only produce noise and
|
|
|
|
// duplicates in the completion list. However, there is one exception. If
|
|
|
|
// Result has a Qualifier which is non-informative, we can refer to an
|
|
|
|
// item by adding that qualifier, so we don't filter out this item.
|
|
|
|
if (Result.Hidden && (!Result.Qualifier || Result.QualifierIsInformative))
|
|
|
|
continue;
|
2017-12-04 21:49:59 +08:00
|
|
|
if (!ClangdOpts.IncludeIneligibleResults &&
|
|
|
|
(Result.Availability == CXAvailability_NotAvailable ||
|
|
|
|
Result.Availability == CXAvailability_NotAccessible))
|
|
|
|
continue;
|
2018-01-13 00:16:09 +08:00
|
|
|
auto FilterScore = fuzzyMatch(S, Context, Filter, Result);
|
|
|
|
if (!FilterScore)
|
2017-12-04 21:49:59 +08:00
|
|
|
continue;
|
2018-01-13 00:16:09 +08:00
|
|
|
Candidates.emplace(Result, *FilterScore);
|
2017-12-04 21:49:59 +08:00
|
|
|
if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) {
|
|
|
|
Candidates.pop();
|
|
|
|
Items.isIncomplete = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (!Candidates.empty()) {
|
|
|
|
auto &Candidate = Candidates.top();
|
|
|
|
const auto *CCS = Candidate.Result->CreateCodeCompletionString(
|
|
|
|
S, Context, *Allocator, CCTUInfo,
|
|
|
|
CodeCompleteOpts.IncludeBriefComments);
|
|
|
|
assert(CCS && "Expected the CodeCompletionString to be non-null");
|
|
|
|
Items.items.push_back(ProcessCodeCompleteResult(Candidate, *CCS));
|
|
|
|
Candidates.pop();
|
|
|
|
}
|
|
|
|
std::reverse(Items.items.begin(), Items.items.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
|
|
|
|
|
|
|
|
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
|
|
|
|
|
|
|
private:
|
2018-01-13 00:16:09 +08:00
|
|
|
llvm::Optional<float> fuzzyMatch(Sema &S, const CodeCompletionContext &CCCtx,
|
|
|
|
FuzzyMatcher &Filter,
|
|
|
|
CodeCompletionResult Result) {
|
2017-12-04 21:49:59 +08:00
|
|
|
switch (Result.Kind) {
|
|
|
|
case CodeCompletionResult::RK_Declaration:
|
|
|
|
if (auto *ID = Result.Declaration->getIdentifier())
|
2018-01-13 00:16:09 +08:00
|
|
|
return Filter.match(ID->getName());
|
2017-12-04 21:49:59 +08:00
|
|
|
break;
|
|
|
|
case CodeCompletionResult::RK_Keyword:
|
2018-01-13 00:16:09 +08:00
|
|
|
return Filter.match(Result.Keyword);
|
2017-12-04 21:49:59 +08:00
|
|
|
case CodeCompletionResult::RK_Macro:
|
2018-01-13 00:16:09 +08:00
|
|
|
return Filter.match(Result.Macro->getName());
|
2017-12-04 21:49:59 +08:00
|
|
|
case CodeCompletionResult::RK_Pattern:
|
2018-01-13 00:16:09 +08:00
|
|
|
return Filter.match(Result.Pattern->getTypedText());
|
2017-12-04 21:49:59 +08:00
|
|
|
}
|
|
|
|
auto *CCS = Result.CreateCodeCompletionString(
|
|
|
|
S, CCCtx, *Allocator, CCTUInfo, /*IncludeBriefComments=*/false);
|
2018-01-13 00:16:09 +08:00
|
|
|
return Filter.match(CCS->getTypedText());
|
2017-12-04 21:49:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
CompletionItem
|
|
|
|
ProcessCodeCompleteResult(const CompletionCandidate &Candidate,
|
|
|
|
const CodeCompletionString &CCS) const {
|
|
|
|
|
|
|
|
// Adjust this to InsertTextFormat::Snippet iff we encounter a
|
|
|
|
// CK_Placeholder chunk in SnippetCompletionItemsCollector.
|
|
|
|
CompletionItem Item;
|
|
|
|
|
|
|
|
Item.documentation = getDocumentation(CCS);
|
|
|
|
Item.sortText = Candidate.sortText();
|
2018-01-13 00:16:09 +08:00
|
|
|
Item.scoreInfo = Candidate.Scores;
|
2017-12-04 21:49:59 +08:00
|
|
|
|
2017-12-21 01:24:31 +08:00
|
|
|
Item.detail = getDetail(CCS);
|
|
|
|
Item.filterText = getFilterText(CCS);
|
|
|
|
getLabelAndInsertText(CCS, &Item.label, &Item.insertText, EnableSnippets);
|
|
|
|
|
|
|
|
Item.insertTextFormat = EnableSnippets ? InsertTextFormat::Snippet
|
|
|
|
: InsertTextFormat::PlainText;
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
// Fill in the kind field of the CompletionItem.
|
2017-12-20 00:50:37 +08:00
|
|
|
Item.kind = toCompletionItemKind(Candidate.Result->Kind,
|
|
|
|
Candidate.Result->CursorKind);
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
return Item;
|
|
|
|
}
|
|
|
|
|
|
|
|
CodeCompleteOptions ClangdOpts;
|
|
|
|
CompletionList &Items;
|
|
|
|
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
|
|
|
|
CodeCompletionTUInfo CCTUInfo;
|
2017-12-20 00:50:37 +08:00
|
|
|
NameToComplete &CompletedName;
|
2017-12-21 01:24:31 +08:00
|
|
|
bool EnableSnippets;
|
2017-12-04 21:49:59 +08:00
|
|
|
}; // CompletionItemsCollector
|
|
|
|
|
|
|
|
class SignatureHelpCollector final : public CodeCompleteConsumer {
|
|
|
|
|
|
|
|
public:
|
|
|
|
SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
|
|
|
|
SignatureHelp &SigHelp)
|
|
|
|
: CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
|
|
|
|
SigHelp(SigHelp),
|
|
|
|
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
|
|
|
|
CCTUInfo(Allocator) {}
|
|
|
|
|
|
|
|
void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
|
|
|
|
OverloadCandidate *Candidates,
|
|
|
|
unsigned NumCandidates) override {
|
|
|
|
SigHelp.signatures.reserve(NumCandidates);
|
|
|
|
// FIXME(rwols): How can we determine the "active overload candidate"?
|
|
|
|
// Right now the overloaded candidates seem to be provided in a "best fit"
|
|
|
|
// order, so I'm not too worried about this.
|
|
|
|
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) {
|
|
|
|
const auto &Candidate = Candidates[I];
|
|
|
|
const auto *CCS = Candidate.CreateSignatureString(
|
|
|
|
CurrentArg, S, *Allocator, CCTUInfo, true);
|
|
|
|
assert(CCS && "Expected the CodeCompletionString to be non-null");
|
|
|
|
SigHelp.signatures.push_back(ProcessOverloadCandidate(Candidate, *CCS));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
|
|
|
|
|
|
|
|
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
|
|
|
|
|
|
|
private:
|
2017-12-21 01:24:31 +08:00
|
|
|
// FIXME(ioeric): consider moving CodeCompletionString logic here to
|
|
|
|
// CompletionString.h.
|
2017-12-04 21:49:59 +08:00
|
|
|
SignatureInformation
|
|
|
|
ProcessOverloadCandidate(const OverloadCandidate &Candidate,
|
|
|
|
const CodeCompletionString &CCS) const {
|
|
|
|
SignatureInformation Result;
|
|
|
|
const char *ReturnType = nullptr;
|
|
|
|
|
|
|
|
Result.documentation = getDocumentation(CCS);
|
|
|
|
|
|
|
|
for (const auto &Chunk : CCS) {
|
|
|
|
switch (Chunk.Kind) {
|
|
|
|
case CodeCompletionString::CK_ResultType:
|
|
|
|
// A piece of text that describes the type of an entity or,
|
|
|
|
// for functions and methods, the return type.
|
|
|
|
assert(!ReturnType && "Unexpected CK_ResultType");
|
|
|
|
ReturnType = Chunk.Text;
|
|
|
|
break;
|
|
|
|
case CodeCompletionString::CK_Placeholder:
|
|
|
|
// A string that acts as a placeholder for, e.g., a function call
|
|
|
|
// argument.
|
|
|
|
// Intentional fallthrough here.
|
|
|
|
case CodeCompletionString::CK_CurrentParameter: {
|
|
|
|
// A piece of text that describes the parameter that corresponds to
|
|
|
|
// the code-completion location within a function call, message send,
|
|
|
|
// macro invocation, etc.
|
|
|
|
Result.label += Chunk.Text;
|
|
|
|
ParameterInformation Info;
|
|
|
|
Info.label = Chunk.Text;
|
|
|
|
Result.parameters.push_back(std::move(Info));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CodeCompletionString::CK_Optional: {
|
|
|
|
// The rest of the parameters are defaulted/optional.
|
|
|
|
assert(Chunk.Optional &&
|
|
|
|
"Expected the optional code completion string to be non-null.");
|
|
|
|
Result.label +=
|
|
|
|
getOptionalParameters(*Chunk.Optional, Result.parameters);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case CodeCompletionString::CK_VerticalSpace:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
Result.label += Chunk.Text;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ReturnType) {
|
|
|
|
Result.label += " -> ";
|
|
|
|
Result.label += ReturnType;
|
|
|
|
}
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
SignatureHelp &SigHelp;
|
|
|
|
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
|
|
|
|
CodeCompletionTUInfo CCTUInfo;
|
|
|
|
|
|
|
|
}; // SignatureHelpCollector
|
|
|
|
|
2017-12-13 20:51:22 +08:00
|
|
|
bool invokeCodeComplete(const Context &Ctx,
|
|
|
|
std::unique_ptr<CodeCompleteConsumer> Consumer,
|
2017-12-04 21:49:59 +08:00
|
|
|
const clang::CodeCompleteOptions &Options,
|
|
|
|
PathRef FileName,
|
|
|
|
const tooling::CompileCommand &Command,
|
|
|
|
PrecompiledPreamble const *Preamble, StringRef Contents,
|
|
|
|
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
2017-12-13 20:51:22 +08:00
|
|
|
std::shared_ptr<PCHContainerOperations> PCHs) {
|
2017-12-04 21:49:59 +08:00
|
|
|
std::vector<const char *> ArgStrs;
|
|
|
|
for (const auto &S : Command.CommandLine)
|
|
|
|
ArgStrs.push_back(S.c_str());
|
|
|
|
|
|
|
|
VFS->setCurrentWorkingDirectory(Command.Directory);
|
|
|
|
|
|
|
|
IgnoreDiagnostics DummyDiagsConsumer;
|
|
|
|
auto CI = createInvocationFromCommandLine(
|
|
|
|
ArgStrs,
|
|
|
|
CompilerInstance::createDiagnostics(new DiagnosticOptions,
|
|
|
|
&DummyDiagsConsumer, false),
|
|
|
|
VFS);
|
|
|
|
assert(CI && "Couldn't create CompilerInvocation");
|
2018-01-05 21:36:55 +08:00
|
|
|
CI->getFrontendOpts().DisableFree = false;
|
2017-12-04 21:49:59 +08:00
|
|
|
|
|
|
|
std::unique_ptr<llvm::MemoryBuffer> ContentsBuffer =
|
|
|
|
llvm::MemoryBuffer::getMemBufferCopy(Contents, FileName);
|
|
|
|
|
2018-01-18 23:17:00 +08:00
|
|
|
// We reuse the preamble whether it's valid or not. This is a
|
|
|
|
// correctness/performance tradeoff: building without a preamble is slow, and
|
|
|
|
// completion is latency-sensitive.
|
2017-12-04 21:49:59 +08:00
|
|
|
if (Preamble) {
|
|
|
|
auto Bounds =
|
|
|
|
ComputePreambleBounds(*CI->getLangOpts(), ContentsBuffer.get(), 0);
|
2018-01-18 23:17:00 +08:00
|
|
|
// FIXME(ibiryukov): Remove this call to CanReuse() after we'll fix
|
|
|
|
// clients relying on getting stats for preamble files during code
|
|
|
|
// completion.
|
|
|
|
// Note that results of CanReuse() are ignored, see the comment above.
|
|
|
|
Preamble->CanReuse(*CI, ContentsBuffer.get(), Bounds, VFS.get());
|
2017-12-04 21:49:59 +08:00
|
|
|
}
|
|
|
|
auto Clang = prepareCompilerInstance(
|
|
|
|
std::move(CI), Preamble, std::move(ContentsBuffer), std::move(PCHs),
|
|
|
|
std::move(VFS), DummyDiagsConsumer);
|
|
|
|
auto &DiagOpts = Clang->getDiagnosticOpts();
|
|
|
|
DiagOpts.IgnoreWarnings = true;
|
|
|
|
|
|
|
|
auto &FrontendOpts = Clang->getFrontendOpts();
|
|
|
|
FrontendOpts.SkipFunctionBodies = true;
|
|
|
|
FrontendOpts.CodeCompleteOpts = Options;
|
|
|
|
FrontendOpts.CodeCompletionAt.FileName = FileName;
|
|
|
|
FrontendOpts.CodeCompletionAt.Line = Pos.line + 1;
|
|
|
|
FrontendOpts.CodeCompletionAt.Column = Pos.character + 1;
|
|
|
|
|
|
|
|
Clang->setCodeCompletionConsumer(Consumer.release());
|
|
|
|
|
|
|
|
SyntaxOnlyAction Action;
|
|
|
|
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
|
2017-12-13 20:51:22 +08:00
|
|
|
log(Ctx,
|
|
|
|
"BeginSourceFile() failed when running codeComplete for " + FileName);
|
2017-12-04 21:49:59 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!Action.Execute()) {
|
2017-12-13 20:51:22 +08:00
|
|
|
log(Ctx, "Execute() failed when running codeComplete for " + FileName);
|
2017-12-04 21:49:59 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Action.EndSourceFile();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionItem indexCompletionItem(const Symbol &Sym, llvm::StringRef Filter,
|
2018-01-10 22:44:34 +08:00
|
|
|
const SpecifiedScope &SSInfo,
|
|
|
|
llvm::StringRef DebuggingLabel = "") {
|
2017-12-20 00:50:37 +08:00
|
|
|
CompletionItem Item;
|
|
|
|
Item.kind = toCompletionItemKind(Sym.SymInfo.Kind);
|
2018-01-10 22:44:34 +08:00
|
|
|
// Add DebuggingLabel to the completion results if DebuggingLabel is not
|
|
|
|
// empty.
|
|
|
|
//
|
|
|
|
// For symbols from static index, there are prefix "[G]" in the
|
|
|
|
// results (which is used for debugging purpose).
|
|
|
|
// So completion list will be like:
|
|
|
|
// clang::symbol_from_dynamic_index
|
|
|
|
// [G]clang::symbol_from_static_index
|
|
|
|
//
|
|
|
|
// FIXME: Find out a better way to show the index source.
|
|
|
|
if (!DebuggingLabel.empty()) {
|
|
|
|
llvm::raw_string_ostream Label(Item.label);
|
|
|
|
Label << llvm::format("[%s]%s", DebuggingLabel.str().c_str(),
|
|
|
|
Sym.Name.str().c_str());
|
|
|
|
} else {
|
|
|
|
Item.label = Sym.Name;
|
|
|
|
}
|
2017-12-20 00:50:37 +08:00
|
|
|
// FIXME(ioeric): support inserting/replacing scope qualifiers.
|
2018-01-10 01:32:00 +08:00
|
|
|
|
2017-12-20 00:50:37 +08:00
|
|
|
// FIXME(ioeric): support snippets.
|
2018-01-10 01:32:00 +08:00
|
|
|
Item.insertText = Sym.CompletionPlainInsertText;
|
2017-12-20 00:50:37 +08:00
|
|
|
Item.insertTextFormat = InsertTextFormat::PlainText;
|
2017-12-29 22:59:22 +08:00
|
|
|
Item.filterText = Sym.Name;
|
2017-12-20 00:50:37 +08:00
|
|
|
|
|
|
|
// FIXME(ioeric): sort symbols appropriately.
|
|
|
|
Item.sortText = "";
|
|
|
|
|
2018-01-10 01:32:00 +08:00
|
|
|
if (Sym.Detail) {
|
|
|
|
Item.documentation = Sym.Detail->Documentation;
|
|
|
|
Item.detail = Sym.Detail->CompletionDetail;
|
|
|
|
}
|
2017-12-20 00:50:37 +08:00
|
|
|
|
|
|
|
return Item;
|
|
|
|
}
|
|
|
|
|
|
|
|
void completeWithIndex(const Context &Ctx, const SymbolIndex &Index,
|
|
|
|
llvm::StringRef Code, const SpecifiedScope &SSInfo,
|
2018-01-10 22:44:34 +08:00
|
|
|
llvm::StringRef Filter, CompletionList *Items,
|
|
|
|
llvm::StringRef DebuggingLabel = "") {
|
2017-12-20 00:50:37 +08:00
|
|
|
FuzzyFindRequest Req;
|
|
|
|
Req.Query = Filter;
|
|
|
|
// FIXME(ioeric): add more possible scopes based on using namespaces and
|
|
|
|
// containing namespaces.
|
|
|
|
StringRef Scope = SSInfo.Resolved.empty() ? SSInfo.Written : SSInfo.Resolved;
|
|
|
|
Req.Scopes = {Scope.trim(':').str()};
|
|
|
|
|
2018-01-10 22:44:34 +08:00
|
|
|
Items->isIncomplete |= !Index.fuzzyFind(Ctx, Req, [&](const Symbol &Sym) {
|
|
|
|
Items->items.push_back(
|
|
|
|
indexCompletionItem(Sym, Filter, SSInfo, DebuggingLabel));
|
2017-12-20 00:50:37 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
SpecifiedScope extraCompletionScope(Sema &S, const CXXScopeSpec &SS) {
|
|
|
|
SpecifiedScope Info;
|
|
|
|
auto &SM = S.getSourceManager();
|
|
|
|
auto SpecifierRange = SS.getRange();
|
|
|
|
Info.Written = Lexer::getSourceText(
|
|
|
|
CharSourceRange::getCharRange(SpecifierRange), SM, clang::LangOptions());
|
|
|
|
if (SS.isValid()) {
|
|
|
|
DeclContext *DC = S.computeDeclContext(SS);
|
|
|
|
if (auto *NS = llvm::dyn_cast<NamespaceDecl>(DC)) {
|
|
|
|
Info.Resolved = NS->getQualifiedNameAsString();
|
2017-12-20 01:05:00 +08:00
|
|
|
} else if (llvm::dyn_cast<TranslationUnitDecl>(DC) != nullptr) {
|
2017-12-20 00:50:37 +08:00
|
|
|
Info.Resolved = "::";
|
|
|
|
// Sema does not include the suffix "::" in the range of SS, so we add
|
|
|
|
// it back here.
|
|
|
|
Info.Written = "::";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Info;
|
|
|
|
}
|
|
|
|
|
2017-12-04 21:49:59 +08:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
|
|
|
|
clang::CodeCompleteOptions Result;
|
|
|
|
Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns;
|
|
|
|
Result.IncludeMacros = IncludeMacros;
|
2018-01-18 23:31:30 +08:00
|
|
|
Result.IncludeGlobals = true;
|
2017-12-04 21:49:59 +08:00
|
|
|
Result.IncludeBriefComments = IncludeBriefComments;
|
|
|
|
|
2018-01-13 02:30:08 +08:00
|
|
|
// When an is used, Sema is responsible for completing the main file,
|
|
|
|
// the index can provide results from the preamble.
|
|
|
|
// Tell Sema not to deserialize the preamble to look for results.
|
|
|
|
Result.LoadExternal = !Index;
|
2017-12-20 00:50:37 +08:00
|
|
|
|
2017-12-04 21:49:59 +08:00
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
2017-12-13 20:51:22 +08:00
|
|
|
CompletionList codeComplete(const Context &Ctx, PathRef FileName,
|
2017-12-04 21:49:59 +08:00
|
|
|
const tooling::CompileCommand &Command,
|
|
|
|
PrecompiledPreamble const *Preamble,
|
|
|
|
StringRef Contents, Position Pos,
|
|
|
|
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
|
|
|
std::shared_ptr<PCHContainerOperations> PCHs,
|
2017-12-13 20:51:22 +08:00
|
|
|
CodeCompleteOptions Opts) {
|
2017-12-04 21:49:59 +08:00
|
|
|
CompletionList Results;
|
2017-12-20 00:50:37 +08:00
|
|
|
NameToComplete CompletedName;
|
2017-12-21 01:24:31 +08:00
|
|
|
auto Consumer =
|
|
|
|
llvm::make_unique<CompletionItemsCollector>(Opts, Results, CompletedName);
|
2017-12-13 20:51:22 +08:00
|
|
|
invokeCodeComplete(Ctx, std::move(Consumer), Opts.getClangCompleteOpts(),
|
|
|
|
FileName, Command, Preamble, Contents, Pos, std::move(VFS),
|
|
|
|
std::move(PCHs));
|
2018-01-10 22:44:34 +08:00
|
|
|
|
|
|
|
// Got scope specifier (ns::f^) for code completion from sema, try to query
|
|
|
|
// global symbols from indexes.
|
2018-01-15 20:33:00 +08:00
|
|
|
// FIXME: merge with Sema results, and respect limits.
|
|
|
|
if (CompletedName.SSInfo && Opts.Index)
|
|
|
|
completeWithIndex(Ctx, *Opts.Index, Contents, *CompletedName.SSInfo,
|
|
|
|
CompletedName.Filter, &Results, /*DebuggingLabel=*/"I");
|
2017-12-04 21:49:59 +08:00
|
|
|
return Results;
|
|
|
|
}
|
|
|
|
|
2017-12-13 20:51:22 +08:00
|
|
|
SignatureHelp signatureHelp(const Context &Ctx, PathRef FileName,
|
|
|
|
const tooling::CompileCommand &Command,
|
|
|
|
PrecompiledPreamble const *Preamble,
|
|
|
|
StringRef Contents, Position Pos,
|
|
|
|
IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
|
|
|
std::shared_ptr<PCHContainerOperations> PCHs) {
|
2017-12-04 21:49:59 +08:00
|
|
|
SignatureHelp Result;
|
|
|
|
clang::CodeCompleteOptions Options;
|
|
|
|
Options.IncludeGlobals = false;
|
|
|
|
Options.IncludeMacros = false;
|
|
|
|
Options.IncludeCodePatterns = false;
|
|
|
|
Options.IncludeBriefComments = true;
|
2017-12-13 20:51:22 +08:00
|
|
|
invokeCodeComplete(Ctx,
|
|
|
|
llvm::make_unique<SignatureHelpCollector>(Options, Result),
|
2017-12-04 21:49:59 +08:00
|
|
|
Options, FileName, Command, Preamble, Contents, Pos,
|
2017-12-13 20:51:22 +08:00
|
|
|
std::move(VFS), std::move(PCHs));
|
2017-12-04 21:49:59 +08:00
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace clangd
|
|
|
|
} // namespace clang
|