diff --git a/schema/constraint_literal_value.go b/schema/constraint_literal_value.go index 0bde2024..32c60f96 100644 --- a/schema/constraint_literal_value.go +++ b/schema/constraint_literal_value.go @@ -1,6 +1,11 @@ package schema import ( + "fmt" + "sort" + "strconv" + "strings" + "github.com/hashicorp/hcl-lang/lang" "github.com/zclconf/go-cty/cty" ) @@ -15,6 +20,10 @@ type LiteralValue struct { // Description defines description of the value Description lang.MarkupContent + + // nestingLvl represents current level of nesting + // used for correctly indenting complex types. + nestingLvl int } func (LiteralValue) isConstraintImpl() constraintSigil { @@ -34,5 +43,136 @@ func (lv LiteralValue) Copy() Constraint { } func (lv LiteralValue) EmptyCompletionData(nextPlaceholder int) CompletionData { - return CompletionData{} + // primitive types + switch lv.Val.Type() { + case cty.String: + return CompletionData{ + NewText: fmt.Sprintf("%q", lv.Val.AsString()), + Snippet: fmt.Sprintf(`"${%d:%s}"`, nextPlaceholder, lv.Val.AsString()), + LastPlaceholder: nextPlaceholder, + } + case cty.Bool: + return CompletionData{ + NewText: fmt.Sprintf("%t", lv.Val.True()), + Snippet: fmt.Sprintf(`${%d:%t}`, nextPlaceholder, lv.Val.True()), + LastPlaceholder: nextPlaceholder, + } + case cty.Number: + return CompletionData{ + NewText: formatNumberVal(lv.Val), + Snippet: fmt.Sprintf(`${%d:%s}`, nextPlaceholder, formatNumberVal(lv.Val)), + LastPlaceholder: nextPlaceholder, + } + } + + // complex types typically span multiple lines, so indenting matters + indent := strings.Repeat(" ", lv.nestingLvl+1) + endBraceIndent := strings.Repeat(" ", lv.nestingLvl) + + // TODO: Why do we not indent NewText same way as Snippet? + + if lv.Val.Type().IsMapType() { + newText := "{\n" + snippet := "{\n" + + valueMap := lv.Val.AsValueMap() + mapKeys := sortedKeysOfValueMap(valueMap) + for _, key := range mapKeys { + elCons := LiteralValue{ + Val: valueMap[key], + nestingLvl: lv.nestingLvl + 1, + } + elCd := elCons.EmptyCompletionData(nextPlaceholder) + + newText += fmt.Sprintf(" %q = %s\n", key, elCd.NewText) + snippet += fmt.Sprintf("%s%q = %s\n", indent, key, elCd.Snippet) + } + newText += "}" + snippet += fmt.Sprintf("%s}", endBraceIndent) + + return CompletionData{ + NewText: newText, + Snippet: snippet, + LastPlaceholder: nextPlaceholder, + } + } + + if lv.Val.Type().IsObjectType() { + newText := "{\n" + snippet := "{\n" + + attrNames := sortedObjectAttrNames(lv.Val.Type()) + + for _, name := range attrNames { + elCons := LiteralValue{ + Val: lv.Val.GetAttr(name), + nestingLvl: lv.nestingLvl + 1, + } + elCd := elCons.EmptyCompletionData(nextPlaceholder) + + newText += fmt.Sprintf(" %s = %s\n", name, elCd.NewText) + snippet += fmt.Sprintf("%s%s = %s\n", indent, name, elCd.Snippet) + } + + newText += "}" + snippet += fmt.Sprintf("%s}", endBraceIndent) + + return CompletionData{ + NewText: newText, + Snippet: snippet, + LastPlaceholder: nextPlaceholder, + } + } + + if lv.Val.Type().IsListType() || lv.Val.Type().IsSetType() || lv.Val.Type().IsTupleType() { + newText := "[\n" + snippet := "[\n" + + for _, elem := range lv.Val.AsValueSlice() { + elCons := LiteralValue{ + Val: elem, + nestingLvl: lv.nestingLvl + 1, + } + elCd := elCons.EmptyCompletionData(nextPlaceholder) + + newText += fmt.Sprintf(" %s,\n", elCd.NewText) + snippet += fmt.Sprintf("%s%s,\n", indent, elCd.Snippet) + } + + newText += "]" + snippet += fmt.Sprintf("%s]", endBraceIndent) + + return CompletionData{ + NewText: newText, + Snippet: snippet, + LastPlaceholder: nextPlaceholder, + } + } + + return CompletionData{ + NewText: "", + Snippet: "", + LastPlaceholder: nextPlaceholder, + } +} + +func sortedKeysOfValueMap(m map[string]cty.Value) []string { + keys := make([]string, 0) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func formatNumberVal(val cty.Value) string { + bf := val.AsBigFloat() + + if bf.IsInt() { + intNum, _ := bf.Int64() + return fmt.Sprintf("%d", intNum) + } + + fNum, _ := bf.Float64() + return strconv.FormatFloat(fNum, 'f', -1, 64) }