|
5 | 5 | package cmp
|
6 | 6 |
|
7 | 7 | import (
|
| 8 | + "bytes" |
8 | 9 | "fmt"
|
9 | 10 | "reflect"
|
10 | 11 | "strconv"
|
@@ -138,14 +139,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
|
138 | 139 | }
|
139 | 140 | }()
|
140 | 141 | if prefix != "" {
|
141 |
| - maxLen := len(strVal) |
142 |
| - if opts.LimitVerbosity { |
143 |
| - maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... |
144 |
| - } |
145 |
| - if len(strVal) > maxLen+len(textEllipsis) { |
146 |
| - return textLine(prefix + formatString(strVal[:maxLen]) + string(textEllipsis)) |
147 |
| - } |
148 |
| - return textLine(prefix + formatString(strVal)) |
| 142 | + return opts.formatString(prefix, strVal) |
149 | 143 | }
|
150 | 144 | }
|
151 | 145 | }
|
@@ -177,14 +171,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
|
177 | 171 | case reflect.Complex64, reflect.Complex128:
|
178 | 172 | return textLine(fmt.Sprint(v.Complex()))
|
179 | 173 | case reflect.String:
|
180 |
| - maxLen := v.Len() |
181 |
| - if opts.LimitVerbosity { |
182 |
| - maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... |
183 |
| - } |
184 |
| - if v.Len() > maxLen+len(textEllipsis) { |
185 |
| - return textLine(formatString(v.String()[:maxLen]) + string(textEllipsis)) |
186 |
| - } |
187 |
| - return textLine(formatString(v.String())) |
| 174 | + return opts.formatString("", v.String()) |
188 | 175 | case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
189 | 176 | return textLine(formatPointer(value.PointerOf(v), true))
|
190 | 177 | case reflect.Struct:
|
@@ -216,6 +203,17 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
|
216 | 203 | if v.IsNil() {
|
217 | 204 | return textNil
|
218 | 205 | }
|
| 206 | + |
| 207 | + // Check whether this is a []byte of text data. |
| 208 | + if t.Elem() == reflect.TypeOf(byte(0)) { |
| 209 | + b := v.Bytes() |
| 210 | + isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } |
| 211 | + if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { |
| 212 | + out = opts.formatString("", string(b)) |
| 213 | + return opts.WithTypeMode(emitType).FormatType(t, out) |
| 214 | + } |
| 215 | + } |
| 216 | + |
219 | 217 | fallthrough
|
220 | 218 | case reflect.Array:
|
221 | 219 | maxLen := v.Len()
|
@@ -301,6 +299,49 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind,
|
301 | 299 | }
|
302 | 300 | }
|
303 | 301 |
|
| 302 | +func (opts formatOptions) formatString(prefix, s string) textNode { |
| 303 | + maxLen := len(s) |
| 304 | + maxLines := strings.Count(s, "\n") + 1 |
| 305 | + if opts.LimitVerbosity { |
| 306 | + maxLen = (1 << opts.verbosity()) << 5 // 32, 64, 128, 256, etc... |
| 307 | + maxLines = (1 << opts.verbosity()) << 2 // 4, 8, 16, 32, 64, etc... |
| 308 | + } |
| 309 | + |
| 310 | + // For multiline strings, use the triple-quote syntax, |
| 311 | + // but only use it when printing removed or inserted nodes since |
| 312 | + // we only want the extra verbosity for those cases. |
| 313 | + lines := strings.Split(strings.TrimSuffix(s, "\n"), "\n") |
| 314 | + isTripleQuoted := len(lines) >= 4 && (opts.DiffMode == '-' || opts.DiffMode == '+') |
| 315 | + for i := 0; i < len(lines) && isTripleQuoted; i++ { |
| 316 | + lines[i] = strings.TrimPrefix(strings.TrimSuffix(lines[i], "\r"), "\r") // trim leading/trailing carriage returns for legacy Windows endline support |
| 317 | + isPrintable := func(r rune) bool { |
| 318 | + return unicode.IsPrint(r) || r == '\t' // specially treat tab as printable |
| 319 | + } |
| 320 | + line := lines[i] |
| 321 | + isTripleQuoted = !strings.HasPrefix(strings.TrimPrefix(line, prefix), `"""`) && !strings.HasPrefix(line, "...") && strings.TrimFunc(line, isPrintable) == "" && len(line) <= maxLen |
| 322 | + } |
| 323 | + if isTripleQuoted { |
| 324 | + var list textList |
| 325 | + list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) |
| 326 | + for i, line := range lines { |
| 327 | + if numElided := len(lines) - i; i == maxLines-1 && numElided > 1 { |
| 328 | + comment := commentString(fmt.Sprintf("%d elided lines", numElided)) |
| 329 | + list = append(list, textRecord{Diff: opts.DiffMode, Value: textEllipsis, ElideComma: true, Comment: comment}) |
| 330 | + break |
| 331 | + } |
| 332 | + list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(line), ElideComma: true}) |
| 333 | + } |
| 334 | + list = append(list, textRecord{Diff: opts.DiffMode, Value: textLine(prefix + `"""`), ElideComma: true}) |
| 335 | + return &textWrap{Prefix: "(", Value: list, Suffix: ")"} |
| 336 | + } |
| 337 | + |
| 338 | + // Format the string as a single-line quoted string. |
| 339 | + if len(s) > maxLen+len(textEllipsis) { |
| 340 | + return textLine(prefix + formatString(s[:maxLen]) + string(textEllipsis)) |
| 341 | + } |
| 342 | + return textLine(prefix + formatString(s)) |
| 343 | +} |
| 344 | + |
304 | 345 | // formatMapKey formats v as if it were a map key.
|
305 | 346 | // The result is guaranteed to be a single line.
|
306 | 347 | func formatMapKey(v reflect.Value, disambiguate bool, ptrs *pointerReferences) string {
|
|
0 commit comments