cue/format: some tweaks

Always keep tabs used for indentation and explicitly
expand. This fixes some alignment issues.

This also prepares for having better eval output.

The default for single-element objects without
position information is now to not simplify.

Change-Id: I47546bce433cf99e2fc153540591cea51841b24c
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2165
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/testdata/partial/eval.out b/cmd/cue/cmd/testdata/partial/eval.out
index 0f97899..ac847f1 100644
--- a/cmd/cue/cmd/testdata/partial/eval.out
+++ b/cmd/cue/cmd/testdata/partial/eval.out
@@ -5,16 +5,20 @@
     A = a
     b: {
         idx: A[str]
-        a b: 4
+        a: {
+            b: 4
+        }
         str: string
     }
     a: {
-        b:  3
-        c:  4
+        b: 3
+        c: 4
     }
     c: {
         idx: 3
-        a b: 4
+        a: {
+            b: 4
+        }
         str: "b"
     }
 }
diff --git a/cue/export_test.go b/cue/export_test.go
index 6835f60..68d59bd 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -256,7 +256,7 @@
 			{
 				b: [{
 					<X>: int
-					"f": 4 if a > 4
+					f:   4 if a > 4
 				}][a]
 				a: int
 				c: 1
@@ -277,7 +277,7 @@
 
 			buf := &bytes.Buffer{}
 			opts := options{raw: !tc.eval}
-			err := format.Node(buf, export(ctx, v.eval(ctx), opts))
+			err := format.Node(buf, export(ctx, v.eval(ctx), opts), format.Simplify())
 			if err != nil {
 				log.Fatal(err)
 			}
@@ -330,7 +330,7 @@
 
 			buf := &bytes.Buffer{}
 			opts := options{raw: false}
-			err = format.Node(buf, export(ctx, v.eval(ctx), opts))
+			err = format.Node(buf, export(ctx, v.eval(ctx), opts), format.Simplify())
 			if err != nil {
 				log.Fatal(err)
 			}
diff --git a/cue/format/format.go b/cue/format/format.go
index 89f0438..9bb81a5 100644
--- a/cue/format/format.go
+++ b/cue/format/format.go
@@ -107,14 +107,18 @@
 type config struct {
 	UseSpaces bool
 	TabIndent bool
-	Tabwidth  int // default: 8
+	Tabwidth  int // default: 4
 	Indent    int // default: 0 (all code is indented at least by this much)
 
 	simplify bool
 }
 
 func newConfig(opt []Option) *config {
-	cfg := &config{Tabwidth: 8, TabIndent: true, UseSpaces: true}
+	cfg := &config{
+		Tabwidth:  8,
+		TabIndent: true,
+		UseSpaces: true,
+	}
 	for _, o := range opt {
 		o(cfg)
 	}
@@ -123,27 +127,24 @@
 
 // Config defines the output of Fprint.
 func (cfg *config) fprint(output io.Writer, node interface{}) (err error) {
-	// print node
 	var p printer
 	p.init(cfg)
 	if err = printNode(node, &p); err != nil {
 		return err
 	}
 
-	minwidth := cfg.Tabwidth
-
 	padchar := byte('\t')
 	if cfg.UseSpaces {
-		padchar = ' '
+		padchar = byte(' ')
 	}
 
-	twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape
+	twmode := tabwriter.StripEscape | tabwriter.TabIndent | tabwriter.DiscardEmptyColumns
 	if cfg.TabIndent {
-		minwidth = 0
 		twmode |= tabwriter.TabIndent
 	}
 
-	tw := tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
+	buf := &bytes.Buffer{}
+	tw := tabwriter.NewWriter(buf, 0, cfg.Tabwidth, 1, padchar, twmode)
 
 	// write printer result via tabwriter/trimmer to output
 	if _, err = tw.Write(p.output); err != nil {
@@ -151,7 +152,16 @@
 	}
 
 	err = tw.Flush()
-	return
+	if err != nil {
+		return err
+	}
+
+	b := buf.Bytes()
+	if !cfg.TabIndent {
+		b = bytes.ReplaceAll(b, []byte{'\t'}, bytes.Repeat([]byte{' '}, cfg.Tabwidth))
+	}
+	_, err = output.Write(b)
+	return err
 }
 
 // A formatter walks a syntax.Node, interspersed with comments and spacing
diff --git a/cue/format/format_test.go b/cue/format/format_test.go
index c6e49ae..46b8a4a 100644
--- a/cue/format/format_test.go
+++ b/cue/format/format_test.go
@@ -58,7 +58,7 @@
 // if any.
 func format(src []byte, mode checkMode) ([]byte, error) {
 	// parse src
-	var opts []Option
+	opts := []Option{TabIndent(true)}
 	if mode&simplify != 0 {
 		opts = append(opts, Simplify())
 	}
diff --git a/cue/format/node.go b/cue/format/node.go
index 6054e31..97850f2 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -116,7 +116,9 @@
 		lastSize := len(f.labelBuf)
 		f.labelBuf = f.labelBuf[:0]
 		first, opt := n.Label, n.Optional != token.NoPos
-		for {
+		// If the field has a valid position, we assume that an unspecified
+		// Lbrace does not signal the intend to collapse fields.
+		for n.Label.Pos().IsValid() || f.printer.cfg.simplify {
 			obj, ok := n.Value.(*ast.StructLit)
 			if !ok || len(obj.Elts) != 1 || (obj.Lbrace.IsValid() && !f.printer.cfg.simplify) {
 				break
diff --git a/internal/protobuf/protobuf_test.go b/internal/protobuf/protobuf_test.go
index d75126d..e21a126 100644
--- a/internal/protobuf/protobuf_test.go
+++ b/internal/protobuf/protobuf_test.go
@@ -46,7 +46,7 @@
 			if f, err := Parse(filename, nil, c); err != nil {
 				fmt.Fprintln(out, err)
 			} else {
-				format.Node(out, f)
+				format.Node(out, f, format.Simplify())
 			}
 
 			wantFile := filepath.Join("testdata", filepath.Base(file)+".out.cue")
diff --git a/internal/protobuf/testdata/client_config.proto.out.cue b/internal/protobuf/testdata/client_config.proto.out.cue
index 880309e..efa1f40 100644
--- a/internal/protobuf/testdata/client_config.proto.out.cue
+++ b/internal/protobuf/testdata/client_config.proto.out.cue
@@ -52,8 +52,8 @@
 	"FAIL_CLOSE"
 
 NetworkFailPolicy_FailPolicy_value: {
-	"FAIL_OPEN":  0
-	"FAIL_CLOSE": 1
+	FAIL_OPEN:  0
+	FAIL_CLOSE: 1
 }
 
 //  Defines the per-service client configuration.
diff --git a/internal/protobuf/testdata/gateway.proto.out.cue b/internal/protobuf/testdata/gateway.proto.out.cue
index 14e4624..dc17a26 100644
--- a/internal/protobuf/testdata/gateway.proto.out.cue
+++ b/internal/protobuf/testdata/gateway.proto.out.cue
@@ -213,8 +213,8 @@
 	//  label search is restricted to the configuration namespace in which the
 	//  the resource is present. In other words, the Gateway resource must
 	//  reside in the same namespace as the gateway workload instance.
-	selector <_>: string
-	selector?: {<name>: name}
+	selector <_>:     string
+	selector? <name>: name
 }
 
 //  `Server` describes the properties of the proxy on a given load balancer
@@ -413,10 +413,10 @@
 	"AUTO_PASSTHROUGH"
 
 Server_TLSOptions_TLSmode_value: {
-	"PASSTHROUGH":      0
-	"SIMPLE":           1
-	"MUTUAL":           2
-	"AUTO_PASSTHROUGH": 3
+	PASSTHROUGH:      0
+	SIMPLE:           1
+	MUTUAL:           2
+	AUTO_PASSTHROUGH: 3
 }
 
 //  TLS protocol versions.
@@ -428,11 +428,11 @@
 	"TLSV1_3" //  TLS version 1.3
 
 Server_TLSOptions_TLSProtocol_value: {
-	"TLS_AUTO": 0
-	"TLSV1_0":  1
-	"TLSV1_1":  2
-	"TLSV1_2":  3
-	"TLSV1_3":  4
+	TLS_AUTO: 0
+	TLSV1_0:  1
+	TLSV1_1:  2
+	TLSV1_2:  3
+	TLSV1_3:  4
 }
 
 //  Port describes the properties of a specific port of a service.
diff --git a/internal/third_party/yaml/decode_test.go b/internal/third_party/yaml/decode_test.go
index 71cbf3c..d33e56c 100644
--- a/internal/third_party/yaml/decode_test.go
+++ b/internal/third_party/yaml/decode_test.go
@@ -209,7 +209,9 @@
 	// Structs
 	{
 		"a: {b: c}",
-		`a b: "c"`,
+		`a: {
+	b: "c"
+}`,
 	},
 	{
 		"hello: world",
@@ -231,7 +233,10 @@
 		"a: true",
 	}, {
 		"{ a: 1, b: {c: 1} }",
-		"a: 1\nb c: 1",
+		`a: 1
+b: {
+	c: 1
+}`,
 	},
 
 	// Some cross type conversions
@@ -396,7 +401,12 @@
 d: 2`,
 	}, {
 		"a: &a {c: 1}\nb: *a",
-		"a c: 1\nb c: 1",
+		`a: {
+	c: 1
+}
+b: {
+	c: 1
+}`,
 	}, {
 		"a: &a [1, 2]\nb: *a",
 		"a: [1, 2]\nb: [1, 2]", // TODO: a: [1, 2], b: a
@@ -449,7 +459,9 @@
 	// issue #295 (allow scalars with colons in flow mappings and sequences)
 	{
 		"a: {b: https://github.com/go-yaml/yaml}",
-		`a b: "https://github.com/go-yaml/yaml"`,
+		`a: {
+	b: "https://github.com/go-yaml/yaml"
+}`,
 	},
 	{
 		"a: [https://github.com/go-yaml/yaml]",
@@ -493,13 +505,19 @@
 a: 1
 d: 4
 c: 3
-sub e: 5`,
+sub: {
+	e: 5
+}`,
 	},
 
 	// Issue #39.
 	{
 		"a:\n b:\n  c: d\n",
-		`a b c: "d"`,
+		`a: {
+	b: {
+		c: "d"
+	}
+}`,
 	},
 
 	// Timestamps
diff --git a/internal/third_party/yaml/testdata/merge.out b/internal/third_party/yaml/testdata/merge.out
index e3edf5a..b8ed7a4 100644
--- a/internal/third_party/yaml/testdata/merge.out
+++ b/internal/third_party/yaml/testdata/merge.out
@@ -1,16 +1,18 @@
 // From http://yaml.org/type/merge.html
 // Test
-anchors list: [{
-	x: 1
-	y: 2
-}, {
-	x: 0
-	y: 2
-}, {
-	r: 10
-}, {
-	r: 1
-}]
+anchors: {
+	list: [{
+		x: 1
+		y: 2
+	}, {
+		x: 0
+		y: 2
+	}, {
+		r: 10
+	}, {
+		r: 1
+	}]
+}
 // All the following maps are equal:
 plain: {
 	// Explicit keys