forked from OSchip/llvm-project
[clangd] More precise representation of symbol names/labels in the index.
Summary: Previously, the strings matched LSP completion pretty closely. The completion label was a single string, for instance. This made implementing completion itself easy but makes it hard to use the names in other way, e.g. pretty-printed name in synthesized documentation/hover. It also limits our introspection into completion items, which can only be as precise as the indexed symbols. This change is a prerequisite to improvements to overload bundling which need to inspect e.g. signature structure. Reviewers: ioeric Subscribers: ilya-biryukov, MaskRay, jkorous, cfe-commits Differential Revision: https://reviews.llvm.org/D48475 llvm-svn: 335360
This commit is contained in:
parent
213cb1b82d
commit
a68951e37e
|
@ -249,17 +249,21 @@ struct CompletionCandidate {
|
|||
CompletionItem I;
|
||||
bool InsertingInclude = false; // Whether a new #include will be added.
|
||||
if (SemaResult) {
|
||||
I.kind = toCompletionItemKind(SemaResult->Kind, SemaResult->Declaration);
|
||||
getLabelAndInsertText(*SemaCCS, &I.label, &I.insertText,
|
||||
Opts.EnableSnippets);
|
||||
if (const char* Text = SemaCCS->getTypedText())
|
||||
I.filterText = Text;
|
||||
llvm::StringRef Name(SemaCCS->getTypedText());
|
||||
std::string Signature, SnippetSuffix, Qualifiers;
|
||||
getSignature(*SemaCCS, &Signature, &SnippetSuffix, &Qualifiers);
|
||||
I.label = (Qualifiers + Name + Signature).str();
|
||||
I.filterText = Name;
|
||||
I.insertText = (Qualifiers + Name).str();
|
||||
if (Opts.EnableSnippets)
|
||||
I.insertText += SnippetSuffix;
|
||||
I.documentation = formatDocumentation(*SemaCCS, SemaDocComment);
|
||||
I.detail = getDetail(*SemaCCS);
|
||||
I.detail = getReturnType(*SemaCCS);
|
||||
if (SemaResult->Kind == CodeCompletionResult::RK_Declaration)
|
||||
if (const auto *D = SemaResult->getDeclaration())
|
||||
if (const auto *ND = llvm::dyn_cast<NamedDecl>(D))
|
||||
I.SymbolScope = splitQualifiedName(printQualifiedName(*ND)).first;
|
||||
I.kind = toCompletionItemKind(SemaResult->Kind, SemaResult->Declaration);
|
||||
}
|
||||
if (IndexResult) {
|
||||
if (I.SymbolScope.empty())
|
||||
|
@ -268,21 +272,22 @@ struct CompletionCandidate {
|
|||
I.kind = toCompletionItemKind(IndexResult->SymInfo.Kind);
|
||||
// FIXME: reintroduce a way to show the index source for debugging.
|
||||
if (I.label.empty())
|
||||
I.label = IndexResult->CompletionLabel;
|
||||
I.label = (IndexResult->Name + IndexResult->Signature).str();
|
||||
if (I.filterText.empty())
|
||||
I.filterText = IndexResult->Name;
|
||||
|
||||
// FIXME(ioeric): support inserting/replacing scope qualifiers.
|
||||
if (I.insertText.empty())
|
||||
I.insertText = Opts.EnableSnippets
|
||||
? IndexResult->CompletionSnippetInsertText
|
||||
: IndexResult->CompletionPlainInsertText;
|
||||
if (I.insertText.empty()) {
|
||||
I.insertText = IndexResult->Name;
|
||||
if (Opts.EnableSnippets)
|
||||
I.insertText += IndexResult->CompletionSnippetSuffix;
|
||||
}
|
||||
|
||||
if (auto *D = IndexResult->Detail) {
|
||||
if (I.documentation.empty())
|
||||
I.documentation = D->Documentation;
|
||||
if (I.detail.empty())
|
||||
I.detail = D->CompletionDetail;
|
||||
I.detail = D->ReturnType;
|
||||
if (auto Inserted = headerToInsertIfNotPresent()) {
|
||||
auto IncludePath = [&]() -> Expected<std::string> {
|
||||
auto ResolvedDeclaring = toHeaderFile(
|
||||
|
|
|
@ -24,31 +24,6 @@ bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
|
|||
StringRef(Chunk.Text).endswith("::");
|
||||
}
|
||||
|
||||
void processPlainTextChunks(const CodeCompletionString &CCS,
|
||||
std::string *LabelOut, std::string *InsertTextOut) {
|
||||
std::string &Label = *LabelOut;
|
||||
std::string &InsertText = *InsertTextOut;
|
||||
for (const auto &Chunk : CCS) {
|
||||
// Informative qualifier chunks only clutter completion results, skip
|
||||
// them.
|
||||
if (isInformativeQualifierChunk(Chunk))
|
||||
continue;
|
||||
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
case CodeCompletionString::CK_Optional:
|
||||
break;
|
||||
case CodeCompletionString::CK_TypedText:
|
||||
InsertText += Chunk.Text;
|
||||
Label += Chunk.Text;
|
||||
break;
|
||||
default:
|
||||
Label += Chunk.Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
|
||||
for (const auto Character : Text) {
|
||||
if (Character == '$' || Character == '}' || Character == '\\')
|
||||
|
@ -57,73 +32,6 @@ void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
|
|||
}
|
||||
}
|
||||
|
||||
void processSnippetChunks(const CodeCompletionString &CCS,
|
||||
std::string *LabelOut, std::string *InsertTextOut) {
|
||||
std::string &Label = *LabelOut;
|
||||
std::string &InsertText = *InsertTextOut;
|
||||
|
||||
unsigned ArgCount = 0;
|
||||
for (const auto &Chunk : CCS) {
|
||||
// Informative qualifier chunks only clutter completion results, skip
|
||||
// them.
|
||||
if (isInformativeQualifierChunk(Chunk))
|
||||
continue;
|
||||
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_TypedText:
|
||||
case CodeCompletionString::CK_Text:
|
||||
Label += Chunk.Text;
|
||||
InsertText += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Optional:
|
||||
// FIXME: Maybe add an option to allow presenting the optional chunks?
|
||||
break;
|
||||
case CodeCompletionString::CK_Placeholder:
|
||||
++ArgCount;
|
||||
InsertText += "${" + std::to_string(ArgCount) + ':';
|
||||
appendEscapeSnippet(Chunk.Text, &InsertText);
|
||||
InsertText += '}';
|
||||
Label += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Informative:
|
||||
// For example, the word "const" for a const method, or the name of
|
||||
// the base class for methods that are part of the base class.
|
||||
Label += Chunk.Text;
|
||||
// Don't put the informative chunks in the insertText.
|
||||
break;
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
// This is retrieved as detail.
|
||||
break;
|
||||
case CodeCompletionString::CK_CurrentParameter:
|
||||
// This should never be present while collecting completion items,
|
||||
// only while collecting overload candidates.
|
||||
llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
|
||||
"CompletionItems");
|
||||
break;
|
||||
case CodeCompletionString::CK_LeftParen:
|
||||
case CodeCompletionString::CK_RightParen:
|
||||
case CodeCompletionString::CK_LeftBracket:
|
||||
case CodeCompletionString::CK_RightBracket:
|
||||
case CodeCompletionString::CK_LeftBrace:
|
||||
case CodeCompletionString::CK_RightBrace:
|
||||
case CodeCompletionString::CK_LeftAngle:
|
||||
case CodeCompletionString::CK_RightAngle:
|
||||
case CodeCompletionString::CK_Comma:
|
||||
case CodeCompletionString::CK_Colon:
|
||||
case CodeCompletionString::CK_SemiColon:
|
||||
case CodeCompletionString::CK_Equal:
|
||||
case CodeCompletionString::CK_HorizontalSpace:
|
||||
InsertText += Chunk.Text;
|
||||
Label += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_VerticalSpace:
|
||||
InsertText += Chunk.Text;
|
||||
// Don't even add a space to the label.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool canRequestComment(const ASTContext &Ctx, const NamedDecl &D,
|
||||
bool CommentsFromHeaders) {
|
||||
if (CommentsFromHeaders)
|
||||
|
@ -199,10 +107,76 @@ getParameterDocComment(const ASTContext &Ctx,
|
|||
return Doc;
|
||||
}
|
||||
|
||||
void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
|
||||
std::string *InsertText, bool EnableSnippets) {
|
||||
return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText)
|
||||
: processPlainTextChunks(CCS, Label, InsertText);
|
||||
void getSignature(const CodeCompletionString &CCS, std::string *Signature,
|
||||
std::string *Snippet, std::string *RequiredQualifiers) {
|
||||
unsigned ArgCount = 0;
|
||||
for (const auto &Chunk : CCS) {
|
||||
// Informative qualifier chunks only clutter completion results, skip
|
||||
// them.
|
||||
if (isInformativeQualifierChunk(Chunk))
|
||||
continue;
|
||||
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_TypedText:
|
||||
// The typed-text chunk is the actual name. We don't record this chunk.
|
||||
// In general our string looks like <qualifiers><name><signature>.
|
||||
// So once we see the name, any text we recorded so far should be
|
||||
// reclassified as qualifiers.
|
||||
if (RequiredQualifiers)
|
||||
*RequiredQualifiers = std::move(*Signature);
|
||||
Signature->clear();
|
||||
Snippet->clear();
|
||||
break;
|
||||
case CodeCompletionString::CK_Text:
|
||||
*Signature += Chunk.Text;
|
||||
*Snippet += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Optional:
|
||||
break;
|
||||
case CodeCompletionString::CK_Placeholder:
|
||||
*Signature += Chunk.Text;
|
||||
++ArgCount;
|
||||
*Snippet += "${" + std::to_string(ArgCount) + ':';
|
||||
appendEscapeSnippet(Chunk.Text, Snippet);
|
||||
*Snippet += '}';
|
||||
break;
|
||||
case CodeCompletionString::CK_Informative:
|
||||
// For example, the word "const" for a const method, or the name of
|
||||
// the base class for methods that are part of the base class.
|
||||
*Signature += Chunk.Text;
|
||||
// Don't put the informative chunks in the snippet.
|
||||
break;
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
// This is not part of the signature.
|
||||
break;
|
||||
case CodeCompletionString::CK_CurrentParameter:
|
||||
// This should never be present while collecting completion items,
|
||||
// only while collecting overload candidates.
|
||||
llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
|
||||
"CompletionItems");
|
||||
break;
|
||||
case CodeCompletionString::CK_LeftParen:
|
||||
case CodeCompletionString::CK_RightParen:
|
||||
case CodeCompletionString::CK_LeftBracket:
|
||||
case CodeCompletionString::CK_RightBracket:
|
||||
case CodeCompletionString::CK_LeftBrace:
|
||||
case CodeCompletionString::CK_RightBrace:
|
||||
case CodeCompletionString::CK_LeftAngle:
|
||||
case CodeCompletionString::CK_RightAngle:
|
||||
case CodeCompletionString::CK_Comma:
|
||||
case CodeCompletionString::CK_Colon:
|
||||
case CodeCompletionString::CK_SemiColon:
|
||||
case CodeCompletionString::CK_Equal:
|
||||
case CodeCompletionString::CK_HorizontalSpace:
|
||||
*Signature += Chunk.Text;
|
||||
*Snippet += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_VerticalSpace:
|
||||
*Snippet += Chunk.Text;
|
||||
// Don't even add a space to the signature.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string formatDocumentation(const CodeCompletionString &CCS,
|
||||
|
@ -235,17 +209,10 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
|
|||
return Result;
|
||||
}
|
||||
|
||||
std::string getDetail(const CodeCompletionString &CCS) {
|
||||
for (const auto &Chunk : CCS) {
|
||||
// Informative qualifier chunks only clutter completion results, skip
|
||||
// them.
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
std::string getReturnType(const CodeCompletionString &CCS) {
|
||||
for (const auto &Chunk : CCS)
|
||||
if (Chunk.Kind == CodeCompletionString::CK_ResultType)
|
||||
return Chunk.Text;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -45,13 +45,15 @@ getParameterDocComment(const ASTContext &Ctx,
|
|||
const CodeCompleteConsumer::OverloadCandidate &Result,
|
||||
unsigned ArgIndex, bool CommentsFromHeaders);
|
||||
|
||||
/// Gets label and insert text for a completion item. For example, for function
|
||||
/// `Foo`, this returns <"Foo(int x, int y)", "Foo"> without snippts enabled.
|
||||
///
|
||||
/// If \p EnableSnippets is true, this will try to use snippet for the insert
|
||||
/// text. Otherwise, the insert text will always be plain text.
|
||||
void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
|
||||
std::string *InsertText, bool EnableSnippets);
|
||||
/// Formats the signature for an item, as a display string and snippet.
|
||||
/// e.g. for const_reference std::vector<T>::at(size_type) const, this returns:
|
||||
/// *Signature = "(size_type) const"
|
||||
/// *Snippet = "(${0:size_type})"
|
||||
/// If set, RequiredQualifiers is the text that must be typed before the name.
|
||||
/// e.g "Base::" when calling a base class member function that's hidden.
|
||||
void getSignature(const CodeCompletionString &CCS, std::string *Signature,
|
||||
std::string *Snippet,
|
||||
std::string *RequiredQualifiers = nullptr);
|
||||
|
||||
/// Assembles formatted documentation for a completion result. This includes
|
||||
/// documentation comments and other relevant information like annotations.
|
||||
|
@ -63,7 +65,7 @@ std::string formatDocumentation(const CodeCompletionString &CCS,
|
|||
|
||||
/// Gets detail to be used as the detail field in an LSP completion item. This
|
||||
/// is usually the return type of a function.
|
||||
std::string getDetail(const CodeCompletionString &CCS);
|
||||
std::string getReturnType(const CodeCompletionString &CCS);
|
||||
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
||||
|
|
|
@ -84,9 +84,8 @@ static void own(Symbol &S, DenseSet<StringRef> &Strings,
|
|||
Intern(S.CanonicalDeclaration.FileURI);
|
||||
Intern(S.Definition.FileURI);
|
||||
|
||||
Intern(S.CompletionLabel);
|
||||
Intern(S.CompletionPlainInsertText);
|
||||
Intern(S.CompletionSnippetInsertText);
|
||||
Intern(S.Signature);
|
||||
Intern(S.CompletionSnippetSuffix);
|
||||
|
||||
if (S.Detail) {
|
||||
// Copy values of StringRefs into arena.
|
||||
|
@ -94,7 +93,7 @@ static void own(Symbol &S, DenseSet<StringRef> &Strings,
|
|||
*Detail = *S.Detail;
|
||||
// Intern the actual strings.
|
||||
Intern(Detail->Documentation);
|
||||
Intern(Detail->CompletionDetail);
|
||||
Intern(Detail->ReturnType);
|
||||
Intern(Detail->IncludeHeader);
|
||||
// Replace the detail pointer with our copy.
|
||||
S.Detail = Detail;
|
||||
|
|
|
@ -125,6 +125,11 @@ namespace clangd {
|
|||
// When adding new unowned data fields to Symbol, remember to update:
|
||||
// - SymbolSlab::Builder in Index.cpp, to copy them to the slab's storage.
|
||||
// - mergeSymbol in Merge.cpp, to properly combine two Symbols.
|
||||
//
|
||||
// A fully documented symbol can be split as:
|
||||
// size_type std::map<k, t>::count(const K& key) const
|
||||
// | Return | Scope |Name| Signature |
|
||||
// We split up these components to allow display flexibility later.
|
||||
struct Symbol {
|
||||
// The ID of the symbol.
|
||||
SymbolID ID;
|
||||
|
@ -152,15 +157,13 @@ struct Symbol {
|
|||
/// Whether or not this symbol is meant to be used for the code completion.
|
||||
/// See also isIndexedForCodeCompletion().
|
||||
bool IsIndexedForCodeCompletion = false;
|
||||
/// A brief description of the symbol that can be displayed in the completion
|
||||
/// candidate list. For example, "Foo(X x, Y y) const" is a label for a
|
||||
/// function.
|
||||
llvm::StringRef CompletionLabel;
|
||||
/// What to insert when completing this symbol (plain text version).
|
||||
llvm::StringRef CompletionPlainInsertText;
|
||||
/// What to insert when completing this symbol (snippet version). This is
|
||||
/// empty if it is the same as the plain insert text above.
|
||||
llvm::StringRef CompletionSnippetInsertText;
|
||||
/// A brief description of the symbol that can be appended in the completion
|
||||
/// candidate list. For example, "(X x, Y y) const" is a function signature.
|
||||
llvm::StringRef Signature;
|
||||
/// What to insert when completing this symbol, after the symbol name.
|
||||
/// This is in LSP snippet syntax (e.g. "({$0})" for a no-args function).
|
||||
/// (When snippets are disabled, the symbol name alone is used).
|
||||
llvm::StringRef CompletionSnippetSuffix;
|
||||
|
||||
/// Optional symbol details that are not required to be set. For example, an
|
||||
/// index fuzzy match can return a large number of symbol candidates, and it
|
||||
|
@ -170,9 +173,9 @@ struct Symbol {
|
|||
struct Details {
|
||||
/// Documentation including comment for the symbol declaration.
|
||||
llvm::StringRef Documentation;
|
||||
/// This is what goes into the LSP detail field in a completion item. For
|
||||
/// example, the result type of a function.
|
||||
llvm::StringRef CompletionDetail;
|
||||
/// Type when this symbol is used in an expression. (Short display form).
|
||||
/// e.g. return type of a function, or type of a variable.
|
||||
llvm::StringRef ReturnType;
|
||||
/// This can be either a URI of the header to be #include'd for this symbol,
|
||||
/// or a literal header quoted with <> or "" that is suitable to be included
|
||||
/// directly. When this is a URI, the exact #include path needs to be
|
||||
|
|
|
@ -96,12 +96,10 @@ mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
|
|||
if (!S.CanonicalDeclaration)
|
||||
S.CanonicalDeclaration = O.CanonicalDeclaration;
|
||||
S.References += O.References;
|
||||
if (S.CompletionLabel == "")
|
||||
S.CompletionLabel = O.CompletionLabel;
|
||||
if (S.CompletionPlainInsertText == "")
|
||||
S.CompletionPlainInsertText = O.CompletionPlainInsertText;
|
||||
if (S.CompletionSnippetInsertText == "")
|
||||
S.CompletionSnippetInsertText = O.CompletionSnippetInsertText;
|
||||
if (S.Signature == "")
|
||||
S.Signature = O.Signature;
|
||||
if (S.CompletionSnippetSuffix == "")
|
||||
S.CompletionSnippetSuffix = O.CompletionSnippetSuffix;
|
||||
|
||||
if (O.Detail) {
|
||||
if (S.Detail) {
|
||||
|
@ -109,8 +107,8 @@ mergeSymbol(const Symbol &L, const Symbol &R, Symbol::Details *Scratch) {
|
|||
*Scratch = *S.Detail;
|
||||
if (Scratch->Documentation == "")
|
||||
Scratch->Documentation = O.Detail->Documentation;
|
||||
if (Scratch->CompletionDetail == "")
|
||||
Scratch->CompletionDetail = O.Detail->CompletionDetail;
|
||||
if (Scratch->ReturnType == "")
|
||||
Scratch->ReturnType = O.Detail->ReturnType;
|
||||
if (Scratch->IncludeHeader == "")
|
||||
Scratch->IncludeHeader = O.Detail->IncludeHeader;
|
||||
S.Detail = Scratch;
|
||||
|
|
|
@ -386,18 +386,13 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
|
|||
*ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator,
|
||||
*CompletionTUInfo,
|
||||
/*IncludeBriefComments*/ false);
|
||||
std::string Label;
|
||||
std::string SnippetInsertText;
|
||||
std::string IgnoredLabel;
|
||||
std::string PlainInsertText;
|
||||
getLabelAndInsertText(*CCS, &Label, &SnippetInsertText,
|
||||
/*EnableSnippets=*/true);
|
||||
getLabelAndInsertText(*CCS, &IgnoredLabel, &PlainInsertText,
|
||||
/*EnableSnippets=*/false);
|
||||
std::string Signature;
|
||||
std::string SnippetSuffix;
|
||||
getSignature(*CCS, &Signature, &SnippetSuffix);
|
||||
std::string Documentation =
|
||||
formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion,
|
||||
/*CommentsFromHeaders=*/true));
|
||||
std::string CompletionDetail = getDetail(*CCS);
|
||||
std::string ReturnType = getReturnType(*CCS);
|
||||
|
||||
std::string Include;
|
||||
if (Opts.CollectIncludePath && shouldCollectIncludePath(S.SymInfo.Kind)) {
|
||||
|
@ -407,12 +402,11 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND,
|
|||
QName, SM, SM.getExpansionLoc(ND.getLocation()), Opts))
|
||||
Include = std::move(*Header);
|
||||
}
|
||||
S.CompletionLabel = Label;
|
||||
S.CompletionPlainInsertText = PlainInsertText;
|
||||
S.CompletionSnippetInsertText = SnippetInsertText;
|
||||
S.Signature = Signature;
|
||||
S.CompletionSnippetSuffix = SnippetSuffix;
|
||||
Symbol::Details Detail;
|
||||
Detail.Documentation = Documentation;
|
||||
Detail.CompletionDetail = CompletionDetail;
|
||||
Detail.ReturnType = ReturnType;
|
||||
Detail.IncludeHeader = Include;
|
||||
S.Detail = &Detail;
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ template <> struct MappingTraits<SymbolInfo> {
|
|||
template <> struct MappingTraits<Symbol::Details> {
|
||||
static void mapping(IO &io, Symbol::Details &Detail) {
|
||||
io.mapOptional("Documentation", Detail.Documentation);
|
||||
io.mapOptional("CompletionDetail", Detail.CompletionDetail);
|
||||
io.mapOptional("ReturnType", Detail.ReturnType);
|
||||
io.mapOptional("IncludeHeader", Detail.IncludeHeader);
|
||||
}
|
||||
};
|
||||
|
@ -110,11 +110,8 @@ template <> struct MappingTraits<Symbol> {
|
|||
IO.mapOptional("References", Sym.References, 0u);
|
||||
IO.mapOptional("IsIndexedForCodeCompletion", Sym.IsIndexedForCodeCompletion,
|
||||
false);
|
||||
IO.mapRequired("CompletionLabel", Sym.CompletionLabel);
|
||||
IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText);
|
||||
|
||||
IO.mapOptional("CompletionSnippetInsertText",
|
||||
Sym.CompletionSnippetInsertText);
|
||||
IO.mapOptional("Signature", Sym.Signature);
|
||||
IO.mapOptional("CompletionSnippetSuffix", Sym.CompletionSnippetSuffix);
|
||||
IO.mapOptional("Detail", NDetail->Opt);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -164,9 +164,6 @@ Symbol sym(StringRef QName, index::SymbolKind Kind, StringRef USRFormat) {
|
|||
}
|
||||
USR += Regex("^.*$").sub(USRFormat, Sym.Name); // e.g. func -> @F@func#
|
||||
Sym.ID = SymbolID(USR);
|
||||
Sym.CompletionPlainInsertText = Sym.Name;
|
||||
Sym.CompletionSnippetInsertText = Sym.Name;
|
||||
Sym.CompletionLabel = Sym.Name;
|
||||
Sym.SymInfo.Kind = Kind;
|
||||
Sym.IsIndexedForCodeCompletion = true;
|
||||
return Sym;
|
||||
|
|
|
@ -23,24 +23,23 @@ public:
|
|||
CCTUInfo(Allocator), Builder(*Allocator, CCTUInfo) {}
|
||||
|
||||
protected:
|
||||
void labelAndInsertText(const CodeCompletionString &CCS,
|
||||
bool EnableSnippets = false) {
|
||||
Label.clear();
|
||||
InsertText.clear();
|
||||
getLabelAndInsertText(CCS, &Label, &InsertText, EnableSnippets);
|
||||
void computeSignature(const CodeCompletionString &CCS) {
|
||||
Signature.clear();
|
||||
Snippet.clear();
|
||||
getSignature(CCS, &Signature, &Snippet);
|
||||
}
|
||||
|
||||
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
|
||||
CodeCompletionTUInfo CCTUInfo;
|
||||
CodeCompletionBuilder Builder;
|
||||
std::string Label;
|
||||
std::string InsertText;
|
||||
std::string Signature;
|
||||
std::string Snippet;
|
||||
};
|
||||
|
||||
TEST_F(CompletionStringTest, Detail) {
|
||||
TEST_F(CompletionStringTest, ReturnType) {
|
||||
Builder.AddResultTypeChunk("result");
|
||||
Builder.AddResultTypeChunk("redundant result no no");
|
||||
EXPECT_EQ(getDetail(*Builder.TakeString()), "result");
|
||||
EXPECT_EQ(getReturnType(*Builder.TakeString()), "result");
|
||||
}
|
||||
|
||||
TEST_F(CompletionStringTest, Documentation) {
|
||||
|
@ -65,31 +64,15 @@ TEST_F(CompletionStringTest, MultipleAnnotations) {
|
|||
"Annotations: Ano1 Ano2 Ano3\n");
|
||||
}
|
||||
|
||||
TEST_F(CompletionStringTest, SimpleLabelAndInsert) {
|
||||
TEST_F(CompletionStringTest, EmptySignature) {
|
||||
Builder.AddTypedTextChunk("X");
|
||||
Builder.AddResultTypeChunk("result no no");
|
||||
labelAndInsertText(*Builder.TakeString());
|
||||
EXPECT_EQ(Label, "X");
|
||||
EXPECT_EQ(InsertText, "X");
|
||||
computeSignature(*Builder.TakeString());
|
||||
EXPECT_EQ(Signature, "");
|
||||
EXPECT_EQ(Snippet, "");
|
||||
}
|
||||
|
||||
TEST_F(CompletionStringTest, FunctionPlainText) {
|
||||
Builder.AddResultTypeChunk("result no no");
|
||||
Builder.AddTypedTextChunk("Foo");
|
||||
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
|
||||
Builder.AddPlaceholderChunk("p1");
|
||||
Builder.AddChunk(CodeCompletionString::CK_Comma);
|
||||
Builder.AddPlaceholderChunk("p2");
|
||||
Builder.AddChunk(CodeCompletionString::CK_RightParen);
|
||||
Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
|
||||
Builder.AddInformativeChunk("const");
|
||||
|
||||
labelAndInsertText(*Builder.TakeString());
|
||||
EXPECT_EQ(Label, "Foo(p1, p2) const");
|
||||
EXPECT_EQ(InsertText, "Foo");
|
||||
}
|
||||
|
||||
TEST_F(CompletionStringTest, FunctionSnippet) {
|
||||
TEST_F(CompletionStringTest, Function) {
|
||||
Builder.AddResultTypeChunk("result no no");
|
||||
Builder.addBriefComment("This comment is ignored");
|
||||
Builder.AddTypedTextChunk("Foo");
|
||||
|
@ -100,13 +83,9 @@ TEST_F(CompletionStringTest, FunctionSnippet) {
|
|||
Builder.AddChunk(CodeCompletionString::CK_RightParen);
|
||||
|
||||
auto *CCS = Builder.TakeString();
|
||||
labelAndInsertText(*CCS);
|
||||
EXPECT_EQ(Label, "Foo(p1, p2)");
|
||||
EXPECT_EQ(InsertText, "Foo");
|
||||
|
||||
labelAndInsertText(*CCS, /*EnableSnippets=*/true);
|
||||
EXPECT_EQ(Label, "Foo(p1, p2)");
|
||||
EXPECT_EQ(InsertText, "Foo(${1:p1}, ${2:p2})");
|
||||
computeSignature(*CCS);
|
||||
EXPECT_EQ(Signature, "(p1, p2)");
|
||||
EXPECT_EQ(Snippet, "(${1:p1}, ${2:p2})");
|
||||
EXPECT_EQ(formatDocumentation(*CCS, "Foo's comment"), "Foo's comment");
|
||||
}
|
||||
|
||||
|
@ -116,18 +95,18 @@ TEST_F(CompletionStringTest, EscapeSnippet) {
|
|||
Builder.AddPlaceholderChunk("$p}1\\");
|
||||
Builder.AddChunk(CodeCompletionString::CK_RightParen);
|
||||
|
||||
labelAndInsertText(*Builder.TakeString(), /*EnableSnippets=*/true);
|
||||
EXPECT_EQ(Label, "Foo($p}1\\)");
|
||||
EXPECT_EQ(InsertText, "Foo(${1:\\$p\\}1\\\\})");
|
||||
computeSignature(*Builder.TakeString());
|
||||
EXPECT_EQ(Signature, "($p}1\\)");
|
||||
EXPECT_EQ(Snippet, "(${1:\\$p\\}1\\\\})");
|
||||
}
|
||||
|
||||
TEST_F(CompletionStringTest, IgnoreInformativeQualifier) {
|
||||
Builder.AddTypedTextChunk("X");
|
||||
Builder.AddInformativeChunk("info ok");
|
||||
Builder.AddInformativeChunk("info no no::");
|
||||
labelAndInsertText(*Builder.TakeString());
|
||||
EXPECT_EQ(Label, "Xinfo ok");
|
||||
EXPECT_EQ(InsertText, "X");
|
||||
computeSignature(*Builder.TakeString());
|
||||
EXPECT_EQ(Signature, "info ok");
|
||||
EXPECT_EQ(Snippet, "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -202,18 +202,15 @@ vector<Ty> make_vector(Arg A) {}
|
|||
bool SeenMakeVector = false;
|
||||
M.fuzzyFind(Req, [&](const Symbol &Sym) {
|
||||
if (Sym.Name == "vector") {
|
||||
EXPECT_EQ(Sym.CompletionLabel, "vector<class Ty>");
|
||||
EXPECT_EQ(Sym.CompletionSnippetInsertText, "vector<${1:class Ty}>");
|
||||
EXPECT_EQ(Sym.CompletionPlainInsertText, "vector");
|
||||
EXPECT_EQ(Sym.Signature, "<class Ty>");
|
||||
EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>");
|
||||
SeenVector = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Sym.Name == "make_vector") {
|
||||
EXPECT_EQ(Sym.CompletionLabel, "make_vector<class Ty>(Arg A)");
|
||||
EXPECT_EQ(Sym.CompletionSnippetInsertText,
|
||||
"make_vector<${1:class Ty}>(${2:Arg A})");
|
||||
EXPECT_EQ(Sym.CompletionPlainInsertText, "make_vector");
|
||||
EXPECT_EQ(Sym.Signature, "<class Ty>(Arg A)");
|
||||
EXPECT_EQ(Sym.CompletionSnippetSuffix, "<${1:class Ty}>(${2:Arg A})");
|
||||
SeenMakeVector = true;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -274,11 +274,11 @@ TEST(MergeTest, Merge) {
|
|||
R.CanonicalDeclaration.FileURI = "file:///right.h";
|
||||
L.References = 1;
|
||||
R.References = 2;
|
||||
L.CompletionPlainInsertText = "f00"; // present in left only
|
||||
R.CompletionSnippetInsertText = "f0{$1:0}"; // present in right only
|
||||
L.Signature = "()"; // present in left only
|
||||
R.CompletionSnippetSuffix = "{$1:0}"; // present in right only
|
||||
Symbol::Details DetL, DetR;
|
||||
DetL.CompletionDetail = "DetL";
|
||||
DetR.CompletionDetail = "DetR";
|
||||
DetL.ReturnType = "DetL";
|
||||
DetR.ReturnType = "DetR";
|
||||
DetR.Documentation = "--doc--";
|
||||
L.Detail = &DetL;
|
||||
R.Detail = &DetR;
|
||||
|
@ -288,10 +288,10 @@ TEST(MergeTest, Merge) {
|
|||
EXPECT_EQ(M.Name, "Foo");
|
||||
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:///left.h");
|
||||
EXPECT_EQ(M.References, 3u);
|
||||
EXPECT_EQ(M.CompletionPlainInsertText, "f00");
|
||||
EXPECT_EQ(M.CompletionSnippetInsertText, "f0{$1:0}");
|
||||
EXPECT_EQ(M.Signature, "()");
|
||||
EXPECT_EQ(M.CompletionSnippetSuffix, "{$1:0}");
|
||||
ASSERT_TRUE(M.Detail);
|
||||
EXPECT_EQ(M.Detail->CompletionDetail, "DetL");
|
||||
EXPECT_EQ(M.Detail->ReturnType, "DetL");
|
||||
EXPECT_EQ(M.Detail->Documentation, "--doc--");
|
||||
}
|
||||
|
||||
|
@ -302,19 +302,19 @@ TEST(MergeTest, PreferSymbolWithDefn) {
|
|||
L.ID = R.ID = SymbolID("hello");
|
||||
L.CanonicalDeclaration.FileURI = "file:/left.h";
|
||||
R.CanonicalDeclaration.FileURI = "file:/right.h";
|
||||
L.CompletionPlainInsertText = "left-insert";
|
||||
R.CompletionPlainInsertText = "right-insert";
|
||||
L.Name = "left";
|
||||
R.Name = "right";
|
||||
|
||||
Symbol M = mergeSymbol(L, R, &Scratch);
|
||||
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/left.h");
|
||||
EXPECT_EQ(M.Definition.FileURI, "");
|
||||
EXPECT_EQ(M.CompletionPlainInsertText, "left-insert");
|
||||
EXPECT_EQ(M.Name, "left");
|
||||
|
||||
R.Definition.FileURI = "file:/right.cpp"; // Now right will be favored.
|
||||
M = mergeSymbol(L, R, &Scratch);
|
||||
EXPECT_EQ(M.CanonicalDeclaration.FileURI, "file:/right.h");
|
||||
EXPECT_EQ(M.Definition.FileURI, "file:/right.cpp");
|
||||
EXPECT_EQ(M.CompletionPlainInsertText, "right-insert");
|
||||
EXPECT_EQ(M.Name, "right");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -36,15 +36,18 @@ using testing::UnorderedElementsAre;
|
|||
using testing::UnorderedElementsAreArray;
|
||||
|
||||
// GMock helpers for matching Symbol.
|
||||
MATCHER_P(Labeled, Label, "") { return arg.CompletionLabel == Label; }
|
||||
MATCHER(HasDetail, "") { return arg.Detail; }
|
||||
MATCHER_P(Detail, D, "") {
|
||||
return arg.Detail && arg.Detail->CompletionDetail == D;
|
||||
MATCHER_P(Labeled, Label, "") {
|
||||
return (arg.Name + arg.Signature).str() == Label;
|
||||
}
|
||||
MATCHER(HasReturnType, "") {
|
||||
return arg.Detail && !arg.Detail->ReturnType.empty();
|
||||
}
|
||||
MATCHER_P(ReturnType, D, "") {
|
||||
return arg.Detail && arg.Detail->ReturnType == D;
|
||||
}
|
||||
MATCHER_P(Doc, D, "") { return arg.Detail && arg.Detail->Documentation == D; }
|
||||
MATCHER_P(Plain, Text, "") { return arg.CompletionPlainInsertText == Text; }
|
||||
MATCHER_P(Snippet, S, "") {
|
||||
return arg.CompletionSnippetInsertText == S;
|
||||
return (arg.Name + arg.CompletionSnippetSuffix).str() == S;
|
||||
}
|
||||
MATCHER_P(QName, Name, "") { return (arg.Scope + arg.Name).str() == Name; }
|
||||
MATCHER_P(DeclURI, P, "") { return arg.CanonicalDeclaration.FileURI == P; }
|
||||
|
@ -656,10 +659,10 @@ TEST_F(SymbolCollectorTest, SymbolWithDocumentation) {
|
|||
Symbols,
|
||||
UnorderedElementsAre(
|
||||
QName("nx"), AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"),
|
||||
Detail("int"), Doc("Foo comment."))));
|
||||
ReturnType("int"), Doc("Foo comment."))));
|
||||
}
|
||||
|
||||
TEST_F(SymbolCollectorTest, PlainAndSnippet) {
|
||||
TEST_F(SymbolCollectorTest, Snippet) {
|
||||
const std::string Header = R"(
|
||||
namespace nx {
|
||||
void f() {}
|
||||
|
@ -667,13 +670,12 @@ TEST_F(SymbolCollectorTest, PlainAndSnippet) {
|
|||
}
|
||||
)";
|
||||
runSymbolCollector(Header, /*Main=*/"");
|
||||
EXPECT_THAT(
|
||||
Symbols,
|
||||
UnorderedElementsAre(
|
||||
QName("nx"),
|
||||
AllOf(QName("nx::f"), Labeled("f()"), Plain("f"), Snippet("f()")),
|
||||
AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Plain("ff"),
|
||||
Snippet("ff(${1:int x}, ${2:double y})"))));
|
||||
EXPECT_THAT(Symbols,
|
||||
UnorderedElementsAre(
|
||||
QName("nx"),
|
||||
AllOf(QName("nx::f"), Labeled("f()"), Snippet("f()")),
|
||||
AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"),
|
||||
Snippet("ff(${1:int x}, ${2:double y})"))));
|
||||
}
|
||||
|
||||
TEST_F(SymbolCollectorTest, YAMLConversions) {
|
||||
|
@ -694,12 +696,9 @@ CanonicalDeclaration:
|
|||
Line: 1
|
||||
Column: 1
|
||||
IsIndexedForCodeCompletion: true
|
||||
CompletionLabel: 'Foo1-label'
|
||||
CompletionFilterText: 'filter'
|
||||
CompletionPlainInsertText: 'plain'
|
||||
Detail:
|
||||
Documentation: 'Foo doc'
|
||||
CompletionDetail: 'int'
|
||||
ReturnType: 'int'
|
||||
...
|
||||
)";
|
||||
const std::string YAML2 = R"(
|
||||
|
@ -719,25 +718,23 @@ CanonicalDeclaration:
|
|||
Line: 1
|
||||
Column: 1
|
||||
IsIndexedForCodeCompletion: false
|
||||
CompletionLabel: 'Foo2-label'
|
||||
CompletionFilterText: 'filter'
|
||||
CompletionPlainInsertText: 'plain'
|
||||
CompletionSnippetInsertText: 'snippet'
|
||||
Signature: '-sig'
|
||||
CompletionSnippetSuffix: '-snippet'
|
||||
...
|
||||
)";
|
||||
|
||||
auto Symbols1 = SymbolsFromYAML(YAML1);
|
||||
|
||||
EXPECT_THAT(Symbols1,
|
||||
UnorderedElementsAre(AllOf(
|
||||
QName("clang::Foo1"), Labeled("Foo1-label"), Doc("Foo doc"),
|
||||
Detail("int"), DeclURI("file:///path/foo.h"),
|
||||
ForCodeCompletion(true))));
|
||||
UnorderedElementsAre(AllOf(QName("clang::Foo1"), Labeled("Foo1"),
|
||||
Doc("Foo doc"), ReturnType("int"),
|
||||
DeclURI("file:///path/foo.h"),
|
||||
ForCodeCompletion(true))));
|
||||
auto Symbols2 = SymbolsFromYAML(YAML2);
|
||||
EXPECT_THAT(Symbols2,
|
||||
UnorderedElementsAre(AllOf(
|
||||
QName("clang::Foo2"), Labeled("Foo2-label"), Not(HasDetail()),
|
||||
DeclURI("file:///path/bar.h"), ForCodeCompletion(false))));
|
||||
EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf(
|
||||
QName("clang::Foo2"), Labeled("Foo2-sig"),
|
||||
Not(HasReturnType()), DeclURI("file:///path/bar.h"),
|
||||
ForCodeCompletion(false))));
|
||||
|
||||
std::string ConcatenatedYAML;
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue