diff --git a/modules/core/src/test/smithy/demo.smithy b/modules/core/src/test/smithy/demo.smithy index b0799872..148797b2 100644 --- a/modules/core/src/test/smithy/demo.smithy +++ b/modules/core/src/test/smithy/demo.smithy @@ -49,6 +49,28 @@ operation GetVersion { @documentation(""" Create a hero. """) +@examples([{ + title: "Valid input" + documentation: "This is a valid input" + input: { + hero: { + good: { + howGood: 10 + } + } + } +}, { + title: "Valid input v2" + documentation: "This is also a valid input, but for a bad hero" + input: { + hero: { + bad: { + evilName: "Evil" + powerLevel: 10 + } + } + } +}]) operation CreateHero { input: CreateHeroInput output: CreateHeroOutput diff --git a/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala b/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala index 56e1890c..a8b7a09f 100644 --- a/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala +++ b/modules/language-support/src/main/scala/playground/language/CompletionProvider.scala @@ -16,6 +16,8 @@ import playground.smithyql.SourceFile import playground.smithyql.WithSource import playground.smithyql.parser.SourceParser import playground.smithyql.syntax._ +import smithy.api.Examples +import smithy4s.Hints import smithy4s.dynamic.DynamicSchemaIndex trait CompletionProvider { @@ -103,7 +105,12 @@ object CompletionProvider { .service .endpoints .map { endpoint => - OperationName[Id](endpoint.name) -> endpoint.input.compile(CompletionVisitor) + OperationName[Id](endpoint.name) -> endpoint + .input + .addHints( + endpoint.hints.get(Examples).map(Hints(_)).getOrElse(Hints.empty) + ) + .compile(CompletionVisitor) } .toMap } diff --git a/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala b/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala index 4cc15da1..14e70d39 100644 --- a/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala +++ b/modules/language-support/src/main/scala/playground/language/CompletionVisitor.scala @@ -3,10 +3,12 @@ package playground.language import cats.Id import cats.implicits._ import cats.kernel.Eq +import playground.NodeEncoder import playground.ServiceNameExtractor import playground.TextUtils import playground.language.CompletionItem.InsertUseClause.NotRequired import playground.language.CompletionItem.InsertUseClause.Required +import playground.smithyql.InputNode import playground.smithyql.NodeContext import playground.smithyql.NodeContext.EmptyPath import playground.smithyql.NodeContext.PathEntry @@ -23,6 +25,7 @@ import playground.smithyql.WithSource import playground.smithyql.format.Formatter import smithy.api import smithy4s.Bijection +import smithy4s.Document import smithy4s.Endpoint import smithy4s.Hints import smithy4s.Lazy @@ -145,15 +148,17 @@ object CompletionItem { label: String, insertText: InsertText, schema: Schema[_], + sortTextOverride: Option[String] = None, ): CompletionItem = { val isField = kind === CompletionItemKind.Field - val sortText = + val sortText = sortTextOverride.orElse { isField match { case true if isRequiredField(schema) => Some(s"1_$label") case true => Some(s"2_$label") case false => None } + } CompletionItem( kind = kind, @@ -530,15 +535,55 @@ object CompletionVisitor extends SchemaVisitor[CompletionResolver] { fields: Vector[SchemaField[S, _]], make: IndexedSeq[Any] => S, ): CompletionResolver[S] = { + // Artificial schema resembling this one. Should be pretty much equivalent. + val schema = Schema.struct(fields)(make).addHints(hints).withId(shapeId) + val documentDecoder = Document.Decoder.fromSchema(schema) + + val nodeEncoder = NodeEncoder.derive(schema) + val compiledFields = fields.map(field => (field.mapK(this), field.instance)) + /* todo: pass this outside of Hints? (visitor context?) */ + val examples = hints + .get(api.Examples) + .foldMap(_.value) + .zipWithIndex + .map { case (example, index) => + val name = example.title + val doc = example.documentation + + val text = Formatter[InputNode] + .format( + nodeEncoder + .toNode(documentDecoder.decode(example.input.get).toTry.get /* todo: be graceful */ ) + .mapK(WithSource.liftId), + Int.MaxValue, + ) + .trim() + .tail + .init /* HACK: trim opening/closing braces */ + .trim() + + CompletionItem.fromHints( + kind = CompletionItemKind.Constant /* todo */, + label = s"Example: $name", + insertText = InsertText.JustString(text), + // issue: this doesn't work if the schema already has a Documentation hint. We should remove it first, or do something else. + schema = schema.addHints( + doc.map(api.Documentation(_)).map(Hints(_)).getOrElse(Hints.empty) + ), + sortTextOverride = Some(s"0_$index"), + ) + } + structLike( inBody = - fields - // todo: filter out present fields - .sortBy(field => (field.isRequired, field.label)) - .map(CompletionItem.fromField) - .toList, + examples ++ + fields + // todo: filter out present fields + .sortBy(field => (field.isRequired, field.label)) + .map(CompletionItem.fromField) + .toList, inValue = ( h, diff --git a/modules/plugin-core/src/main/scala/playground/plugins/PlaygroundPlugin.scala b/modules/plugin-core/src/main/scala/playground/plugins/PlaygroundPlugin.scala index 03541224..9fa54bc2 100644 --- a/modules/plugin-core/src/main/scala/playground/plugins/PlaygroundPlugin.scala +++ b/modules/plugin-core/src/main/scala/playground/plugins/PlaygroundPlugin.scala @@ -51,7 +51,7 @@ object SimpleHttpBuilder { service: Service[Alg], backend: Client[F], ): Either[UnsupportedProtocolError, FunctorAlgebra[Alg, F]] = - builder(service).client(backend).use + builder(service).client(backend).make }