From 095ae1e6668d80456c6d734059fcfbad0a7a186a Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 14 Mar 2023 16:07:17 +0000 Subject: [PATCH 1/6] decoder/schema: pass PrefillRequiredFields via context --- decoder/attribute_candidates.go | 5 +++-- decoder/body_candidates.go | 11 ++++++----- decoder/candidates.go | 8 +++++--- decoder/expr_list_completion.go | 2 +- decoder/expr_map_completion.go | 4 ++-- decoder/expr_set_completion.go | 2 +- decoder/expr_tuple_completion.go | 2 +- decoder/label_candidates.go | 4 +++- schema/constraint.go | 4 +++- schema/constraint_any_expression.go | 4 +++- schema/constraint_keyword.go | 4 +++- schema/constraint_list.go | 5 +++-- schema/constraint_literal_type.go | 13 +++++++------ schema/constraint_literal_value.go | 13 +++++++------ schema/constraint_map.go | 5 +++-- schema/constraint_object.go | 21 ++++++++++++++++++--- schema/constraint_one_of.go | 5 +++-- schema/constraint_reference.go | 3 ++- schema/constraint_set.go | 5 +++-- schema/constraint_test.go | 4 +++- schema/constraint_tuple.go | 5 +++-- schema/constraint_type_declaration.go | 4 +++- 22 files changed, 86 insertions(+), 47 deletions(-) diff --git a/decoder/attribute_candidates.go b/decoder/attribute_candidates.go index 51f383a8..0fce1256 100644 --- a/decoder/attribute_candidates.go +++ b/decoder/attribute_candidates.go @@ -1,6 +1,7 @@ package decoder import ( + "context" "fmt" "sort" "strings" @@ -11,11 +12,11 @@ import ( "github.com/zclconf/go-cty/cty" ) -func attributeSchemaToCandidate(name string, attr *schema.AttributeSchema, rng hcl.Range) lang.Candidate { +func attributeSchemaToCandidate(ctx context.Context, name string, attr *schema.AttributeSchema, rng hcl.Range) lang.Candidate { var snippet string var triggerSuggest bool if attr.Constraint != nil { - cData := attr.Constraint.EmptyCompletionData(1, 0) + cData := attr.Constraint.EmptyCompletionData(ctx, 1, 0) snippet = fmt.Sprintf("%s = %s", name, cData.Snippet) triggerSuggest = cData.TriggerSuggest } else { diff --git a/decoder/body_candidates.go b/decoder/body_candidates.go index 26f1ed2b..5d261cf4 100644 --- a/decoder/body_candidates.go +++ b/decoder/body_candidates.go @@ -1,6 +1,7 @@ package decoder import ( + "context" "sort" "strings" @@ -11,7 +12,7 @@ import ( ) // bodySchemaCandidates returns candidates for completion of fields inside a body or block. -func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema.BodySchema, prefixRng, editRng hcl.Range) lang.Candidates { +func (d *PathDecoder) bodySchemaCandidates(ctx context.Context, body *hclsyntax.Body, schema *schema.BodySchema, prefixRng, editRng hcl.Range) lang.Candidates { prefix, _ := d.bytesFromRange(prefixRng) candidates := lang.NewCandidates() @@ -23,7 +24,7 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. // check if count attribute is already declared, so we don't // suggest a duplicate if _, ok := body.Attributes["count"]; !ok { - candidates.List = append(candidates.List, attributeSchemaToCandidate("count", countAttributeSchema(), editRng)) + candidates.List = append(candidates.List, attributeSchemaToCandidate(ctx, "count", countAttributeSchema(), editRng)) } } @@ -31,7 +32,7 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. // check if for_each attribute is already declared, so we don't // suggest a duplicate if _, present := body.Attributes["for_each"]; !present { - candidates.List = append(candidates.List, attributeSchemaToCandidate("for_each", forEachAttributeSchema(), editRng)) + candidates.List = append(candidates.List, attributeSchemaToCandidate(ctx, "for_each", forEachAttributeSchema(), editRng)) } } } @@ -51,7 +52,7 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. return candidates } - candidates.List = append(candidates.List, attributeSchemaToCandidate(name, attr, editRng)) + candidates.List = append(candidates.List, attributeSchemaToCandidate(ctx, name, attr, editRng)) count++ } } else if attr := schema.AnyAttribute; attr != nil && len(prefix) == 0 { @@ -59,7 +60,7 @@ func (d *PathDecoder) bodySchemaCandidates(body *hclsyntax.Body, schema *schema. return candidates } - candidates.List = append(candidates.List, attributeSchemaToCandidate("name", attr, editRng)) + candidates.List = append(candidates.List, attributeSchemaToCandidate(ctx, "name", attr, editRng)) count++ } diff --git a/decoder/candidates.go b/decoder/candidates.go index 1f3c5d8a..415f5441 100644 --- a/decoder/candidates.go +++ b/decoder/candidates.go @@ -38,6 +38,8 @@ func (d *PathDecoder) CandidatesAtPos(ctx context.Context, filename string, pos outerBodyRng = ob.Range() } + ctx = schema.WithPrefillRequiredFields(ctx, d.PrefillRequiredFields) + return d.candidatesAtPos(ctx, rootBody, outerBodyRng, d.pathCtx.Schema, pos) } @@ -71,7 +73,7 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, if attr.NameRange.ContainsPos(pos) { prefixRng := attr.NameRange prefixRng.End = pos - return d.bodySchemaCandidates(body, bodySchema, prefixRng, attr.Range()), nil + return d.bodySchemaCandidates(ctx, body, bodySchema, prefixRng, attr.Range()), nil } if attr.EqualsRange.ContainsPos(pos) { return lang.ZeroCandidates(), nil @@ -98,7 +100,7 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, if block.TypeRange.ContainsPos(pos) { prefixRng := block.TypeRange prefixRng.End = pos - return d.bodySchemaCandidates(body, bodySchema, prefixRng, block.Range()), nil + return d.bodySchemaCandidates(ctx, body, bodySchema, prefixRng, block.Range()), nil } for i, labelRange := range block.LabelRanges { @@ -152,7 +154,7 @@ func (d *PathDecoder) candidatesAtPos(ctx context.Context, body *hclsyntax.Body, rng = tokenRng } - return d.bodySchemaCandidates(body, bodySchema, rng, rng), nil + return d.bodySchemaCandidates(ctx, body, bodySchema, rng, rng), nil } func (d *PathDecoder) isPosInsideAttrExpr(attr *hclsyntax.Attribute, pos hcl.Pos) bool { diff --git a/decoder/expr_list_completion.go b/decoder/expr_list_completion.go index b3106945..6118f002 100644 --- a/decoder/expr_list_completion.go +++ b/decoder/expr_list_completion.go @@ -17,7 +17,7 @@ func (list List) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candid label = fmt.Sprintf("[ %s ]", list.cons.Elem.FriendlyName()) } - d := list.cons.EmptyCompletionData(1, 0) + d := list.cons.EmptyCompletionData(ctx, 1, 0) return []lang.Candidate{ { diff --git a/decoder/expr_map_completion.go b/decoder/expr_map_completion.go index 18479c22..17ce7319 100644 --- a/decoder/expr_map_completion.go +++ b/decoder/expr_map_completion.go @@ -18,7 +18,7 @@ func (m Map) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate label = fmt.Sprintf(`{ "key" = %s }`, m.cons.Elem.FriendlyName()) } - cData := m.cons.EmptyCompletionData(1, 0) + cData := m.cons.EmptyCompletionData(ctx, 1, 0) return []lang.Candidate{ { @@ -62,7 +62,7 @@ func (m Map) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate return []lang.Candidate{} } - cData := m.cons.Elem.EmptyCompletionData(2, 0) + cData := m.cons.Elem.EmptyCompletionData(ctx, 2, 0) kind := lang.AttributeCandidateKind // TODO: replace "attribute" kind w/ Elem type diff --git a/decoder/expr_set_completion.go b/decoder/expr_set_completion.go index 2dc17c84..83f3d17b 100644 --- a/decoder/expr_set_completion.go +++ b/decoder/expr_set_completion.go @@ -17,7 +17,7 @@ func (set Set) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidat label = fmt.Sprintf("[ %s ]", set.cons.Elem.FriendlyName()) } - d := set.cons.EmptyCompletionData(1, 0) + d := set.cons.EmptyCompletionData(ctx, 1, 0) return []lang.Candidate{ { diff --git a/decoder/expr_tuple_completion.go b/decoder/expr_tuple_completion.go index efd4119f..49dc09ca 100644 --- a/decoder/expr_tuple_completion.go +++ b/decoder/expr_tuple_completion.go @@ -28,7 +28,7 @@ func (tuple Tuple) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Cand label = fmt.Sprintf("[ %s ]", elemLabel) } - d := tuple.cons.EmptyCompletionData(1, 0) + d := tuple.cons.EmptyCompletionData(ctx, 1, 0) return []lang.Candidate{ { diff --git a/decoder/label_candidates.go b/decoder/label_candidates.go index 65aad2c9..ef0fb356 100644 --- a/decoder/label_candidates.go +++ b/decoder/label_candidates.go @@ -1,6 +1,7 @@ package decoder import ( + "context" "encoding/json" "fmt" "sort" @@ -164,7 +165,8 @@ func requiredFieldsSnippet(bodySchema *schema.BodySchema, placeholder int, inden var snippet string if attr.Constraint != nil { - snippet = attr.Constraint.EmptyCompletionData(placeholder, indentCount).Snippet + ctx := schema.WithPrefillRequiredFields(context.Background(), true) + snippet = attr.Constraint.EmptyCompletionData(ctx, placeholder, indentCount).Snippet } else { snippet = snippetForExprContraint(uint(placeholder), attr.Expr) } diff --git a/schema/constraint.go b/schema/constraint.go index 2dcd39fc..8733f5d0 100644 --- a/schema/constraint.go +++ b/schema/constraint.go @@ -1,6 +1,8 @@ package schema import ( + "context" + "github.com/hashicorp/hcl-lang/lang" "github.com/zclconf/go-cty/cty" ) @@ -16,7 +18,7 @@ type Constraint interface { // there is no corresponding configuration, such as when the Constraint // is part of another and it is desirable to complete // the parent constraint as whole. - EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData + EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData } type ConstraintWithHoverData interface { diff --git a/schema/constraint_any_expression.go b/schema/constraint_any_expression.go index e376c4d0..624cd56f 100644 --- a/schema/constraint_any_expression.go +++ b/schema/constraint_any_expression.go @@ -1,6 +1,8 @@ package schema import ( + "context" + "github.com/zclconf/go-cty/cty" ) @@ -28,7 +30,7 @@ func (ae AnyExpression) Copy() Constraint { } } -func (ae AnyExpression) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (ae AnyExpression) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { // TODO return CompletionData{} } diff --git a/schema/constraint_keyword.go b/schema/constraint_keyword.go index 0f4b2b26..ad357d23 100644 --- a/schema/constraint_keyword.go +++ b/schema/constraint_keyword.go @@ -1,6 +1,8 @@ package schema import ( + "context" + "github.com/hashicorp/hcl-lang/lang" ) @@ -36,7 +38,7 @@ func (k Keyword) Copy() Constraint { } } -func (k Keyword) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (k Keyword) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { return CompletionData{ TriggerSuggest: true, NextPlaceholder: nextPlaceholder, diff --git a/schema/constraint_list.go b/schema/constraint_list.go index 7ea89a49..a9738f56 100644 --- a/schema/constraint_list.go +++ b/schema/constraint_list.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/hcl-lang/lang" @@ -48,7 +49,7 @@ func (l List) Copy() Constraint { } } -func (l List) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (l List) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if l.Elem == nil { return CompletionData{ NewText: "[ ]", @@ -57,7 +58,7 @@ func (l List) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Complet } } - elemData := l.Elem.EmptyCompletionData(nextPlaceholder, nestingLevel) + elemData := l.Elem.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) if elemData.NewText == "" || elemData.Snippet == "" { return CompletionData{ NewText: "[ ]", diff --git a/schema/constraint_literal_type.go b/schema/constraint_literal_type.go index 6ca5bf67..333f42fc 100644 --- a/schema/constraint_literal_type.go +++ b/schema/constraint_literal_type.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/hcl-lang/lang" @@ -36,7 +37,7 @@ func (lt LiteralType) Copy() Constraint { } } -func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (lt LiteralType) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if lt.Type.IsPrimitiveType() { var newText, snippet string @@ -69,7 +70,7 @@ func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Type: lt.Type.ElementType(), }, } - return listCons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return listCons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } if lt.Type.IsSetType() { setCons := Set{ @@ -77,7 +78,7 @@ func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Type: lt.Type.ElementType(), }, } - return setCons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return setCons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } if lt.Type.IsMapType() { mapCons := Map{ @@ -85,7 +86,7 @@ func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Type: lt.Type.ElementType(), }, } - return mapCons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return mapCons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } if lt.Type.IsTupleType() { types := lt.Type.TupleElementTypes() @@ -95,7 +96,7 @@ func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Type: typ, } } - return tupleCons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return tupleCons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } if lt.Type.IsObjectType() { attrTypes := lt.Type.AttributeTypes() @@ -117,7 +118,7 @@ func (lt LiteralType) EmptyCompletionData(nextPlaceholder int, nestingLevel int) cons := Object{ Attributes: attrs, } - return cons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return cons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } return CompletionData{ diff --git a/schema/constraint_literal_value.go b/schema/constraint_literal_value.go index f3de9701..a56a9360 100644 --- a/schema/constraint_literal_value.go +++ b/schema/constraint_literal_value.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "sort" "strconv" @@ -38,7 +39,7 @@ func (lv LiteralValue) Copy() Constraint { } } -func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (lv LiteralValue) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if lv.Value.Type().IsPrimitiveType() { var value string switch lv.Value.Type() { @@ -75,7 +76,7 @@ func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int c := LiteralValue{ Value: val, } - cData := c.EmptyCompletionData(lastPlaceholder, nestingLevel) + cData := c.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NextPlaceholder: lastPlaceholder, @@ -103,7 +104,7 @@ func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int c := LiteralValue{ Value: val, } - cData := c.EmptyCompletionData(lastPlaceholder, nestingLevel) + cData := c.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NextPlaceholder: lastPlaceholder, @@ -131,7 +132,7 @@ func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int c := LiteralValue{ Value: val, } - cData := c.EmptyCompletionData(lastPlaceholder, nestingLevel) + cData := c.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NextPlaceholder: lastPlaceholder, @@ -163,7 +164,7 @@ func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int Value: val, } - cData := cons.EmptyCompletionData(lastPlaceholder, nestingLevel+1) + cData := cons.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel+1) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NextPlaceholder: lastPlaceholder, @@ -206,7 +207,7 @@ func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int, nestingLevel int cons := Object{ Attributes: attrs, } - return cons.EmptyCompletionData(nextPlaceholder, nestingLevel) + return cons.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) } return CompletionData{ diff --git a/schema/constraint_map.go b/schema/constraint_map.go index 24c30eed..73c935e8 100644 --- a/schema/constraint_map.go +++ b/schema/constraint_map.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "strings" @@ -56,7 +57,7 @@ func (m Map) Copy() Constraint { } } -func (m Map) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (m Map) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { rootNesting := strings.Repeat(" ", nestingLevel) insideNesting := strings.Repeat(" ", nestingLevel+1) @@ -68,7 +69,7 @@ func (m Map) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Completi } } - elemData := m.Elem.EmptyCompletionData(nextPlaceholder+1, nestingLevel+1) + elemData := m.Elem.EmptyCompletionData(ctx, nextPlaceholder+1, nestingLevel+1) if elemData.NewText == "" || elemData.Snippet == "" { return CompletionData{ NewText: fmt.Sprintf("{\n%s\n%s}", insideNesting, rootNesting), diff --git a/schema/constraint_object.go b/schema/constraint_object.go index 955f6e18..b7ba6844 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "sort" "strings" @@ -44,7 +45,21 @@ func (o Object) Copy() Constraint { } } -func (o Object) EmptyCompletionData(placeholder int, nestingLevel int) CompletionData { +type prefillRequiredFieldsCtxKey struct{} + +func WithPrefillRequiredFields(ctx context.Context, enabled bool) context.Context { + return context.WithValue(ctx, prefillRequiredFieldsCtxKey{}, enabled) +} + +func prefillRequiredFields(ctx context.Context) bool { + enabled, ok := ctx.Value(prefillRequiredFieldsCtxKey{}).(bool) + if !ok { + return false + } + return enabled +} + +func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestingLevel int) CompletionData { if len(o.Attributes) == 0 { return CompletionData{ NewText: "{}", @@ -63,7 +78,7 @@ func (o Object) EmptyCompletionData(placeholder int, nestingLevel int) Completio for _, name := range attrNames { attr := o.Attributes[name] - cData := attr.Constraint.EmptyCompletionData(lastPlaceholder, nestingLevel+1) + cData := attr.Constraint.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel+1) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NewText: "{}", @@ -197,7 +212,7 @@ func (oa ObjectAttributes) Copy() Constraint { return m } -func (oa ObjectAttributes) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (oa ObjectAttributes) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { // TODO return CompletionData{} } diff --git a/schema/constraint_one_of.go b/schema/constraint_one_of.go index 1280b8b3..6932f860 100644 --- a/schema/constraint_one_of.go +++ b/schema/constraint_one_of.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "strings" @@ -74,14 +75,14 @@ func namesContain(names []string, name string) bool { return false } -func (o OneOf) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (o OneOf) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if len(o) == 0 { return CompletionData{ NextPlaceholder: nextPlaceholder, } } - cData := o[0].EmptyCompletionData(nextPlaceholder, nestingLevel) + cData := o[0].EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) return CompletionData{ NewText: cData.NewText, diff --git a/schema/constraint_reference.go b/schema/constraint_reference.go index 8b3138c6..f89af200 100644 --- a/schema/constraint_reference.go +++ b/schema/constraint_reference.go @@ -1,6 +1,7 @@ package schema import ( + "context" "errors" "github.com/hashicorp/hcl-lang/lang" @@ -65,7 +66,7 @@ func (ref Reference) Copy() Constraint { } } -func (ref Reference) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (ref Reference) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { // TODO return CompletionData{} } diff --git a/schema/constraint_set.go b/schema/constraint_set.go index 4edc6927..14acbab6 100644 --- a/schema/constraint_set.go +++ b/schema/constraint_set.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/hcl-lang/lang" @@ -48,7 +49,7 @@ func (s Set) Copy() Constraint { } } -func (s Set) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (s Set) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if s.Elem == nil { return CompletionData{ NewText: "[ ]", @@ -57,7 +58,7 @@ func (s Set) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Completi } } - elemData := s.Elem.EmptyCompletionData(nextPlaceholder, nestingLevel) + elemData := s.Elem.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel) if elemData.NewText == "" || elemData.Snippet == "" { return CompletionData{ NewText: "[ ]", diff --git a/schema/constraint_test.go b/schema/constraint_test.go index f5a03934..b795f0e4 100644 --- a/schema/constraint_test.go +++ b/schema/constraint_test.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "testing" @@ -731,7 +732,8 @@ STRING } for i, tc := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - data := tc.cons.EmptyCompletionData(1, 0) + ctx := context.Background() + data := tc.cons.EmptyCompletionData(ctx, 1, 0) if diff := cmp.Diff(tc.expectedCompData, data); diff != "" { t.Fatalf("unexpected completion data: %s", diff) } diff --git a/schema/constraint_tuple.go b/schema/constraint_tuple.go index 209cb971..37ed50f8 100644 --- a/schema/constraint_tuple.go +++ b/schema/constraint_tuple.go @@ -1,6 +1,7 @@ package schema import ( + "context" "fmt" "strings" @@ -41,7 +42,7 @@ func (t Tuple) Copy() Constraint { return newTuple } -func (t Tuple) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (t Tuple) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { if len(t.Elems) == 0 { return CompletionData{ NewText: "[ ]", @@ -55,7 +56,7 @@ func (t Tuple) EmptyCompletionData(nextPlaceholder int, nestingLevel int) Comple lastPlaceholder := nextPlaceholder for i, elem := range t.Elems { - cData := elem.EmptyCompletionData(lastPlaceholder, nestingLevel) + cData := elem.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ NewText: "[ ]", diff --git a/schema/constraint_type_declaration.go b/schema/constraint_type_declaration.go index 789d30be..82d8191d 100644 --- a/schema/constraint_type_declaration.go +++ b/schema/constraint_type_declaration.go @@ -1,5 +1,7 @@ package schema +import "context" + // TypeDeclaration represents a type declaration as // interpreted by HCL's ext/typeexpr package, // i.e. declaration of cty.Type in HCL @@ -19,7 +21,7 @@ func (td TypeDeclaration) Copy() Constraint { return TypeDeclaration{} } -func (td TypeDeclaration) EmptyCompletionData(nextPlaceholder int, nestingLevel int) CompletionData { +func (td TypeDeclaration) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { return CompletionData{ TriggerSuggest: true, NextPlaceholder: nextPlaceholder, From 5240885e5a3501627754b085d53a6b9f69cdd902 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Thu, 16 Feb 2023 10:28:38 +0000 Subject: [PATCH 2/6] decoder/schema: Remove ObjectAttributes --- decoder/expr_object.go | 30 ------------------------------ decoder/expression_test.go | 3 --- schema/constraint_object.go | 22 ++-------------------- schema/constraint_test.go | 2 -- 4 files changed, 2 insertions(+), 55 deletions(-) diff --git a/decoder/expr_object.go b/decoder/expr_object.go index 5447b62b..49155edd 100644 --- a/decoder/expr_object.go +++ b/decoder/expr_object.go @@ -39,33 +39,3 @@ func (obj Object) ReferenceTargets(ctx context.Context, targetCtx *TargetContext // TODO return nil } - -type ObjectAttributes struct { - expr hcl.Expression - cons schema.ObjectAttributes -} - -func (oa ObjectAttributes) CompletionAtPos(ctx context.Context, pos hcl.Pos) []lang.Candidate { - // TODO - return nil -} - -func (oa ObjectAttributes) HoverAtPos(ctx context.Context, pos hcl.Pos) *lang.HoverData { - // TODO - return nil -} - -func (oa ObjectAttributes) SemanticTokens(ctx context.Context) []lang.SemanticToken { - // TODO - return nil -} - -func (oa ObjectAttributes) ReferenceOrigins(ctx context.Context, allowSelfRefs bool) reference.Origins { - // TODO - return nil -} - -func (oa ObjectAttributes) ReferenceTargets(ctx context.Context, targetCtx *TargetContext) reference.Targets { - // TODO - return nil -} diff --git a/decoder/expression_test.go b/decoder/expression_test.go index a954eac5..10621a9e 100644 --- a/decoder/expression_test.go +++ b/decoder/expression_test.go @@ -18,7 +18,6 @@ var ( _ Expression = LiteralType{} _ Expression = LiteralValue{} _ Expression = Map{} - _ Expression = ObjectAttributes{} _ Expression = Object{} _ Expression = Set{} _ Expression = Reference{} @@ -29,7 +28,6 @@ var ( _ ReferenceOriginsExpression = List{} _ ReferenceOriginsExpression = LiteralType{} _ ReferenceOriginsExpression = Map{} - _ ReferenceOriginsExpression = ObjectAttributes{} _ ReferenceOriginsExpression = Object{} _ ReferenceOriginsExpression = Set{} _ ReferenceOriginsExpression = Reference{} @@ -39,7 +37,6 @@ var ( _ ReferenceTargetsExpression = List{} _ ReferenceTargetsExpression = LiteralType{} _ ReferenceTargetsExpression = Map{} - _ ReferenceTargetsExpression = ObjectAttributes{} _ ReferenceTargetsExpression = Object{} _ ReferenceTargetsExpression = Reference{} _ ReferenceTargetsExpression = Tuple{} diff --git a/schema/constraint_object.go b/schema/constraint_object.go index b7ba6844..5ab02177 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -39,7 +39,7 @@ func (o Object) FriendlyName() string { func (o Object) Copy() Constraint { return Object{ - Attributes: o.Attributes.Copy().(ObjectAttributes), + Attributes: o.Attributes.Copy(), Name: o.Name, Description: o.Description, } @@ -196,28 +196,10 @@ func (o Object) ConstraintType() (cty.Type, bool) { return cty.Object(objAttributes), true } -func (ObjectAttributes) isConstraintImpl() constraintSigil { - return constraintSigil{} -} - -func (oa ObjectAttributes) FriendlyName() string { - return "attributes" -} - -func (oa ObjectAttributes) Copy() Constraint { +func (oa ObjectAttributes) Copy() ObjectAttributes { m := make(ObjectAttributes, 0) for name, aSchema := range oa { m[name] = aSchema.Copy() } return m } - -func (oa ObjectAttributes) EmptyCompletionData(ctx context.Context, nextPlaceholder int, nestingLevel int) CompletionData { - // TODO - return CompletionData{} -} - -func (oa ObjectAttributes) EmptyHoverData(nestingLevel int) *HoverData { - // TODO - return nil -} diff --git a/schema/constraint_test.go b/schema/constraint_test.go index b795f0e4..ca334d44 100644 --- a/schema/constraint_test.go +++ b/schema/constraint_test.go @@ -17,7 +17,6 @@ var ( _ Constraint = LiteralType{} _ Constraint = LiteralValue{} _ Constraint = Map{} - _ Constraint = ObjectAttributes{} _ Constraint = Object{} _ Constraint = Set{} _ Constraint = Reference{} @@ -28,7 +27,6 @@ var ( _ ConstraintWithHoverData = LiteralType{} _ ConstraintWithHoverData = LiteralValue{} _ ConstraintWithHoverData = Map{} - _ ConstraintWithHoverData = ObjectAttributes{} _ ConstraintWithHoverData = Object{} _ ConstraintWithHoverData = Set{} _ ConstraintWithHoverData = Tuple{} From 300e60071b835cb7efcef0e472995b02e908f0d8 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 14 Mar 2023 15:38:29 +0000 Subject: [PATCH 3/6] schema: Make Object empty completion multi-line --- schema/constraint_object.go | 16 +++++---- schema/constraint_test.go | 72 +++++++++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/schema/constraint_object.go b/schema/constraint_object.go index 5ab02177..9ac19f81 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -60,10 +60,13 @@ func prefillRequiredFields(ctx context.Context) bool { } func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestingLevel int) CompletionData { + rootNesting := strings.Repeat(" ", nestingLevel) + attrNesting := strings.Repeat(" ", nestingLevel+1) + if len(o.Attributes) == 0 { return CompletionData{ - NewText: "{}", - Snippet: fmt.Sprintf("{ ${%d} }", placeholder), + NewText: fmt.Sprintf("{\n%s\n%s}", attrNesting, rootNesting), + Snippet: fmt.Sprintf("{\n%s${%d}\n%s}", attrNesting, placeholder, rootNesting), NextPlaceholder: placeholder + 1, } } @@ -71,7 +74,6 @@ func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestin newText := "{\n" snippet := "{\n" - nesting := strings.Repeat(" ", nestingLevel+1) lastPlaceholder := placeholder attrNames := sortedObjectExprAttrNames(o.Attributes) @@ -81,15 +83,15 @@ func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestin cData := attr.Constraint.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel+1) if cData.NewText == "" || cData.Snippet == "" { return CompletionData{ - NewText: "{}", - Snippet: fmt.Sprintf("{ ${%d} }", placeholder), + NewText: fmt.Sprintf("{\n%s\n%s}", attrNesting, rootNesting), + Snippet: fmt.Sprintf("{\n%s${%d}\n%s}", attrNesting, placeholder, rootNesting), TriggerSuggest: cData.TriggerSuggest, NextPlaceholder: placeholder + 1, } } - newText += fmt.Sprintf("%s%s = %s\n", nesting, name, cData.NewText) - snippet += fmt.Sprintf("%s%s = %s\n", nesting, name, cData.Snippet) + newText += fmt.Sprintf("%s%s = %s\n", attrNesting, name, cData.NewText) + snippet += fmt.Sprintf("%s%s = %s\n", attrNesting, name, cData.Snippet) lastPlaceholder = cData.NextPlaceholder } diff --git a/schema/constraint_test.go b/schema/constraint_test.go index ca334d44..4fd590b9 100644 --- a/schema/constraint_test.go +++ b/schema/constraint_test.go @@ -721,12 +721,80 @@ STRING }, }, CompletionData{ - NewText: `{}`, - Snippet: `{ ${1} }`, + NewText: `{ + +}`, + Snippet: `{ + ${1} +}`, TriggerSuggest: true, NextPlaceholder: 2, }, }, + { + Object{ + Attributes: map[string]*AttributeSchema{ + "foo": { + Constraint: LiteralType{ + Type: cty.Bool, + }, + }, + "bar": { + Constraint: LiteralType{ + Type: cty.String, + }, + }, + }, + }, + CompletionData{ + NewText: `{ + bar = "value" + foo = false +}`, + Snippet: `{ + bar = "${1:value}" + foo = ${2:false} +}`, + NextPlaceholder: 3, + }, + }, + { + Object{ + Attributes: map[string]*AttributeSchema{ + "foo": { + Constraint: LiteralType{ + Type: cty.Bool, + }, + }, + "bar": { + Constraint: Object{ + Attributes: map[string]*AttributeSchema{ + "baz": { + Constraint: LiteralType{ + Type: cty.String, + }, + }, + }, + }, + }, + }, + }, + CompletionData{ + NewText: `{ + bar = { + baz = "value" + } + foo = false +}`, + Snippet: `{ + bar = { + baz = "${1:value}" + } + foo = ${2:false} +}`, + NextPlaceholder: 3, + }, + }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { From 0b9bc9097fec575e68c7c5f9ef7c9de9292bcfe5 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 14 Mar 2023 10:45:06 +0000 Subject: [PATCH 4/6] schema: Refactor & Limit Object.EmptyCompletionData to required attributes --- schema/constraint_object.go | 77 ++++++++++++++++++++++--------------- schema/constraint_test.go | 7 +++- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/schema/constraint_object.go b/schema/constraint_object.go index 9ac19f81..29b596bb 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -60,54 +60,67 @@ func prefillRequiredFields(ctx context.Context) bool { } func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestingLevel int) CompletionData { - rootNesting := strings.Repeat(" ", nestingLevel) + nesting := strings.Repeat(" ", nestingLevel) attrNesting := strings.Repeat(" ", nestingLevel+1) + triggerSuggest := false + if len(o.Attributes) > 0 { + triggerSuggest = true + } - if len(o.Attributes) == 0 { - return CompletionData{ - NewText: fmt.Sprintf("{\n%s\n%s}", attrNesting, rootNesting), - Snippet: fmt.Sprintf("{\n%s${%d}\n%s}", attrNesting, placeholder, rootNesting), - NextPlaceholder: placeholder + 1, - } + emptyObjectData := CompletionData{ + NewText: fmt.Sprintf("{\n%s\n%s}", attrNesting, nesting), + Snippet: fmt.Sprintf("{\n%s${%d}\n%s}", attrNesting, placeholder, nesting), + NextPlaceholder: placeholder + 1, + TriggerSuggest: triggerSuggest, } - newText := "{\n" - snippet := "{\n" + attrData, ok := o.attributesCompletionData(ctx, placeholder, nestingLevel) + if !ok { + return emptyObjectData + } + + return CompletionData{ + NewText: fmt.Sprintf("{\n%s%s}", attrData.NewText, nesting), + Snippet: fmt.Sprintf("{\n%s%s}", attrData.Snippet, nesting), + NextPlaceholder: attrData.NextPlaceholder, + } +} - lastPlaceholder := placeholder +func (o Object) attributesCompletionData(ctx context.Context, placeholder, nestingLevel int) (CompletionData, bool) { + newText, snippet := "", "" + anyRequiredFields := false + attrNesting := strings.Repeat(" ", nestingLevel+1) + nextPlaceholder := placeholder attrNames := sortedObjectExprAttrNames(o.Attributes) for _, name := range attrNames { attr := o.Attributes[name] - cData := attr.Constraint.EmptyCompletionData(ctx, lastPlaceholder, nestingLevel+1) - if cData.NewText == "" || cData.Snippet == "" { - return CompletionData{ - NewText: fmt.Sprintf("{\n%s\n%s}", attrNesting, rootNesting), - Snippet: fmt.Sprintf("{\n%s${%d}\n%s}", attrNesting, placeholder, rootNesting), - TriggerSuggest: cData.TriggerSuggest, - NextPlaceholder: placeholder + 1, - } + attrData := attr.Constraint.EmptyCompletionData(ctx, nextPlaceholder, nestingLevel+1) + if attrData.NewText == "" || attrData.Snippet == "" { + return CompletionData{}, false } - newText += fmt.Sprintf("%s%s = %s\n", attrNesting, name, cData.NewText) - snippet += fmt.Sprintf("%s%s = %s\n", attrNesting, name, cData.Snippet) - lastPlaceholder = cData.NextPlaceholder - } + if attr.IsRequired { + anyRequiredFields = true + } else { + continue + } - if nestingLevel > 0 { - newText += fmt.Sprintf("%s}", strings.Repeat(" ", nestingLevel)) - snippet += fmt.Sprintf("%s}", strings.Repeat(" ", nestingLevel)) - } else { - newText += "}" - snippet += "}" + newText += fmt.Sprintf("%s%s = %s\n", attrNesting, name, attrData.NewText) + snippet += fmt.Sprintf("%s%s = %s\n", attrNesting, name, attrData.Snippet) + nextPlaceholder = attrData.NextPlaceholder } - return CompletionData{ - NewText: newText, - Snippet: snippet, - NextPlaceholder: lastPlaceholder, + if anyRequiredFields { + return CompletionData{ + NewText: newText, + Snippet: snippet, + NextPlaceholder: nextPlaceholder, + }, true } + + return CompletionData{}, false } func (o Object) EmptyHoverData(nestingLevel int) *HoverData { diff --git a/schema/constraint_test.go b/schema/constraint_test.go index 4fd590b9..a94f87d6 100644 --- a/schema/constraint_test.go +++ b/schema/constraint_test.go @@ -738,11 +738,13 @@ STRING Constraint: LiteralType{ Type: cty.Bool, }, + IsRequired: true, }, "bar": { Constraint: LiteralType{ Type: cty.String, }, + IsRequired: true, }, }, }, @@ -765,6 +767,7 @@ STRING Constraint: LiteralType{ Type: cty.Bool, }, + IsRequired: true, }, "bar": { Constraint: Object{ @@ -773,9 +776,11 @@ STRING Constraint: LiteralType{ Type: cty.String, }, + IsRequired: true, }, }, }, + IsRequired: true, }, }, }, @@ -797,7 +802,7 @@ STRING }, } for i, tc := range testCases { - t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + t.Run(fmt.Sprintf("%2d", i), func(t *testing.T) { ctx := context.Background() data := tc.cons.EmptyCompletionData(ctx, 1, 0) if diff := cmp.Diff(tc.expectedCompData, data); diff != "" { From 02495a122dc44c1f8ec7bc8fd55cb59b60b9ac40 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Tue, 14 Mar 2023 18:23:37 +0000 Subject: [PATCH 5/6] schema: Only prefill required fields if enabled --- schema/constraint_object.go | 4 +++ schema/constraint_test.go | 59 +++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/schema/constraint_object.go b/schema/constraint_object.go index 29b596bb..8c47ecb8 100644 --- a/schema/constraint_object.go +++ b/schema/constraint_object.go @@ -74,6 +74,10 @@ func (o Object) EmptyCompletionData(ctx context.Context, placeholder int, nestin TriggerSuggest: triggerSuggest, } + if !prefillRequiredFields(ctx) { + return emptyObjectData + } + attrData, ok := o.attributesCompletionData(ctx, placeholder, nestingLevel) if !ok { return emptyObjectData diff --git a/schema/constraint_test.go b/schema/constraint_test.go index a94f87d6..cb7c6da5 100644 --- a/schema/constraint_test.go +++ b/schema/constraint_test.go @@ -325,13 +325,15 @@ tomap({ func TestConstraint_EmptyCompletionData(t *testing.T) { testCases := []struct { - cons Constraint - expectedCompData CompletionData + cons Constraint + prefillRequiredFields bool + expectedCompData CompletionData }{ { LiteralType{ Type: cty.String, }, + false, CompletionData{ NewText: `"value"`, Snippet: `"${1:value}"`, @@ -344,6 +346,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { Type: cty.String, }, }, + false, CompletionData{ NewText: `[ "value" ]`, Snippet: `[ "${1:value}" ]`, @@ -354,6 +357,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { LiteralType{ Type: cty.List(cty.String), }, + false, CompletionData{ NewText: `[ "value" ]`, Snippet: `[ "${1:value}" ]`, @@ -366,6 +370,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { Type: cty.String, }, }, + false, CompletionData{ NewText: `[ "value" ]`, Snippet: `[ "${1:value}" ]`, @@ -376,6 +381,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { LiteralType{ Type: cty.Set(cty.String), }, + false, CompletionData{ NewText: `[ "value" ]`, Snippet: `[ "${1:value}" ]`, @@ -390,6 +396,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { "baz": cty.List(cty.String), }), }, + true, CompletionData{ NewText: `{ bar = 0 @@ -415,6 +422,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { }), }), }, + true, CompletionData{ NewText: `{ bar = 0 @@ -439,6 +447,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { LiteralValue{ Value: cty.StringVal("foobar"), }, + false, CompletionData{ NewText: `"foobar"`, Snippet: `"foobar"`, @@ -449,6 +458,7 @@ func TestConstraint_EmptyCompletionData(t *testing.T) { LiteralValue{ Value: cty.StringVal("foo\nbar"), }, + false, CompletionData{ NewText: `<< Date: Wed, 15 Mar 2023 07:50:56 +0000 Subject: [PATCH 6/6] Add comment per feedback --- decoder/label_candidates.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/decoder/label_candidates.go b/decoder/label_candidates.go index ef0fb356..71b9aad3 100644 --- a/decoder/label_candidates.go +++ b/decoder/label_candidates.go @@ -165,6 +165,9 @@ func requiredFieldsSnippet(bodySchema *schema.BodySchema, placeholder int, inden var snippet string if attr.Constraint != nil { + // We already know we want to do pre-filling at this point + // We could plumb through the context here, but it saves us + // an argument in multiple functions above. ctx := schema.WithPrefillRequiredFields(context.Background(), true) snippet = attr.Constraint.EmptyCompletionData(ctx, placeholder, indentCount).Snippet } else {