cue/format: indent multiline string literals based on context.

Change-Id: Ic58d29dfa347a2f0b3642e56676c2c944dbb8b2d
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7283
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/testdata/script/import_files.txt b/cmd/cue/cmd/testdata/script/import_files.txt
index 76be294..e94b5ac 100644
--- a/cmd/cue/cmd/testdata/script/import_files.txt
+++ b/cmd/cue/cmd/testdata/script/import_files.txt
@@ -8,9 +8,9 @@
 replicas: 1
 kind: "Service"
 name: """
-		supplement
-		foo
-		"""
+	supplement
+	foo
+	"""
 json: "[1, 2]"
 -- import/services.jsonl --
 {
diff --git a/cmd/cue/cmd/testdata/script/import_hoiststr.txt b/cmd/cue/cmd/testdata/script/import_hoiststr.txt
index d45414c..3a0be7d 100644
--- a/cmd/cue/cmd/testdata/script/import_hoiststr.txt
+++ b/cmd/cue/cmd/testdata/script/import_hoiststr.txt
@@ -14,9 +14,9 @@
 	"supplement\nfoo": [{
 		kind: "Service"
 		name: """
-		supplement
-		foo
-		"""
+			supplement
+			foo
+			"""
 		json: json656e63.Marshal(_cue_json)
 		let _cue_json = [1, 2]
 	}]
diff --git a/cue/format/printer.go b/cue/format/printer.go
index c9f43a3..0d5ed74 100644
--- a/cue/format/printer.go
+++ b/cue/format/printer.go
@@ -22,6 +22,7 @@
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/literal"
 	"cuelang.org/go/cue/token"
 )
 
@@ -116,6 +117,16 @@
 	case *ast.BasicLit:
 		data = x.Value
 		switch x.Kind {
+		case token.STRING:
+			// TODO: only do this when simplifying. Right now this does not
+			// give the right result, but it should be better if:
+			// 1) simplification is done as a separate step
+			// 2) simplified structs are explicitly referenced separately
+			//    in the AST.
+			if p.indent < 6 {
+				data = literal.IndentTabs(data, p.indent+1)
+			}
+
 		case token.INT:
 			if len(data) > 1 &&
 				data[0] == '0' &&
diff --git a/cue/literal/indent.go b/cue/literal/indent.go
new file mode 100644
index 0000000..193ca3b
--- /dev/null
+++ b/cue/literal/indent.go
@@ -0,0 +1,33 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package literal
+
+import "strings"
+
+// IndentTabs takes a quoted string and reindents it for the given indentation.
+// If a string is not a multiline string it will return the string as is.
+func IndentTabs(s string, n int) string {
+	indent := tabs(n)
+
+	qi, _, _, err := ParseQuotes(s, s)
+	if err != nil || !qi.multiline || qi.whitespace == indent {
+		return s
+	}
+
+	search := "\n" + qi.whitespace
+	replace := "\n" + indent
+
+	return strings.ReplaceAll(s, search, replace)
+}
diff --git a/cue/literal/indent_test.go b/cue/literal/indent_test.go
new file mode 100644
index 0000000..a10d657
--- /dev/null
+++ b/cue/literal/indent_test.go
@@ -0,0 +1,55 @@
+// Copyright 2020 CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package literal
+
+import (
+	"testing"
+)
+
+func TestIndentTabs(t *testing.T) {
+	testCases := []struct {
+		in  string
+		out string
+	}{{
+		in: `"""
+		foo
+		bar
+		"""`,
+		out: `"""
+			foo
+			bar
+			"""`,
+	}, {
+		in: `"""
+			foo
+			bar
+			"""`,
+		out: `"""
+			foo
+			bar
+			"""`,
+	}, {
+		in:  `""`,
+		out: `""`,
+	}}
+	for _, tc := range testCases {
+		t.Run("", func(t *testing.T) {
+			got := IndentTabs(tc.in, 3)
+			if got != tc.out {
+				t.Errorf("got %s; want %s", got, tc.out)
+			}
+		})
+	}
+}
diff --git a/cue/literal/quote.go b/cue/literal/quote.go
index e39438d..ac997b6 100644
--- a/cue/literal/quote.go
+++ b/cue/literal/quote.go
@@ -45,18 +45,21 @@
 
 // WithTabIndent returns a new Form with indentation set to the given number
 // of tabs. The result will be a multiline string.
-func (f Form) WithTabIndent(tabs int) Form {
-	if tabs < len(tabIndent) {
-		f.indent = tabIndent[:tabs]
-	} else {
-		f.indent = strings.Repeat("\t", tabs)
-	}
+func (f Form) WithTabIndent(n int) Form {
+	f.indent = tabs(n)
 	f.multiline = true
 	return f
 }
 
 const tabIndent = "\t\t\t\t\t\t\t\t\t\t\t\t"
 
+func tabs(n int) string {
+	if n < len(tabIndent) {
+		return tabIndent[:n]
+	}
+	return strings.Repeat("\t", n)
+}
+
 // WithOptionalIndent is like WithTabIndent, but only returns a multiline
 // strings if it doesn't contain any newline characters.
 func (f Form) WithOptionalTabIndent(tabs int) Form {
diff --git a/doc/tutorial/kubernetes/quick/services/mon/alertmanager/configmap.cue b/doc/tutorial/kubernetes/quick/services/mon/alertmanager/configmap.cue
index 5f4360e..618b75a 100644
--- a/doc/tutorial/kubernetes/quick/services/mon/alertmanager/configmap.cue
+++ b/doc/tutorial/kubernetes/quick/services/mon/alertmanager/configmap.cue
@@ -15,9 +15,9 @@
 				slack_configs: [{
 					channel: "#cloudmon"
 					text: """
-		{{ range .Alerts }}{{ .Annotations.description }}
-		{{ end }}
-		"""
+						{{ range .Alerts }}{{ .Annotations.description }}
+						{{ end }}
+						"""
 					send_resolved: true
 				}]
 			}]
diff --git a/internal/core/export/testdata/strings.txtar b/internal/core/export/testdata/strings.txtar
index 75a806f..2e5967c 100644
--- a/internal/core/export/testdata/strings.txtar
+++ b/internal/core/export/testdata/strings.txtar
@@ -10,9 +10,9 @@
 
 -- out/definition --
 a: """
-    multi
-    line
-    """
+	multi
+	line
+	"""
 b: "message: \(a)!"
 c: {
 	d: a
@@ -27,68 +27,68 @@
 == Simplified
 {
 	a: """
-	multi
-	line
-	"""
-	b: """
-	message: multi
-	line!
-	"""
-	c: {
-		d: """
 		multi
 		line
 		"""
+	b: """
+		message: multi
+		line!
+		"""
+	c: {
+		d: """
+			multi
+			line
+			"""
 	}
 }
 == Raw
 {
 	a: """
-	multi
-	line
-	"""
-	b: """
-	message: multi
-	line!
-	"""
-	c: {
-		d: """
 		multi
 		line
 		"""
+	b: """
+		message: multi
+		line!
+		"""
+	c: {
+		d: """
+			multi
+			line
+			"""
 	}
 }
 == Final
 {
 	a: """
-	multi
-	line
-	"""
-	b: """
-	message: multi
-	line!
-	"""
-	c: {
-		d: """
 		multi
 		line
 		"""
+	b: """
+		message: multi
+		line!
+		"""
+	c: {
+		d: """
+			multi
+			line
+			"""
 	}
 }
 == All
 {
 	a: """
-	multi
-	line
-	"""
-	b: """
-	message: multi
-	line!
-	"""
-	c: {
-		d: """
 		multi
 		line
 		"""
+	b: """
+		message: multi
+		line!
+		"""
+	c: {
+		d: """
+			multi
+			line
+			"""
 	}
 }
diff --git a/internal/third_party/yaml/decode_test.go b/internal/third_party/yaml/decode_test.go
index 9156cda..7b70071 100644
--- a/internal/third_party/yaml/decode_test.go
+++ b/internal/third_party/yaml/decode_test.go
@@ -205,11 +205,11 @@
 		"scalar: | # Comment\n\n literal\n\n \ttext\n\n",
 		`scalar: """
 
-		literal
+	literal
 
-		\ttext
+	\ttext
 
-		"""`,
+	"""`,
 	},
 
 	// Folded block scalar
@@ -217,14 +217,14 @@
 		"scalar: > # Comment\n\n folded\n line\n \n next\n line\n  * one\n  * two\n\n last\n line\n\n",
 		`scalar: """
 
-		folded line
-		next line
-		 * one
-		 * two
+	folded line
+	next line
+	 * one
+	 * two
 
-		last line
+	last line
 
-		"""`,
+	"""`,
 	},
 
 	// Structs
@@ -456,10 +456,10 @@
 			` Line separator\u2028\` + "\n" +
 			` Paragraph separator\u2029"` + "\n",
 		`"""
-		Generic line break (no glyph)
-		Generic line break (glyphed)
-		Line separator\u2028Paragraph separator\u2029
-		"""`,
+	Generic line break (no glyph)
+	Generic line break (glyphed)
+	Line separator\u2028Paragraph separator\u2029
+	"""`,
 	},
 
 	// bug 1243827