Skip to content

Commit

Permalink
Address feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
milesziemer committed Nov 27, 2024
1 parent bc80afb commit c65cbe3
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import software.amazon.smithy.model.traits.IdRefTrait;

/**
* Candidates for code-completions.
* Candidates for code completions.
*
* <p>There are different kinds of completion candidates, each of which may
* need to be represented differently, filtered, and/or mapped to IDE-specific
* data structures in their own way.</p>
*/
sealed interface Candidates {
sealed interface CompletionCandidates {
Constant NONE = new Constant("");
Constant EMPTY_STRING = new Constant("\"\"");
Constant EMPTY_OBJ = new Constant("{}");
Expand All @@ -43,14 +43,12 @@ sealed interface Candidates {
"apply"));
// TODO: Maybe BUILTIN_CONTROLS and BUILTIN_METADATA should be regular
// Labeled/Members, with custom mappers.
Literals BUILTIN_CONTROLS = new Candidates.Literals(
Builtins.CONTROL.members().stream()
.map(member -> "$" + member.getMemberName() + ": " + Candidates.defaultCandidates(member).value())
.toList());
Literals BUILTIN_METADATA = new Candidates.Literals(
Builtins.METADATA.members().stream()
.map(member -> member.getMemberName() + " = []")
.toList());
Literals BUILTIN_CONTROLS = new Literals(Builtins.CONTROL.members().stream()
.map(member -> "$" + member.getMemberName() + ": " + defaultCandidates(member).value())
.toList());
Literals BUILTIN_METADATA = new Literals(Builtins.METADATA.members().stream()
.map(member -> member.getMemberName() + " = []")
.toList());
Labeled SMITHY_IDL_VERSION = new Labeled(Stream.of("1.0", "2.0")
.collect(StreamUtils.toWrappedMap()));
Labeled VALIDATOR_NAMES = new Labeled(Builtins.VALIDATOR_CONFIG_MAPPING.keySet().stream()
Expand All @@ -64,7 +62,7 @@ sealed interface Candidates {
* @return A constant value corresponding to the 'default' or 'empty' value
* of a shape.
*/
static Candidates.Constant defaultCandidates(Shape shape) {
static Constant defaultCandidates(Shape shape) {
if (shape.hasTrait(DefaultTrait.class)) {
DefaultTrait defaultTrait = shape.expectTrait(DefaultTrait.class);
return new Constant(Node.printJson(defaultTrait.toNode()));
Expand All @@ -85,7 +83,7 @@ static Candidates.Constant defaultCandidates(Shape shape) {
* @param result The search result to get candidates from.
* @return The completion candidates for {@code result}.
*/
static Candidates fromSearchResult(NodeSearch.Result result) {
static CompletionCandidates fromSearchResult(NodeSearch.Result result) {
return switch (result) {
case NodeSearch.Result.TerminalShape(Shape shape, var ignored) ->
terminalCandidates(shape);
Expand All @@ -98,39 +96,53 @@ static Candidates fromSearchResult(NodeSearch.Result result) {

case NodeSearch.Result.ArrayShape(var ignored, ListShape shape, Model model) ->
model.getShape(shape.getMember().getTarget())
.map(Candidates::terminalCandidates)
.map(CompletionCandidates::terminalCandidates)
.orElse(NONE);

default -> NONE;
};
}

/**
* @param idlPosition The position in the idl to get candidates for.
* @return The candidates for shape completions.
*/
static CompletionCandidates shapeCandidates(IdlPosition idlPosition) {
return switch (idlPosition) {
case IdlPosition.UseTarget ignored -> Shapes.USE_TARGET;
case IdlPosition.TraitId ignored -> Shapes.TRAITS;
case IdlPosition.Mixin ignored -> Shapes.MIXINS;
case IdlPosition.ForResource ignored -> Shapes.RESOURCE_SHAPES;
case IdlPosition.MemberTarget ignored -> Shapes.MEMBER_TARGETABLE;
case IdlPosition.ApplyTarget ignored -> Shapes.ANY_SHAPE;
case IdlPosition.NodeMemberTarget nodeMemberTarget -> fromSearchResult(
ShapeSearch.searchNodeMemberTarget(nodeMemberTarget));
default -> CompletionCandidates.NONE;
};
}

/**
* @param model The model that {@code shape} is a part of.
* @param shape The shape to get member candidates for.
* @return If a struct or union shape, returns {@link Members} candidates.
* Otherwise, {@link #NONE}.
*/
static Candidates membersCandidates(Model model, Shape shape) {
static CompletionCandidates membersCandidates(Model model, Shape shape) {
if (shape.isStructureShape() || shape.isUnionShape()) {
return new Members(shape.getAllMembers().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> model.getShape(entry.getValue().getTarget())
.map(Candidates::defaultCandidates)
.orElse(NONE))));
.collect(StreamUtils.mappingValue(member -> model.getShape(member.getTarget())
.map(CompletionCandidates::defaultCandidates)
.orElse(NONE))));
} else if (shape instanceof MapShape mapShape) {
EnumShape enumKey = model.getShape(mapShape.getKey().getTarget())
return model.getShape(mapShape.getKey().getTarget())
.flatMap(Shape::asEnumShape)
.orElse(null);
if (enumKey != null) {
return terminalCandidates(enumKey);
}
.map(CompletionCandidates::terminalCandidates)
.orElse(NONE);
}
return NONE;
}

private static Candidates terminalCandidates(Shape shape) {
private static CompletionCandidates terminalCandidates(Shape shape) {
Builtins.BuiltinShape builtinShape = Builtins.BUILTIN_SHAPES.get(shape.getId());
if (builtinShape != null) {
return forBuiltin(builtinShape);
Expand All @@ -155,7 +167,7 @@ private static Candidates terminalCandidates(Shape shape) {
};
}

private static Candidates forBuiltin(Builtins.BuiltinShape builtinShape) {
private static CompletionCandidates forBuiltin(Builtins.BuiltinShape builtinShape) {
return switch (builtinShape) {
case SmithyIdlVersion -> SMITHY_IDL_VERSION;
case AnyNamespace -> Custom.NAMESPACE_FILTER;
Expand All @@ -176,40 +188,46 @@ private static Candidates forBuiltin(Builtins.BuiltinShape builtinShape) {
*
* @param value The completion value.
*/
record Constant(String value) implements Candidates {}
record Constant(String value) implements CompletionCandidates {}

/**
* Multiple values to be completed as literals, like keywords.
*
* @param literals The completion values.
*/
record Literals(List<String> literals) implements Candidates {}
record Literals(List<String> literals) implements CompletionCandidates {}

/**
* Multiple label -> value pairs, where the label is displayed to the user,
* and may be used for matching, and the value is the literal text to complete.
*
* <p>For example, completing enum value in a trait may display and match on the
* name, like FOO, but complete the actual value, like "foo".
*
* @param labeled The labeled completion values.
*/
record Labeled(Map<String, String> labeled) implements Candidates {}
record Labeled(Map<String, String> labeled) implements CompletionCandidates {}

/**
* Multiple name -> constant pairs, where the name corresponds to a member
* name, and the constant is a default/empty value for that member.
*
* <p>For example, shape members can be completed as {@code name: constant}.
*
* @param members The members completion values.
*/
record Members(Map<String, Candidates.Constant> members) implements Candidates {}
record Members(Map<String, Constant> members) implements CompletionCandidates {}

/**
* Multiple member names to complete as elided members.
*
* @apiNote These are distinct from {@link Literals} because they may have
* custom filtering/mapping, and may appear _with_ {@link Literals} in an
* {@link And}.
*
* @param memberNames The member names completion values.
*/
record ElidedMembers(Collection<String> memberNames) implements Candidates {}
record ElidedMembers(Collection<String> memberNames) implements CompletionCandidates {}

/**
* A combination of two sets of completion candidates, of possibly different
Expand All @@ -218,13 +236,13 @@ record ElidedMembers(Collection<String> memberNames) implements Candidates {}
* @param one The first set of completion candidates.
* @param two The second set of completion candidates.
*/
record And(Candidates one, Candidates two) implements Candidates {}
record And(CompletionCandidates one, CompletionCandidates two) implements CompletionCandidates {}

/**
* Shape completion candidates, each corresponding to a different set of
* shapes that will be selected from the model.
*/
enum Shapes implements Candidates {
enum Shapes implements CompletionCandidates {
ANY_SHAPE,
USE_TARGET,
TRAITS,
Expand All @@ -239,7 +257,7 @@ enum Shapes implements Candidates {
/**
* Candidates that require a custom computation to generate, lazily.
*/
enum Custom implements Candidates {
enum Custom implements CompletionCandidates {
NAMESPACE_FILTER,
VALIDATOR_NAME,
PROJECT_NAMESPACES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,22 +72,22 @@ public List<CompletionItem> handle(CompletionParams params, CancelChecker cc) {
case IdlPosition.ControlKey ignored -> builder
.literalKind(CompletionItemKind.Constant)
.buildSimpleCompletions()
.getCompletionItems(Candidates.BUILTIN_CONTROLS);
.getCompletionItems(CompletionCandidates.BUILTIN_CONTROLS);

case IdlPosition.MetadataKey ignored -> builder
.literalKind(CompletionItemKind.Field)
.buildSimpleCompletions()
.getCompletionItems(Candidates.BUILTIN_METADATA);
.getCompletionItems(CompletionCandidates.BUILTIN_METADATA);

case IdlPosition.StatementKeyword ignored -> builder
.literalKind(CompletionItemKind.Keyword)
.buildSimpleCompletions()
.getCompletionItems(Candidates.KEYWORD);
.getCompletionItems(CompletionCandidates.KEYWORD);

case IdlPosition.Namespace ignored -> builder
.literalKind(CompletionItemKind.Module)
.buildSimpleCompletions()
.getCompletionItems(Candidates.Custom.PROJECT_NAMESPACES);
.getCompletionItems(CompletionCandidates.Custom.PROJECT_NAMESPACES);

case IdlPosition.MetadataValue metadataValue -> metadataValueCompletions(metadataValue, builder);

Expand Down Expand Up @@ -129,7 +129,7 @@ private List<CompletionItem> metadataValueCompletions(
) {
var result = ShapeSearch.searchMetadataValue(metadataValue);
Set<String> excludeKeys = getOtherPresentKeys(result);
Candidates candidates = Candidates.fromSearchResult(result);
CompletionCandidates candidates = CompletionCandidates.fromSearchResult(result);
return builder.exclude(excludeKeys).buildSimpleCompletions().getCompletionItems(candidates);
}

Expand Down Expand Up @@ -176,10 +176,10 @@ private List<CompletionItem> modelBasedCompletions(IdlPosition idlPosition, Simp
return traitValueCompletions(traitValue, model, builder);
}

Candidates candidates = shapeCandidates(idlPosition);
if (candidates instanceof Candidates.Shapes shapes) {
CompletionCandidates candidates = CompletionCandidates.shapeCandidates(idlPosition);
if (candidates instanceof CompletionCandidates.Shapes shapes) {
return builder.buildShapeCompletions(idlPosition, model).getCompletionItems(shapes);
} else if (candidates != Candidates.NONE) {
} else if (candidates != CompletionCandidates.NONE) {
return builder.buildSimpleCompletions().getCompletionItems(candidates);
}

Expand All @@ -191,7 +191,7 @@ private List<CompletionItem> elidedMemberCompletions(
Model model,
SimpleCompletions.Builder builder
) {
Candidates candidates = getElidableMemberCandidates(elidedMember.statementIndex(), model);
CompletionCandidates candidates = getElidableMemberCandidates(elidedMember.statementIndex(), model);
if (candidates == null) {
return List.of();
}
Expand All @@ -210,24 +210,10 @@ private List<CompletionItem> traitValueCompletions(
) {
var result = ShapeSearch.searchTraitValue(traitValue, model);
Set<String> excludeKeys = getOtherPresentKeys(result);
Candidates candidates = Candidates.fromSearchResult(result);
CompletionCandidates candidates = CompletionCandidates.fromSearchResult(result);
return builder.exclude(excludeKeys).buildSimpleCompletions().getCompletionItems(candidates);
}

private Candidates shapeCandidates(IdlPosition idlPosition) {
return switch (idlPosition) {
case IdlPosition.UseTarget ignored -> Candidates.Shapes.USE_TARGET;
case IdlPosition.TraitId ignored -> Candidates.Shapes.TRAITS;
case IdlPosition.Mixin ignored -> Candidates.Shapes.MIXINS;
case IdlPosition.ForResource ignored -> Candidates.Shapes.RESOURCE_SHAPES;
case IdlPosition.MemberTarget ignored -> Candidates.Shapes.MEMBER_TARGETABLE;
case IdlPosition.ApplyTarget ignored -> Candidates.Shapes.ANY_SHAPE;
case IdlPosition.NodeMemberTarget nodeMemberTarget -> Candidates.fromSearchResult(
ShapeSearch.searchNodeMemberTarget(nodeMemberTarget));
default -> Candidates.NONE;
};
}

private List<CompletionItem> memberNameCompletions(
IdlPosition.MemberName memberName,
SimpleCompletions.Builder builder
Expand All @@ -243,20 +229,20 @@ private List<CompletionItem> memberNameCompletions(
String shapeType = shapeDef.shapeType().copyValueFrom(smithyFile.document());
StructureShape shapeMembersDef = Builtins.getMembersForShapeType(shapeType);

Candidates candidates = null;
CompletionCandidates candidates = null;
if (shapeMembersDef != null) {
candidates = Candidates.membersCandidates(Builtins.MODEL, shapeMembersDef);
candidates = CompletionCandidates.membersCandidates(Builtins.MODEL, shapeMembersDef);
}

if (project.modelResult().getResult().isPresent()) {
Candidates elidedCandidates = getElidableMemberCandidates(
CompletionCandidates elidedCandidates = getElidableMemberCandidates(
memberName.statementIndex(),
project.modelResult().getResult().get());

if (elidedCandidates != null) {
candidates = candidates == null
? elidedCandidates
: new Candidates.And(candidates, elidedCandidates);
: new CompletionCandidates.And(candidates, elidedCandidates);
}
}

Expand All @@ -271,7 +257,7 @@ private List<CompletionItem> memberNameCompletions(
return builder.exclude(otherMembers).buildSimpleCompletions().getCompletionItems(candidates);
}

private Candidates getElidableMemberCandidates(int statementIndex, Model model) {
private CompletionCandidates getElidableMemberCandidates(int statementIndex, Model model) {
var resourceAndMixins = ShapeSearch.findForResourceAndMixins(
SyntaxSearch.closestForResourceAndMixinsBeforeMember(smithyFile.statements(), statementIndex),
smithyFile,
Expand All @@ -291,6 +277,6 @@ private Candidates getElidableMemberCandidates(int statementIndex, Model model)
return null;
}

return new Candidates.ElidedMembers(memberNames);
return new CompletionCandidates.ElidedMembers(memberNames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import software.amazon.smithy.model.traits.TraitDefinition;

/**
* Maps {@link Candidates.Shapes} to {@link CompletionItem}s.
* Maps {@link CompletionCandidates.Shapes} to {@link CompletionItem}s.
*/
final class ShapeCompletions {
private final Model model;
Expand All @@ -45,14 +45,14 @@ private ShapeCompletions(Model model, SmithyFile smithyFile, Matcher matcher, Ma
this.mapper = mapper;
}

List<CompletionItem> getCompletionItems(Candidates.Shapes candidates) {
List<CompletionItem> getCompletionItems(CompletionCandidates.Shapes candidates) {
return streamShapes(candidates)
.filter(matcher::test)
.mapMulti(mapper::accept)
.toList();
}

private Stream<? extends Shape> streamShapes(Candidates.Shapes candidates) {
private Stream<? extends Shape> streamShapes(CompletionCandidates.Shapes candidates) {
return switch (candidates) {
case ANY_SHAPE -> model.shapes();
case STRING_SHAPES -> model.getStringShapes().stream();
Expand Down Expand Up @@ -202,7 +202,7 @@ public void add(Mapper mapper, String shapeLabel, Shape shape, Consumer<Completi

@Override
protected String getDefault(Shape shape) {
return Candidates.defaultCandidates(shape).value();
return CompletionCandidates.defaultCandidates(shape).value();
}

@Override
Expand Down
Loading

0 comments on commit c65cbe3

Please sign in to comment.