mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This cleans up 90 instances of this warning and reduces the build spew when building on Linux. This helps identify actual issues when building which can get lost in the stream of warning messages. It also helps restore the ability to build the compiler with gcc.
1275 lines
41 KiB
C++
1275 lines
41 KiB
C++
//===--- CodeCompletionOrganizer.cpp --------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CodeCompletionOrganizer.h"
|
|
#include "swift/AST/ASTContext.h"
|
|
#include "swift/AST/Module.h"
|
|
#include "swift/IDE/CodeCompletionResultPrinter.h"
|
|
#include "swift/IDE/FuzzyStringMatcher.h"
|
|
#include "swift/Frontend/Frontend.h"
|
|
#include "swift/Markup/XMLUtils.h"
|
|
#include "clang/Basic/CharInfo.h"
|
|
#include "clang/Basic/Module.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/ilist.h"
|
|
#include "llvm/ADT/ilist_node.h"
|
|
#include <deque>
|
|
|
|
using namespace SourceKit;
|
|
using namespace CodeCompletion;
|
|
using namespace swift;
|
|
using namespace ide;
|
|
|
|
namespace {
|
|
enum class ItemKind : uint8_t {
|
|
None,
|
|
Group,
|
|
Result,
|
|
};
|
|
struct Item {
|
|
std::string name;
|
|
std::string description;
|
|
uint8_t kind : 2;
|
|
uint8_t isExactMatch : 1;
|
|
double matchScore = 0.0; ///< The quality of the filter matching.
|
|
double finalScore = -1.0; ///< The final score including match and context.
|
|
ItemKind getKind() const { return static_cast<ItemKind>(kind); }
|
|
Item(ItemKind k = ItemKind::None)
|
|
: kind(static_cast<decltype(kind)>(k)), isExactMatch(0) {}
|
|
virtual ~Item() {}
|
|
};
|
|
struct Result : public Item {
|
|
Completion *value;
|
|
Result(Completion *result = nullptr)
|
|
: Item(ItemKind::Result), value(result) {}
|
|
static bool classof(const Item *item) {
|
|
return item->getKind() == ItemKind::Result;
|
|
}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
struct CodeCompletion::Group : public Item {
|
|
std::vector<std::unique_ptr<Item>> contents;
|
|
Group() : Item(ItemKind::Group) {}
|
|
static bool classof(const Item *item) {
|
|
return item->getKind() == ItemKind::Group;
|
|
}
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// extendCompletions
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
std::vector<Completion *> SourceKit::CodeCompletion::extendCompletions(
|
|
ArrayRef<SwiftResult *> swiftResults, CompletionSink &sink,
|
|
SwiftCompletionInfo &info, const NameToPopularityMap *nameToPopularity,
|
|
const Options &options, Completion *prefix, bool clearFlair) {
|
|
|
|
ImportDepth depth;
|
|
if (info.swiftASTContext) {
|
|
// Build import depth map.
|
|
depth = ImportDepth(*info.swiftASTContext,
|
|
info.invocation->getFrontendOptions());
|
|
}
|
|
|
|
if (info.completionContext)
|
|
sink.adoptSwiftSink(info.completionContext->getResultSink());
|
|
|
|
std::vector<Completion *> results;
|
|
for (auto *result : swiftResults) {
|
|
CompletionBuilder builder(sink, *result);
|
|
if (result->getSemanticContext() == SemanticContextKind::OtherModule) {
|
|
builder.setModuleImportDepth(depth.lookup(result->getModuleName()));
|
|
|
|
if (info.completionContext->typeContextKind ==
|
|
TypeContextKind::Required &&
|
|
result->getKind() == Completion::ResultKind::Declaration) {
|
|
// FIXME: because other-module results are cached, they will not be
|
|
// given a type-relation of invalid. As a hack, we look at the text of
|
|
// the result type and look for 'Void'.
|
|
bool isVoid = false;
|
|
auto chunks = result->getCompletionString()->getChunks();
|
|
for (auto i = chunks.begin(), e = chunks.end(); i != e; ++i) {
|
|
using ChunkKind = ide::CodeCompletionString::Chunk::ChunkKind;
|
|
bool isVoid = false;
|
|
if (i->is(ChunkKind::TypeAnnotation)) {
|
|
isVoid = i->getText() == "Void";
|
|
break;
|
|
} else if (i->is(ChunkKind::TypeAnnotationBegin)) {
|
|
auto n = i + 1, t = i + 2;
|
|
isVoid =
|
|
// i+1 has text 'Void'.
|
|
n != e && n->hasText() && n->getText() == "Void" &&
|
|
// i+2 terminates the group.
|
|
(t == e || t->endsPreviousNestedGroup(i->getNestingLevel()));
|
|
break;
|
|
}
|
|
}
|
|
if (isVoid)
|
|
builder.setExpectedTypeRelation(
|
|
Completion::ExpectedTypeRelation::Invalid);
|
|
}
|
|
}
|
|
|
|
if (prefix) {
|
|
builder.setPrefix(prefix->getCompletionString());
|
|
builder.setSemanticContext(prefix->getSemanticContext());
|
|
}
|
|
|
|
if (clearFlair) {
|
|
builder.setFlair(CodeCompletionFlair());
|
|
builder.setSemanticContext(SemanticContextKind::None);
|
|
}
|
|
|
|
// If this result is not from the current module, try to get a popularity
|
|
// score for it.
|
|
if (nameToPopularity) {
|
|
builder.setPopularityFactor(
|
|
nameToPopularity->lookup(builder.getOriginalName()));
|
|
}
|
|
|
|
results.push_back(builder.finish());
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
bool SourceKit::CodeCompletion::addCustomCompletions(
|
|
CompletionSink &sink, std::vector<Completion *> &completions,
|
|
ArrayRef<CustomCompletionInfo> customCompletions,
|
|
CompletionKind completionKind) {
|
|
|
|
auto addCompletion = [&](CustomCompletionInfo customCompletion) {
|
|
using Chunk = CodeCompletionString::Chunk;
|
|
auto nameCopy = copyString(sink.allocator, customCompletion.Name);
|
|
auto chunk = Chunk::createWithText(Chunk::ChunkKind::Text, 0, nameCopy);
|
|
auto *completionString =
|
|
CodeCompletionString::create(sink.allocator, chunk);
|
|
CodeCompletion::SwiftResult swiftResult(
|
|
CodeCompletion::SwiftResult::ResultKind::Pattern,
|
|
SemanticContextKind::Local, CodeCompletionFlairBit::ExpressionSpecific,
|
|
/*NumBytesToErase=*/0, completionString,
|
|
CodeCompletionResult::ExpectedTypeRelation::Unknown);
|
|
|
|
CompletionBuilder builder(sink, swiftResult);
|
|
builder.setCustomKind(customCompletion.Kind);
|
|
completions.push_back(builder.finish());
|
|
};
|
|
|
|
bool changed = false;
|
|
|
|
for (auto &custom : customCompletions) {
|
|
switch (completionKind) {
|
|
case CompletionKind::StmtOrExpr:
|
|
if (custom.Contexts.contains(CustomCompletionInfo::Stmt)) {
|
|
changed = true;
|
|
addCompletion(custom);
|
|
}
|
|
break;
|
|
case CompletionKind::PostfixExprBeginning:
|
|
case CompletionKind::CallArg:
|
|
case CompletionKind::ReturnStmtExpr:
|
|
case CompletionKind::YieldStmtExpr:
|
|
if (custom.Contexts.contains(CustomCompletionInfo::Expr)) {
|
|
changed = true;
|
|
addCompletion(custom);
|
|
}
|
|
break;
|
|
case CompletionKind::ForEachSequence:
|
|
if (custom.Contexts.contains(CustomCompletionInfo::ForEachSequence)) {
|
|
changed = true;
|
|
addCompletion(custom);
|
|
}
|
|
break;
|
|
case CompletionKind::TypeDeclResultBeginning:
|
|
case CompletionKind::TypeSimpleBeginning:
|
|
if (custom.Contexts.contains(CustomCompletionInfo::Type)) {
|
|
changed = true;
|
|
addCompletion(custom);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CodeCompletionOrganizer::Impl declaration
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
class CodeCompletionOrganizer::Impl {
|
|
std::unique_ptr<Group> rootGroup;
|
|
CompletionKind completionKind;
|
|
TypeContextKind typeContextKind;
|
|
|
|
void groupStemsRecursive(Group *group, bool recurseIntoNewGroups,
|
|
StringRef(getStem)(StringRef));
|
|
|
|
public:
|
|
Impl(CompletionKind kind, TypeContextKind typeContextKind);
|
|
|
|
void addCompletionsWithFilter(ArrayRef<Completion *> completions,
|
|
StringRef filterText, Options options,
|
|
const FilterRules &rules,
|
|
Completion *&exactMatch);
|
|
|
|
void sort(Options options);
|
|
|
|
void groupOverloads() {
|
|
groupStemsRecursive(
|
|
rootGroup.get(), /*recurseIntoNewGroups*/ false, [](StringRef name) {
|
|
auto endIdx = name.find_first_of("([");
|
|
if (endIdx == 0 && !name.empty())
|
|
return name.slice(0, 1); // [ => subscript, ( => initializer
|
|
return name.slice(0, endIdx);
|
|
});
|
|
}
|
|
|
|
void groupStems() {
|
|
groupStemsRecursive(rootGroup.get(), /*recurseIntoNewGroups*/ true,
|
|
[](StringRef name) {
|
|
unsigned i = 0;
|
|
while (i < name.size()) {
|
|
char c = name[i];
|
|
// FIXME: unicode
|
|
if (i > 0 && clang::isUppercase(c) &&
|
|
!clang::isUppercase(name[i - 1]))
|
|
break;
|
|
if (!clang::isAlphanumeric(c))
|
|
break;
|
|
++i;
|
|
}
|
|
if (i == 0 && !name.empty())
|
|
return name.slice(0, 1);
|
|
return name.slice(0, i);
|
|
});
|
|
}
|
|
|
|
CodeCompletionViewRef takeView() {
|
|
assert(rootGroup);
|
|
auto view = std::make_shared<CodeCompletionView>();
|
|
view->rootGroup = rootGroup.release(); // View takes ownership.
|
|
|
|
// Reset fields manually in case the move constructors don't.
|
|
rootGroup = nullptr;
|
|
|
|
return view;
|
|
}
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CodeCompletionOrganizer implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
CodeCompletionOrganizer::CodeCompletionOrganizer(const Options &options,
|
|
CompletionKind kind,
|
|
TypeContextKind typeContextKind)
|
|
: impl(*new Impl(kind, typeContextKind)), options(options) {}
|
|
CodeCompletionOrganizer::~CodeCompletionOrganizer() { delete &impl; }
|
|
|
|
void CodeCompletionOrganizer::preSortCompletions(
|
|
llvm::MutableArrayRef<Completion *> completions) {
|
|
// We do a case-sensitive sort here, then do a case-insensitive sort after any
|
|
// name-based grouping.
|
|
|
|
llvm::array_pod_sort(completions.begin(), completions.end(),
|
|
[](Completion *const *a, Completion *const *b) {
|
|
|
|
// Sort first by filter name (case-sensitive).
|
|
if (int primary = (*a)->getName().compare((*b)->getName()))
|
|
return primary;
|
|
|
|
// Next, sort by full description text.
|
|
return (*a)->getDescription().compare((*b)->getDescription());
|
|
});
|
|
}
|
|
|
|
void CodeCompletionOrganizer::addCompletionsWithFilter(
|
|
ArrayRef<Completion *> completions, StringRef filterText,
|
|
const FilterRules &rules, Completion *&exactMatch) {
|
|
impl.addCompletionsWithFilter(completions, filterText, options, rules,
|
|
exactMatch);
|
|
}
|
|
|
|
void CodeCompletionOrganizer::groupAndSort(const Options &options) {
|
|
if (options.groupStems)
|
|
impl.groupStems();
|
|
else if (options.groupOverloads)
|
|
impl.groupOverloads();
|
|
|
|
impl.sort(options);
|
|
}
|
|
|
|
CodeCompletionViewRef CodeCompletionOrganizer::takeResultsView() {
|
|
return impl.takeView();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CodeCompletionOrganizer::Impl utilities
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static std::unique_ptr<Group> make_group(StringRef name) {
|
|
auto g = std::make_unique<Group>();
|
|
g->name = name.str();
|
|
g->description = name.str();
|
|
return g;
|
|
}
|
|
|
|
static std::unique_ptr<Result> make_result(Completion *result) {
|
|
auto r = std::make_unique<Result>(result);
|
|
r->name = result->getName().str();
|
|
r->description = result->getDescription().str();
|
|
return r;
|
|
}
|
|
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CodeCompletionOrganizer::Impl implementation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
CodeCompletionOrganizer::Impl::Impl(CompletionKind kind, TypeContextKind typeContextKind)
|
|
: completionKind(kind), typeContextKind(typeContextKind) {
|
|
assert(!rootGroup && "initialized twice");
|
|
rootGroup = make_group("");
|
|
}
|
|
|
|
static bool matchesExpectedStyle(Completion *completion, NameStyle style) {
|
|
switch (completion->getAssociatedDeclKind()) {
|
|
case CodeCompletionDeclKind::Class:
|
|
case CodeCompletionDeclKind::Struct:
|
|
case CodeCompletionDeclKind::Enum:
|
|
case CodeCompletionDeclKind::Protocol:
|
|
case CodeCompletionDeclKind::TypeAlias:
|
|
case CodeCompletionDeclKind::AssociatedType:
|
|
return style.possiblyUpperCamelCase();
|
|
|
|
case CodeCompletionDeclKind::StaticMethod:
|
|
case CodeCompletionDeclKind::InstanceMethod:
|
|
case CodeCompletionDeclKind::FreeFunction:
|
|
case CodeCompletionDeclKind::StaticVar:
|
|
case CodeCompletionDeclKind::InstanceVar:
|
|
case CodeCompletionDeclKind::LocalVar:
|
|
case CodeCompletionDeclKind::GlobalVar:
|
|
return style.possiblyLowerCamelCase();
|
|
|
|
default:
|
|
return true; // Conservatively say yes.
|
|
}
|
|
}
|
|
|
|
static bool isHighPriorityKeyword(CodeCompletionKeywordKind kind) {
|
|
switch (kind) {
|
|
case CodeCompletionKeywordKind::kw_let:
|
|
case CodeCompletionKeywordKind::kw_var:
|
|
case CodeCompletionKeywordKind::kw_if:
|
|
case CodeCompletionKeywordKind::kw_for:
|
|
case CodeCompletionKeywordKind::kw_while:
|
|
case CodeCompletionKeywordKind::kw_return:
|
|
case CodeCompletionKeywordKind::kw_func:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FilterRules::hideFilterName(StringRef name) const {
|
|
auto I = hideByFilterName.find(name);
|
|
if (I != hideByFilterName.end())
|
|
return I->getValue();
|
|
return hideAll;
|
|
}
|
|
|
|
bool FilterRules::hideCompletion(Completion *completion) const {
|
|
return hideCompletion(completion, completion->getName(),
|
|
completion->getDescription(),
|
|
completion->getCustomKind());
|
|
}
|
|
|
|
bool FilterRules::hideCompletion(SwiftResult *completion, StringRef filterName,
|
|
StringRef description,
|
|
void *customKind) const {
|
|
|
|
if (!description.empty()) {
|
|
auto I = hideByDescription.find(description);
|
|
if (I != hideByDescription.end())
|
|
return I->getValue();
|
|
}
|
|
|
|
if (!filterName.empty()) {
|
|
auto I = hideByFilterName.find(filterName);
|
|
if (I != hideByFilterName.end())
|
|
return I->getValue();
|
|
}
|
|
|
|
switch (completion->getKind()) {
|
|
case Completion::ResultKind::BuiltinOperator:
|
|
case Completion::ResultKind::Declaration:
|
|
break;
|
|
case Completion::ResultKind::Keyword: {
|
|
auto I = hideKeyword.find(completion->getKeywordKind());
|
|
if (I != hideKeyword.end())
|
|
return I->second;
|
|
if (hideAllKeywords)
|
|
return true;
|
|
break;
|
|
}
|
|
case Completion::ResultKind::Pattern: {
|
|
if (customKind) {
|
|
// FIXME: individual custom completions
|
|
if (hideCustomCompletions)
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case Completion::ResultKind::Literal: {
|
|
auto I = hideValueLiteral.find(completion->getLiteralKind());
|
|
if (I != hideValueLiteral.end())
|
|
return I->second;
|
|
if (hideAllValueLiterals)
|
|
return true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!completion->getModuleName().empty()) {
|
|
// FIXME: try each submodule chain starting from the most specific.
|
|
auto M = hideModule.find(completion->getModuleName());
|
|
if (M != hideModule.end())
|
|
return M->getValue();
|
|
}
|
|
|
|
return hideAll;
|
|
}
|
|
|
|
void CodeCompletionOrganizer::Impl::addCompletionsWithFilter(
|
|
ArrayRef<Completion *> completions, StringRef filterText, Options options,
|
|
const FilterRules &rules, Completion *&exactMatch) {
|
|
assert(rootGroup);
|
|
|
|
auto &contents = rootGroup->contents;
|
|
|
|
// If we have no filter text, add all non-hidden results.
|
|
if (filterText.empty()) {
|
|
bool hideLowPriority =
|
|
options.hideLowPriority &&
|
|
completionKind != CompletionKind::TypeDeclResultBeginning &&
|
|
completionKind != CompletionKind::TypeSimpleBeginning &&
|
|
completionKind != CompletionKind::PostfixExpr;
|
|
for (Completion *completion : completions) {
|
|
if (rules.hideCompletion(completion))
|
|
continue;
|
|
|
|
if (options.hideLowPriority &&
|
|
(completion->isNotRecommended() ||
|
|
completion->getExpectedTypeRelation() ==
|
|
Completion::ExpectedTypeRelation::Invalid))
|
|
continue;
|
|
|
|
NameStyle style(completion->getName());
|
|
bool hideUnderscore = options.hideUnderscores && style.leadingUnderscores;
|
|
if (hideUnderscore && options.reallyHideAllUnderscores)
|
|
continue;
|
|
|
|
bool hideByNameStyle =
|
|
options.hideByNameStyle &&
|
|
completion->getKind() == Completion::ResultKind::Declaration &&
|
|
!matchesExpectedStyle(completion, style);
|
|
|
|
hideByNameStyle |= hideUnderscore;
|
|
|
|
switch (completion->getSemanticContext()) {
|
|
case SemanticContextKind::Super:
|
|
case SemanticContextKind::OutsideNominal:
|
|
if (hideUnderscore)
|
|
continue;
|
|
break;
|
|
case SemanticContextKind::OtherModule:
|
|
case SemanticContextKind::None:
|
|
if (auto depth = completion->getModuleImportDepth()) {
|
|
if (*depth == 0) // Treat as if it's "thismodule"
|
|
break;
|
|
}
|
|
if (completion->getExpectedTypeRelation() >=
|
|
Completion::ExpectedTypeRelation::Convertible ||
|
|
(completion->getKind() == Completion::ResultKind::Literal &&
|
|
completionKind != CompletionKind::StmtOrExpr &&
|
|
typeContextKind < TypeContextKind::Required))
|
|
break;
|
|
|
|
if (completion->getKind() == Completion::ResultKind::Keyword &&
|
|
completionKind == CompletionKind::StmtOrExpr &&
|
|
isHighPriorityKeyword(completion->getKeywordKind()))
|
|
break;
|
|
|
|
if (hideByNameStyle || hideLowPriority)
|
|
continue;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Build wrapper and add to results.
|
|
contents.push_back(make_result(completion));
|
|
}
|
|
return;
|
|
}
|
|
|
|
FuzzyStringMatcher pattern(filterText);
|
|
pattern.normalize = true;
|
|
for (Completion *completion : completions) {
|
|
if (rules.hideCompletion(completion))
|
|
continue;
|
|
|
|
// Hide literals other than the ones that are also keywords if they don't
|
|
// match the expected types.
|
|
if (completion->getKind() == Completion::ResultKind::Literal &&
|
|
typeContextKind == TypeContextKind::Required &&
|
|
completion->getExpectedTypeRelation() <
|
|
Completion::ExpectedTypeRelation::Convertible &&
|
|
completion->getLiteralKind() !=
|
|
CodeCompletionLiteralKind::BooleanLiteral &&
|
|
completion->getLiteralKind() != CodeCompletionLiteralKind::NilLiteral)
|
|
continue;
|
|
|
|
bool match = false;
|
|
if (options.fuzzyMatching && filterText.size() >= options.minFuzzyLength) {
|
|
match = pattern.matchesCandidate(completion->getName());
|
|
} else {
|
|
match = completion->getName().startswith_insensitive(filterText);
|
|
}
|
|
|
|
bool isExactMatch =
|
|
match && completion->getName().equals_insensitive(filterText);
|
|
|
|
if (isExactMatch) {
|
|
if (!exactMatch) { // first match
|
|
exactMatch = completion;
|
|
} else if (completion->getName() != exactMatch->getName()) {
|
|
if (completion->getName() == filterText && // first case-sensitive match
|
|
exactMatch->getName() != filterText)
|
|
exactMatch = completion;
|
|
else if (pattern.scoreCandidate(completion->getName()) > // better match
|
|
pattern.scoreCandidate(exactMatch->getName()))
|
|
exactMatch = completion;
|
|
}
|
|
|
|
match = (options.addInnerResults || options.addInnerOperators)
|
|
? options.includeExactMatch
|
|
: true;
|
|
}
|
|
|
|
// Build wrapper and add to results.
|
|
if (match) {
|
|
auto wrapper = make_result(completion);
|
|
if (options.fuzzyMatching) {
|
|
wrapper->matchScore = pattern.scoreCandidate(completion->getName());
|
|
}
|
|
wrapper->isExactMatch = isExactMatch;
|
|
|
|
contents.push_back(std::move(wrapper));
|
|
}
|
|
}
|
|
}
|
|
|
|
static double getSemanticContextScore(bool useImportDepth,
|
|
Completion *completion) {
|
|
double order = -1.0;
|
|
switch (completion->getSemanticContext()) {
|
|
case SemanticContextKind::Local: order = 1; break;
|
|
case SemanticContextKind::CurrentNominal: order = 2; break;
|
|
case SemanticContextKind::Super: order = 3; break;
|
|
case SemanticContextKind::OutsideNominal: order = 4; break;
|
|
case SemanticContextKind::CurrentModule: order = 5; break;
|
|
case SemanticContextKind::OtherModule: {
|
|
unsigned depth = Completion::maxModuleImportDepth + 1; // unknown > known
|
|
if (useImportDepth && completion->getModuleImportDepth())
|
|
depth = *completion->getModuleImportDepth();
|
|
// We treat depth == 0 the same as CurrentModule.
|
|
// Note: there is a gap in between that we use for keywords.
|
|
order = (depth == 0) ? 5.0 : 6.0; // Base value.
|
|
order += double(depth) / (Completion::maxModuleImportDepth + 1);
|
|
assert((depth == 0 && order == 5.0) ||
|
|
(depth && 6.0 <= order && order <= 7.0));
|
|
break;
|
|
}
|
|
case SemanticContextKind::None: {
|
|
order =
|
|
completion->getKind() == Completion::ResultKind::Keyword ? 5.5 : 8.0;
|
|
break;
|
|
}
|
|
}
|
|
assert(0.0 <= order && order <= 8.0);
|
|
return (8.0 - order) / 8.0;
|
|
}
|
|
|
|
static double combinedScore(const Options &options, double matchScore,
|
|
Completion *completion) {
|
|
|
|
double score = matchScore * options.fuzzyMatchWeight;
|
|
score += getSemanticContextScore(options.useImportDepth, completion) *
|
|
options.semanticContextWeight;
|
|
|
|
PopularityFactor popularity = completion->getPopularityFactor();
|
|
if (popularity.isPopular())
|
|
score += popularity.rawValue + options.popularityBonus;
|
|
else if (popularity.isUnpopular())
|
|
score += popularity.rawValue - options.popularityBonus;
|
|
|
|
return score;
|
|
}
|
|
|
|
static int compareResultName(Item &a, Item &b) {
|
|
// Sort first by filter name (case-insensitive).
|
|
if (int primary = StringRef(a.name).compare_insensitive(b.name))
|
|
return primary;
|
|
|
|
// Next, sort by full description text.
|
|
return a.description.compare(b.description);
|
|
}
|
|
|
|
namespace {
|
|
enum class ResultBucket {
|
|
NotRecommended,
|
|
Normal,
|
|
Literal,
|
|
NormalTypeMatch,
|
|
LiteralTypeMatch,
|
|
HighPriorityKeyword,
|
|
Operator,
|
|
ExpressionSpecific,
|
|
ExactMatch,
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
static ResultBucket getResultBucket(Item &item, bool hasRequiredTypes,
|
|
bool skipMetaGroups = false) {
|
|
if (item.isExactMatch && !skipMetaGroups)
|
|
return ResultBucket::ExactMatch;
|
|
|
|
if (isa<Group>(item))
|
|
return ResultBucket::Normal; // FIXME: take best contained result.
|
|
auto *completion = cast<Result>(item).value;
|
|
|
|
if (completion->isNotRecommended() && !skipMetaGroups)
|
|
return ResultBucket::NotRecommended;
|
|
|
|
if (!skipMetaGroups) {
|
|
auto flair = completion->getFlair();
|
|
if (flair.contains(CodeCompletionFlairBit::ExpressionSpecific) ||
|
|
flair.contains(CodeCompletionFlairBit::SuperChain) ||
|
|
flair.contains(CodeCompletionFlairBit::ArgumentLabels))
|
|
return ResultBucket::ExpressionSpecific;
|
|
}
|
|
|
|
if (completion->isOperator())
|
|
return ResultBucket::Operator;
|
|
|
|
switch (completion->getKind()) {
|
|
case Completion::ResultKind::Literal:
|
|
if (completion->getExpectedTypeRelation() >=
|
|
Completion::ExpectedTypeRelation::Convertible) {
|
|
return ResultBucket::LiteralTypeMatch;
|
|
} else if (!hasRequiredTypes) {
|
|
return ResultBucket::Literal;
|
|
} else {
|
|
// When we have type context, we still show literals that are keywords,
|
|
// but we treat them as keywords instead of literals for prioritization.
|
|
return ResultBucket::Normal;
|
|
}
|
|
case Completion::ResultKind::Keyword:
|
|
return isHighPriorityKeyword(completion->getKeywordKind())
|
|
? ResultBucket::HighPriorityKeyword
|
|
: ResultBucket::Normal;
|
|
case Completion::ResultKind::Pattern:
|
|
case Completion::ResultKind::Declaration:
|
|
switch (completion->getExpectedTypeRelation()) {
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::Convertible:
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::Identical:
|
|
return ResultBucket::NormalTypeMatch;
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::NotApplicable:
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::Unknown:
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::Unrelated:
|
|
return ResultBucket::Normal;
|
|
case swift::ide::CodeCompletionResult::ExpectedTypeRelation::Invalid:
|
|
if (!skipMetaGroups)
|
|
return ResultBucket::NotRecommended;
|
|
return ResultBucket::Normal;
|
|
}
|
|
case Completion::ResultKind::BuiltinOperator:
|
|
llvm_unreachable("operators should be handled above");
|
|
}
|
|
}
|
|
|
|
static int compareHighPriorityKeywords(Item &a_, Item &b_) {
|
|
static CodeCompletionKeywordKind order[] = {
|
|
CodeCompletionKeywordKind::kw_let,
|
|
CodeCompletionKeywordKind::kw_var,
|
|
CodeCompletionKeywordKind::kw_if,
|
|
CodeCompletionKeywordKind::kw_for,
|
|
CodeCompletionKeywordKind::kw_while,
|
|
CodeCompletionKeywordKind::kw_return,
|
|
CodeCompletionKeywordKind::kw_func,
|
|
};
|
|
auto size = sizeof(order) / sizeof(order[0]);
|
|
|
|
auto getIndex = [=](Item &item) {
|
|
auto I = std::find(order, &order[size], cast<Result>(item).value->getKeywordKind());
|
|
assert(I != &order[size]);
|
|
return std::distance(order, I);
|
|
};
|
|
|
|
auto a = getIndex(a_);
|
|
auto b = getIndex(b_);
|
|
return a < b ? -1 : (b < a ? 1 : 0);
|
|
}
|
|
|
|
static int compareLiterals(Item &a_, Item &b_) {
|
|
static CodeCompletionLiteralKind order[] = {
|
|
CodeCompletionLiteralKind::IntegerLiteral,
|
|
CodeCompletionLiteralKind::StringLiteral,
|
|
CodeCompletionLiteralKind::BooleanLiteral,
|
|
CodeCompletionLiteralKind::ColorLiteral,
|
|
CodeCompletionLiteralKind::ImageLiteral,
|
|
CodeCompletionLiteralKind::ArrayLiteral,
|
|
CodeCompletionLiteralKind::DictionaryLiteral,
|
|
CodeCompletionLiteralKind::Tuple,
|
|
CodeCompletionLiteralKind::NilLiteral,
|
|
};
|
|
auto size = sizeof(order) / sizeof(order[0]);
|
|
|
|
auto getIndex = [=](Item &item) {
|
|
auto I = std::find(order, &order[size], cast<Result>(item).value->getLiteralKind());
|
|
assert(I != &order[size]);
|
|
return std::distance(order, I);
|
|
};
|
|
|
|
auto a = getIndex(a_);
|
|
auto b = getIndex(b_);
|
|
|
|
if (a != b)
|
|
return a < b ? -1 : 1;
|
|
|
|
// Sort true before false instead of alphabetically.
|
|
if (cast<Result>(a_).value->getLiteralKind() == CodeCompletionLiteralKind::BooleanLiteral)
|
|
return b_.name.compare(a_.name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int compareOperators(Item &a_, Item &b_) {
|
|
using CCOK = CodeCompletionOperatorKind;
|
|
static CCOK order[] = {
|
|
CCOK::Dot, // .
|
|
CCOK::QuestionDot, // ?.
|
|
CCOK::Bang, // !
|
|
CCOK::LParen, // ( -- not really an operator, but treated as one in some
|
|
// cases.
|
|
CCOK::Eq, // =
|
|
|
|
CCOK::EqEq, // ==
|
|
CCOK::NotEq, // !=
|
|
CCOK::Less, // <
|
|
CCOK::Greater, // >
|
|
CCOK::LessEq, // <=
|
|
CCOK::GreaterEq, // >=
|
|
|
|
CCOK::Plus, // +
|
|
CCOK::Minus, // -
|
|
CCOK::Star, // *
|
|
CCOK::Slash, // /
|
|
CCOK::Modulo, // %
|
|
|
|
CCOK::PlusEq, // +=
|
|
CCOK::MinusEq, // -=
|
|
CCOK::StarEq, // *=
|
|
CCOK::SlashEq, // /=
|
|
CCOK::ModuloEq, // %=
|
|
|
|
CCOK::AmpAmp, // &&
|
|
CCOK::PipePipe, // ||
|
|
|
|
CCOK::Unknown,
|
|
|
|
CCOK::Amp, // &
|
|
CCOK::Pipe, // |
|
|
CCOK::Caret, // ^
|
|
CCOK::LessLess, // <<
|
|
CCOK::GreaterGreater, // >>
|
|
|
|
CCOK::AmpEq, // &=
|
|
CCOK::PipeEq, // |=
|
|
CCOK::CaretEq, // ^=
|
|
CCOK::LessLessEq, // <<=
|
|
CCOK::GreaterGreaterEq, // >>=
|
|
|
|
// Range
|
|
CCOK::DotDotDot, // ...
|
|
CCOK::DotDotLess, // ..<
|
|
|
|
CCOK::AmpStar, // &*
|
|
CCOK::AmpPlus, // &+
|
|
CCOK::AmpMinus, // &-
|
|
|
|
// Misc.
|
|
CCOK::EqEqEq, // ===
|
|
CCOK::NotEqEq, // !==
|
|
CCOK::TildeEq, // ~=
|
|
};
|
|
auto size = sizeof(order) / sizeof(order[0]);
|
|
|
|
auto getIndex = [=](Item &item) {
|
|
auto I = std::find(order, &order[size],
|
|
cast<Result>(item).value->getOperatorKind());
|
|
assert(I != &order[size]);
|
|
return std::distance(order, I);
|
|
};
|
|
|
|
auto a = getIndex(a_);
|
|
auto b = getIndex(b_);
|
|
return a < b ? -1 : (b < a ? 1 : 0);
|
|
}
|
|
|
|
static bool isTopNonLiteralResult(Item &item, ResultBucket literalBucket) {
|
|
if (isa<Group>(item))
|
|
return true; // FIXME: should have a semantic context for groups.
|
|
auto *completion = cast<Result>(item).value;
|
|
|
|
switch (literalBucket) {
|
|
case ResultBucket::Literal:
|
|
return completion->getSemanticContext() <=
|
|
SemanticContextKind::CurrentNominal;
|
|
case ResultBucket::LiteralTypeMatch:
|
|
return completion->getExpectedTypeRelation() >=
|
|
Completion::ExpectedTypeRelation::Convertible;
|
|
default:
|
|
llvm_unreachable("invalid literal bucket");
|
|
}
|
|
}
|
|
|
|
static void sortTopN(const Options &options, Group *group,
|
|
bool hasRequiredTypes) {
|
|
|
|
auto &contents = group->contents;
|
|
if (contents.empty() || options.showTopNonLiteralResults == 0)
|
|
return;
|
|
|
|
auto best = getResultBucket(*contents[0], hasRequiredTypes);
|
|
if (best == ResultBucket::LiteralTypeMatch || best == ResultBucket::Literal) {
|
|
|
|
unsigned beginNewIndex = 0;
|
|
unsigned endNewIndex = 0;
|
|
for (unsigned i = 1; i < contents.size(); ++i) {
|
|
auto bucket = getResultBucket(*contents[i], hasRequiredTypes);
|
|
if (bucket < best) {
|
|
// This algorithm assumes we don't have both literal and
|
|
// literal-type-match at the start of the list.
|
|
assert(bucket != ResultBucket::Literal);
|
|
if (isTopNonLiteralResult(*contents[i], best)) {
|
|
beginNewIndex = i;
|
|
endNewIndex = beginNewIndex + 1;
|
|
for (; endNewIndex < contents.size() &&
|
|
endNewIndex < beginNewIndex + options.showTopNonLiteralResults;
|
|
++endNewIndex) {
|
|
if (!isTopNonLiteralResult(*contents[endNewIndex], best))
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!beginNewIndex)
|
|
return;
|
|
|
|
assert(endNewIndex > beginNewIndex && endNewIndex <= contents.size());
|
|
|
|
// Temporarily copy the first result to temporary storage.
|
|
SmallVector<Item *, 16> firstResults;
|
|
for (unsigned i = 0; i < endNewIndex; ++i) {
|
|
firstResults.push_back(contents[i].release());
|
|
}
|
|
|
|
// Swap the literals with the next few results.
|
|
for (unsigned ci = 0, i = beginNewIndex; i < endNewIndex; ++i, ++ci) {
|
|
assert(ci < contents.size() && !contents[ci]);
|
|
contents[ci] = std::unique_ptr<Item>(firstResults[i]);
|
|
}
|
|
unsigned topN = endNewIndex - beginNewIndex;
|
|
assert(topN <= options.showTopNonLiteralResults);
|
|
for (unsigned ci = topN, i = 0; i < beginNewIndex; ++i, ++ci) {
|
|
assert(ci < contents.size() && !contents[ci]);
|
|
contents[ci] = std::unique_ptr<Item>(firstResults[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sortRecursive(const Options &options, Group *group,
|
|
bool hasRequiredTypes) {
|
|
// Sort all of the subgroups first, and fill in the bucket for each result.
|
|
auto &contents = group->contents;
|
|
double best = -1.0;
|
|
for (auto &item : contents) {
|
|
if (auto *g = dyn_cast<Group>(item.get())) {
|
|
sortRecursive(options, g, hasRequiredTypes);
|
|
} else {
|
|
Result *r = cast<Result>(item.get());
|
|
item->finalScore = combinedScore(options, item->matchScore, r->value);
|
|
}
|
|
|
|
if (item->finalScore > best)
|
|
best = item->finalScore;
|
|
}
|
|
|
|
group->finalScore = best;
|
|
|
|
// Now sort the group itself.
|
|
|
|
if (options.sortByName) {
|
|
llvm::array_pod_sort(contents.begin(), contents.end(),
|
|
[](const std::unique_ptr<Item> *a, const std::unique_ptr<Item> *b) {
|
|
return compareResultName(**a, **b);
|
|
});
|
|
return;
|
|
}
|
|
|
|
std::sort(contents.begin(), contents.end(), [=](const std::unique_ptr<Item> &a_, const std::unique_ptr<Item> &b_) {
|
|
Item &a = *a_;
|
|
Item &b = *b_;
|
|
|
|
auto bucketA = getResultBucket(a, hasRequiredTypes);
|
|
auto bucketB = getResultBucket(b, hasRequiredTypes);
|
|
if (bucketA < bucketB)
|
|
return false;
|
|
else if (bucketB < bucketA)
|
|
return true;
|
|
|
|
// Try again, skipping any meta groups like "expr-specific" in case that
|
|
// lets us order
|
|
if (bucketA == ResultBucket::ExactMatch ||
|
|
bucketA == ResultBucket::ExpressionSpecific ||
|
|
bucketA == ResultBucket::NotRecommended) {
|
|
bucketA = getResultBucket(a, hasRequiredTypes, /*skipMetaGroups*/ true);
|
|
bucketB = getResultBucket(b, hasRequiredTypes, /*skipMetaGroups*/ true);
|
|
if (bucketA < bucketB)
|
|
return false;
|
|
else if (bucketB < bucketA)
|
|
return true;
|
|
}
|
|
|
|
// Special internal orderings.
|
|
switch (bucketA) {
|
|
case ResultBucket::HighPriorityKeyword:
|
|
return compareHighPriorityKeywords(a, b) < 0;
|
|
case ResultBucket::Literal:
|
|
case ResultBucket::LiteralTypeMatch:
|
|
return compareLiterals(a, b) < 0;
|
|
case ResultBucket::Operator: {
|
|
int cmp = compareOperators(a, b);
|
|
if (cmp != 0)
|
|
return cmp < 0;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// "Normal" order.
|
|
if (a.finalScore < b.finalScore)
|
|
return false;
|
|
else if (b.finalScore < a.finalScore)
|
|
return true;
|
|
|
|
return compareResultName(a, b) < 0;
|
|
});
|
|
}
|
|
|
|
void CodeCompletionOrganizer::Impl::sort(Options options) {
|
|
bool hasRequiredTypes = typeContextKind == TypeContextKind::Required;
|
|
sortRecursive(options, rootGroup.get(), hasRequiredTypes);
|
|
if (options.showTopNonLiteralResults != 0)
|
|
sortTopN(options, rootGroup.get(), hasRequiredTypes);
|
|
}
|
|
|
|
void CodeCompletionOrganizer::Impl::groupStemsRecursive(
|
|
Group *group, bool recurseIntoNewGroups, StringRef(getStem)(StringRef)) {
|
|
std::vector<std::unique_ptr<Item>> newContents;
|
|
std::vector<std::unique_ptr<Item>> &worklist = group->contents;
|
|
|
|
auto getSubStem = [getStem](StringRef name, StringRef groupName) {
|
|
StringRef subName = name.slice(groupName.size(), StringRef::npos);
|
|
return getStem(subName);
|
|
};
|
|
|
|
if (worklist.empty())
|
|
return;
|
|
|
|
auto start = worklist.begin();
|
|
while (start != worklist.end()) {
|
|
if (auto *g = dyn_cast<Group>(start->get())) {
|
|
groupStemsRecursive(g, recurseIntoNewGroups, getStem);
|
|
newContents.push_back(std::move(*start));
|
|
++start;
|
|
continue;
|
|
}
|
|
|
|
StringRef stem = getSubStem((*start)->name, group->name);
|
|
auto end = start;
|
|
auto next = ++end;
|
|
while (end != worklist.end() && !stem.empty() &&
|
|
stem == getSubStem((*end)->name, group->name))
|
|
++end;
|
|
if (end == next) {
|
|
// Only one element; don't group.
|
|
newContents.push_back(std::move(*start));
|
|
start = end; // == next == ++start
|
|
} else if (end == worklist.end() && newContents.empty()) {
|
|
// Only one group; inline it.
|
|
// FIXME: this is wrong, we should try to sub-group.
|
|
assert(start == worklist.begin());
|
|
std::swap(newContents, worklist);
|
|
break;
|
|
} else {
|
|
std::string name = (Twine(group->name) + stem).str();
|
|
if (recurseIntoNewGroups) {
|
|
while (true) {
|
|
StringRef next = getSubStem((*start)->name, name);
|
|
if (next.empty())
|
|
break;
|
|
auto I = start; ++I;
|
|
for ( ; I != end; ++I)
|
|
if (next != getSubStem((*I)->name, name))
|
|
goto done;
|
|
name += next;
|
|
}
|
|
done:
|
|
; // exit label
|
|
}
|
|
|
|
auto newGroup = make_group(name);
|
|
for (; start != end; ++start)
|
|
newGroup->contents.push_back(std::move(*start));
|
|
|
|
if (recurseIntoNewGroups)
|
|
groupStemsRecursive(newGroup.get(), recurseIntoNewGroups, getStem);
|
|
newContents.push_back(std::move(newGroup));
|
|
}
|
|
}
|
|
|
|
group->contents = std::move(newContents);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CodeCompletionView
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static bool walkRecursive(CodeCompletionView::Walker &walker, const Item *item) {
|
|
if (auto *result = dyn_cast<Result>(item))
|
|
return walker.handleResult(result->value);
|
|
|
|
walker.startGroup(item->name);
|
|
for (auto &child : cast<Group>(item)->contents) {
|
|
if (!walkRecursive(walker, child.get()))
|
|
return false;
|
|
}
|
|
walker.endGroup();
|
|
return true;
|
|
}
|
|
|
|
bool CodeCompletionView::walk(CodeCompletionView::Walker &walker) const {
|
|
assert(rootGroup);
|
|
return walkRecursive(walker, rootGroup);
|
|
}
|
|
|
|
CodeCompletionView::~CodeCompletionView() { delete rootGroup; }
|
|
|
|
unsigned LimitedResultView::getNextOffset() const { return start; }
|
|
|
|
bool LimitedResultView::walk(CodeCompletionView::Walker &walker) const {
|
|
const Group *root = baseView.rootGroup;
|
|
walker.startGroup(root->name);
|
|
auto begin = root->contents.begin();
|
|
auto end = root->contents.end();
|
|
unsigned count = 0;
|
|
while (count < start && begin != end) {
|
|
++count;
|
|
++begin;
|
|
}
|
|
while (begin != end && (maxResults == 0 || count < start + maxResults)) {
|
|
if (!walkRecursive(walker, begin->get()))
|
|
return false;
|
|
++count;
|
|
++begin;
|
|
}
|
|
|
|
if (begin == end) {
|
|
assert(maxResults == 0 || count <= start + maxResults);
|
|
start = 0;
|
|
} else {
|
|
start = count;
|
|
}
|
|
|
|
walker.endGroup();
|
|
return true;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// CompletionBuilder
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
CompletionBuilder::CompletionBuilder(CompletionSink &sink, SwiftResult &base)
|
|
: sink(sink), current(base) {
|
|
typeRelation = current.getExpectedTypeRelation();
|
|
semanticContext = current.getSemanticContext();
|
|
flair = current.getFlair();
|
|
completionString =
|
|
const_cast<CodeCompletionString *>(current.getCompletionString());
|
|
|
|
// FIXME: this works around the fact we're producing invalid completion
|
|
// strings for our inner "." result.
|
|
if (current.getCompletionString()->getFirstTextChunkIndex().hasValue()) {
|
|
llvm::raw_svector_ostream OSS(originalName);
|
|
ide::printCodeCompletionResultFilterName(current, OSS);
|
|
}
|
|
}
|
|
|
|
void CompletionBuilder::setPrefix(CodeCompletionString *prefix) {
|
|
if (!prefix)
|
|
return;
|
|
|
|
modified = true;
|
|
|
|
// The underlying text is kept alive by an CompletionSink. The chunks
|
|
// themselves get copied into the CodeCompletionString.
|
|
std::vector<CodeCompletionString::Chunk> chunks;
|
|
chunks.reserve(prefix->getChunks().size());
|
|
for (auto chunk : prefix->getChunks()) {
|
|
if (chunk.is(CodeCompletionString::Chunk::ChunkKind::TypeAnnotation))
|
|
continue; // The type is the type of the actual result, not the prefix.
|
|
chunks.push_back(chunk);
|
|
}
|
|
|
|
auto existing = current.getCompletionString()->getChunks();
|
|
chunks.insert(chunks.end(), existing.begin(), existing.end());
|
|
completionString = CodeCompletionString::create(sink.allocator, chunks);
|
|
}
|
|
|
|
Completion *CompletionBuilder::finish() {
|
|
SwiftResult base = current;
|
|
llvm::SmallString<64> nameStorage;
|
|
StringRef name = getOriginalName();
|
|
if (modified) {
|
|
// We've modified the original result, so build a new one.
|
|
auto opKind = CodeCompletionOperatorKind::None;
|
|
if (current.isOperator())
|
|
opKind = current.getOperatorKind();
|
|
|
|
if (current.getKind() == SwiftResult::ResultKind::Declaration) {
|
|
base = SwiftResult(
|
|
semanticContext, flair, current.getNumBytesToErase(),
|
|
completionString, current.getAssociatedDeclKind(), current.isSystem(),
|
|
current.getModuleName(), current.getNotRecommendedReason(),
|
|
current.getDiagnosticSeverity(), current.getDiagnosticMessage(),
|
|
current.getBriefDocComment(), current.getAssociatedUSRs(),
|
|
typeRelation, opKind);
|
|
} else {
|
|
base = SwiftResult(current.getKind(), semanticContext, flair,
|
|
current.getNumBytesToErase(), completionString,
|
|
typeRelation, opKind);
|
|
}
|
|
|
|
llvm::raw_svector_ostream OSS(nameStorage);
|
|
ide::printCodeCompletionResultFilterName(base, OSS);
|
|
name = OSS.str();
|
|
}
|
|
|
|
llvm::SmallString<64> description;
|
|
{
|
|
llvm::raw_svector_ostream OSS(description);
|
|
ide::printCodeCompletionResultDescription(base, OSS,
|
|
/*leadingPunctuation=*/true);
|
|
}
|
|
|
|
auto *result = new (sink.allocator)
|
|
Completion(std::move(base), copyString(sink.allocator, name),
|
|
copyString(sink.allocator, description));
|
|
result->moduleImportDepth = moduleImportDepth;
|
|
result->popularityFactor = popularityFactor;
|
|
result->opaqueCustomKind = customKind;
|
|
return result;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// NameStyle
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
NameStyle::NameStyle(StringRef name)
|
|
: leadingUnderscores(0), trailingUnderscores(0) {
|
|
// Trim leading and trailing underscores.
|
|
StringRef center = name.ltrim("_");
|
|
if (center == "")
|
|
return;
|
|
leadingUnderscores = name.size() - center.size();
|
|
center = center.rtrim("_");
|
|
assert(!center.empty());
|
|
trailingUnderscores = name.size() - center.size() - leadingUnderscores;
|
|
|
|
unsigned pos = 0;
|
|
|
|
enum Case {
|
|
None = 0,
|
|
Lower,
|
|
Upper,
|
|
};
|
|
|
|
auto caseOf = [](char c) {
|
|
if (clang::isLowercase(c))
|
|
return Lower;
|
|
if (clang::isUppercase(c))
|
|
return Upper;
|
|
return None;
|
|
};
|
|
|
|
unsigned underscores = 0;
|
|
unsigned caseCount[3] = {0, 0, 0};
|
|
Case leadingCase = None;
|
|
for (; pos < center.size(); ++pos) {
|
|
char c = center[pos];
|
|
Case curCase = caseOf(c);
|
|
if (!leadingCase)
|
|
leadingCase = curCase;
|
|
|
|
underscores += (c == '_');
|
|
caseCount[curCase] += 1;
|
|
}
|
|
|
|
assert(caseCount[leadingCase] > 0);
|
|
|
|
if (caseCount[Lower] && !caseCount[Upper]) {
|
|
wordDelimiter = underscores ? LowercaseWithUnderscores : Lowercase;
|
|
return;
|
|
}
|
|
if (caseCount[Upper] && !caseCount[Lower]) {
|
|
wordDelimiter = underscores ? UppercaseWithUnderscores : Uppercase;
|
|
return;
|
|
}
|
|
if (leadingCase && !underscores) {
|
|
wordDelimiter = leadingCase == Lower ? LowerCamelCase : UpperCamelCase;
|
|
return;
|
|
}
|
|
|
|
// FIXME: should we try to choose a delimiter if there is more than one?
|
|
wordDelimiter = Unknown;
|
|
}
|