Commit e8a049b4 authored by Robert Griesemer's avatar Robert Griesemer

gofmt: modified algorithm for alignment of multi-line composite/list entries

- only manual changes are in src/pkg/go/printer/nodes.go
- use a heuristic to determine "outliers" such that not entire composites are
  forced to align with them
- improves several places that were not unligned before due too simple heuristic
- unalignes some cases that contain "outliers"
- gofmt -w src misc

Fixes #644.

R=rsc, r
CC=golang-dev
https://golang.org/cl/241041
parent 3e4e4ec7
...@@ -109,10 +109,15 @@ func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) { ...@@ -109,10 +109,15 @@ func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) {
} }
// isOneLineExpr returns true if x is "small enough" to fit onto a single line. // Compute the key size of a key:value expression.
func (p *printer) isOneLineExpr(x ast.Expr) bool { // Returns 0 if the expression doesn't fit onto a single line.
const maxSize = 60 // aproximate value, excluding space for comments func (p *printer) keySize(pair *ast.KeyValueExpr) int {
return p.nodeSize(x, maxSize) <= maxSize const infinity = 1e6 // larger than any source line
if p.nodeSize(pair, infinity) <= infinity {
// entire expression fits on one line - return key size
return p.nodeSize(pair.Key, infinity)
}
return 0
} }
...@@ -120,6 +125,10 @@ func (p *printer) isOneLineExpr(x ast.Expr) bool { ...@@ -120,6 +125,10 @@ func (p *printer) isOneLineExpr(x ast.Expr) bool {
// source lines, the original line breaks are respected between // source lines, the original line breaks are respected between
// expressions. Sets multiLine to true if the list spans multiple // expressions. Sets multiLine to true if the list spans multiple
// lines. // lines.
//
// TODO(gri) Consider rewriting this to be independent of []ast.Expr
// so that we can use the algorithm for any kind of list
// (e.g., pass list via a channel over which to range).
func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next token.Position) { func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next token.Position) {
if len(list) == 0 { if len(list) == 0 {
return return
...@@ -165,30 +174,69 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode ...@@ -165,30 +174,69 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
ws = indent ws = indent
} }
oneLiner := false // true if the previous expression fit on a single line
prevBreak := -1 // index of last expression that was followed by a linebreak
// the first linebreak is always a formfeed since this section must not // the first linebreak is always a formfeed since this section must not
// depend on any previous formatting // depend on any previous formatting
prevBreak := -1 // index of last expression that was followed by a linebreak
if prev.IsValid() && prev.Line < line && p.linebreak(line, 1, 2, ws, true) { if prev.IsValid() && prev.Line < line && p.linebreak(line, 1, 2, ws, true) {
ws = ignore ws = ignore
*multiLine = true *multiLine = true
prevBreak = 0 prevBreak = 0
} }
// initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line
size := 0
// print all list elements
for i, x := range list { for i, x := range list {
prev := line prevLine := line
line = x.Pos().Line line = x.Pos().Line
// determine if the next linebreak, if any, needs to use formfeed:
// in general, use the entire node size to make the decision; for
// key:value expressions, use the key size
// TODO(gri) for a better result, should probably incorporate both
// the key and the node size into the decision process
useFF := true
// determine size
prevSize := size
const infinity = 1e6 // larger than any source line
size = p.nodeSize(x, infinity)
pair, isPair := x.(*ast.KeyValueExpr)
if size <= infinity {
// x fits on a single line
if isPair {
size = p.nodeSize(pair.Key, infinity) // size <= infinity
}
} else {
size = 0
}
// if the previous line and the current line had single-
// line-expressions and the key sizes are small or the
// the ratio between the key sizes does not exceed a
// threshold, align columns and do not use formfeed
if prevSize > 0 && size > 0 {
const smallSize = 20
if prevSize <= smallSize && size <= smallSize {
useFF = false
} else {
const r = 4 // threshold
ratio := float(size) / float(prevSize)
useFF = ratio <= 1/r || r <= ratio
}
}
if i > 0 { if i > 0 {
if mode&commaSep != 0 { if mode&commaSep != 0 {
p.print(token.COMMA) p.print(token.COMMA)
} }
if prev < line && prev > 0 && line > 0 { if prevLine < line && prevLine > 0 && line > 0 {
// lines are broken using newlines so comments remain aligned, // lines are broken using newlines so comments remain aligned
// but if an expression is not a "one-line" expression, or if // unless forceFF is set or there are multiple expressions on
// multiple expressions are on the same line, the section is // the same line in which case formfeed is used
// broken with a formfeed // broken with a formfeed
if p.linebreak(line, 1, 2, ws, !oneLiner || prevBreak+1 < i) { if p.linebreak(line, 1, 2, ws, useFF || prevBreak+1 < i) {
ws = ignore ws = ignore
*multiLine = true *multiLine = true
prevBreak = i prevBreak = i
...@@ -197,17 +245,14 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode ...@@ -197,17 +245,14 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
p.print(blank) p.print(blank)
} }
} }
// determine if x satisfies the "one-liner" criteria
// TODO(gri): determine if the multiline information returned if isPair && size > 0 && len(list) > 1 {
// from p.expr0 is precise enough so it could be // we have a key:value expression that fits onto one line and
// used instead // is in a list with more then one entry: use a column for the
oneLiner = p.isOneLineExpr(x) // key such that consecutive entries can align if possible
if t, isPair := x.(*ast.KeyValueExpr); isPair && oneLiner && len(list) > 1 { p.expr(pair.Key, multiLine)
// we have a key:value expression that fits onto one line, and p.print(pair.Colon, token.COLON, vtab)
// is a list with more then one entry: align all the values p.expr(pair.Value, multiLine)
p.expr(t.Key, multiLine)
p.print(t.Colon, token.COLON, vtab)
p.expr(t.Value, multiLine)
} else { } else {
p.expr0(x, depth, multiLine) p.expr0(x, depth, multiLine)
} }
......
...@@ -540,6 +540,30 @@ func _() { ...@@ -540,6 +540,30 @@ func _() {
} }
// alignment of map composite entries
var _ = map[int]int{
// small key sizes: always align even if size ratios are large
a: a,
abcdefghabcdefgh: a,
ab: a,
abc: a,
abcdefgabcdefg: a,
abcd: a,
abcde: a,
abcdef: a,
// mixed key sizes: align when key sizes change within accepted ratio
abcdefgh: a,
abcdefghabcdefg: a,
abcdefghij: a,
abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // outlier - do not align with previous line
abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // align with previous line
ab: a, // do not align with previous line
abcde: a, // align with previous line
}
func _() { func _() {
var _ = T{ var _ = T{
a, // must introduce trailing comma a, // must introduce trailing comma
......
...@@ -534,6 +534,30 @@ func _() { ...@@ -534,6 +534,30 @@ func _() {
} }
// alignment of map composite entries
var _ = map[int]int{
// small key sizes: always align even if size ratios are large
a: a,
abcdefghabcdefgh: a,
ab: a,
abc: a,
abcdefgabcdefg: a,
abcd: a,
abcde: a,
abcdef: a,
// mixed key sizes: align when key sizes change within accepted ratio
abcdefgh: a,
abcdefghabcdefg: a,
abcdefghij: a,
abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // outlier - do not align with previous line
abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // align with previous line
ab: a, // do not align with previous line
abcde: a, // align with previous line
}
func _() { func _() {
var _ = T{ var _ = T{
a, // must introduce trailing comma a, // must introduce trailing comma
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment