Skip to content

Commit

Permalink
Add autocomplete context to result (#611)
Browse files Browse the repository at this point in the history
Closes #599 

Not sure if these context definitions are distinct enough, but they work for my use cases.

I repurposed (a majority of) the existing unit tests for this, but if it should live in separate test cases let me know

---
* Add autocomplete context to result

* Update tests

* Remove comments

* Fix expression context issues
  • Loading branch information
JohnnyMorganz committed Aug 10, 2022
1 parent 1b20fcd commit e15b072
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 38 deletions.
15 changes: 14 additions & 1 deletion Analysis/include/Luau/Autocomplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ struct TypeChecker;

using ModulePtr = std::shared_ptr<Module>;

enum class AutocompleteContext
{
Unknown,
Expression,
Statement,
Property,
Type,
Keyword,
String,
};

enum class AutocompleteEntryKind
{
Property,
Expand Down Expand Up @@ -66,11 +77,13 @@ struct AutocompleteResult
{
AutocompleteEntryMap entryMap;
std::vector<AstNode*> ancestry;
AutocompleteContext context = AutocompleteContext::Unknown;

AutocompleteResult() = default;
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry)
AutocompleteResult(AutocompleteEntryMap entryMap, std::vector<AstNode*> ancestry, AutocompleteContext context)
: entryMap(std::move(entryMap))
, ancestry(std::move(ancestry))
, context(context)
{
}
};
Expand Down
76 changes: 39 additions & 37 deletions Analysis/src/Autocomplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,7 @@ static bool autocompleteIfElseExpression(
}
}

static void autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena,
static AutocompleteContext autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker, TypeArena* typeArena,
const std::vector<AstNode*>& ancestry, Position position, AutocompleteEntryMap& result)
{
LUAU_ASSERT(!ancestry.empty());
Expand All @@ -1213,9 +1213,9 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
autocompleteProps(module, typeArena, *it, PropIndexType::Point, ancestry, result);
}
else if (autocompleteIfElseExpression(node, ancestry, position, result))
return;
return AutocompleteContext::Keyword;
else if (node->is<AstExprFunction>())
return;
return AutocompleteContext::Unknown;
else
{
// This is inefficient. :(
Expand Down Expand Up @@ -1260,14 +1260,16 @@ static void autocompleteExpression(const SourceModule& sourceModule, const Modul
if (auto ty = findExpectedTypeAt(module, node, position))
autocompleteStringSingleton(*ty, true, result);
}

return AutocompleteContext::Expression;
}

static AutocompleteEntryMap autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
static AutocompleteResult autocompleteExpression(const SourceModule& sourceModule, const Module& module, const TypeChecker& typeChecker,
TypeArena* typeArena, const std::vector<AstNode*>& ancestry, Position position)
{
AutocompleteEntryMap result;
autocompleteExpression(sourceModule, module, typeChecker, typeArena, ancestry, position, result);
return result;
AutocompleteContext context = autocompleteExpression(sourceModule, module, typeChecker, typeArena, ancestry, position, result);
return {result, ancestry, context};
}

static std::optional<const ClassTypeVar*> getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr)
Expand Down Expand Up @@ -1406,27 +1408,27 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M

if (!FFlag::LuauSelfCallAutocompleteFix3 && isString(ty))
return {
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry};
autocompleteProps(*module, typeArena, typeChecker.globalScope->bindings[AstName{"string"}].typeId, indexType, ancestry), ancestry, AutocompleteContext::Property};
else
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry};
return {autocompleteProps(*module, typeArena, ty, indexType, ancestry), ancestry, AutocompleteContext::Property};
}
else if (auto typeReference = node->as<AstTypeReference>())
{
if (typeReference->prefix)
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry};
return {autocompleteModuleTypes(*module, position, typeReference->prefix->value), ancestry, AutocompleteContext::Type};
else
return {autocompleteTypeNames(*module, position, ancestry), ancestry};
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
}
else if (node->is<AstTypeError>())
{
return {autocompleteTypeNames(*module, position, ancestry), ancestry};
return {autocompleteTypeNames(*module, position, ancestry), ancestry, AutocompleteContext::Type};
}
else if (AstStatLocal* statLocal = node->as<AstStatLocal>())
{
if (statLocal->vars.size == 1 && (!statLocal->equalsSignLocation || position < statLocal->equalsSignLocation->begin))
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"function", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Unknown};
else if (statLocal->equalsSignLocation && position >= statLocal->equalsSignLocation->end)
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
else
return {};
}
Expand All @@ -1436,16 +1438,16 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (!statFor->hasDo || position < statFor->doLocation.begin)
{
if (!statFor->from->is<AstExprError>() && !statFor->to->is<AstExprError>() && (!statFor->step || !statFor->step->is<AstExprError>()))
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};

if (statFor->from->location.containsClosed(position) || statFor->to->location.containsClosed(position) ||
(statFor->step && statFor->step->location.containsClosed(position)))
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);

return {};
}

return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}

else if (AstStatForIn* statForIn = parent->as<AstStatForIn>(); statForIn && (node->is<AstStatBlock>() || isIdentifier(node)))
Expand All @@ -1461,7 +1463,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
return {};
}

return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"in", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}

if (!statForIn->hasDo || position <= statForIn->doLocation.begin)
Expand All @@ -1470,10 +1472,10 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
AstExpr* lastExpr = statForIn->values.data[statForIn->values.size - 1];

if (lastExpr->location.containsClosed(position))
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);

if (position > lastExpr->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};

return {}; // Not sure what this means
}
Expand All @@ -1483,45 +1485,45 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
// The AST looks a bit differently if the cursor is at a position where only the "do" keyword is allowed.
// ex "for f in f do"
if (!statForIn->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};

return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}

else if (AstStatWhile* statWhile = parent->as<AstStatWhile>(); node->is<AstStatBlock>() && statWhile)
{
if (!statWhile->hasDo && !statWhile->condition->is<AstStatError>() && position > statWhile->condition->location.end)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};

if (!statWhile->hasDo || position < statWhile->doLocation.begin)
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);

if (statWhile->hasDo && position > statWhile->doLocation.end)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
}

else if (AstStatWhile* statWhile = extractStat<AstStatWhile>(ancestry); statWhile && !statWhile->hasDo)
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"do", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};

else if (AstStatIf* statIf = node->as<AstStatIf>(); statIf && !statIf->elseLocation.has_value())
{
return {
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
{{"else", AutocompleteEntry{AutocompleteEntryKind::Keyword}}, {"elseif", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
else if (AstStatIf* statIf = parent->as<AstStatIf>(); statIf && node->is<AstStatBlock>())
{
if (statIf->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
else if (!statIf->thenLocation || statIf->thenLocation->containsClosed(position))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
}
else if (AstStatIf* statIf = extractStat<AstStatIf>(ancestry);
statIf && (!statIf->thenLocation || statIf->thenLocation->containsClosed(position)))
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry};
return {{{"then", AutocompleteEntry{AutocompleteEntryKind::Keyword}}}, ancestry, AutocompleteContext::Keyword};
else if (AstStatRepeat* statRepeat = node->as<AstStatRepeat>(); statRepeat && statRepeat->condition->is<AstExprError>())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
else if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat)
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};
else if (AstExprTable* exprTable = parent->as<AstExprTable>(); exprTable && (node->is<AstExprGlobal>() || node->is<AstExprConstantString>()))
{
for (const auto& [kind, key, value] : exprTable->items)
Expand All @@ -1547,19 +1549,19 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
if (!key)
autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position, result);

return {result, ancestry};
return {result, ancestry, AutocompleteContext::Property};
}

break;
}
}
}
else if (isIdentifier(node) && (parent->is<AstStatExpr>() || parent->is<AstStatError>()))
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};

if (std::optional<AutocompleteEntryMap> ret = autocompleteStringParams(sourceModule, module, ancestry, position, callback))
{
return {*ret, ancestry};
return {*ret, ancestry, AutocompleteContext::String};
}
else if (node->is<AstExprConstantString>())
{
Expand All @@ -1585,7 +1587,7 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
}
}

return {result, ancestry};
return {result, ancestry, AutocompleteContext::String};
}

if (node->is<AstExprConstantNumber>())
Expand All @@ -1594,9 +1596,9 @@ static AutocompleteResult autocomplete(const SourceModule& sourceModule, const M
}

if (node->asExpr())
return {autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position), ancestry};
return autocompleteExpression(sourceModule, *module, typeChecker, typeArena, ancestry, position);
else if (node->asStat())
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry};
return {autocompleteStatement(sourceModule, *module, ancestry, position), ancestry, AutocompleteContext::Statement};

return {};
}
Expand Down
Loading

0 comments on commit e15b072

Please sign in to comment.