// ----------------------------------------------------------------------------
 // Common AST nodes.
 
-// Print as many newlines as necessary (but at least min and and at most
-// max newlines) to get to the current line. ws is printed before the first
-// line break. If newSection is set, the first line break is printed as
-// formfeed. Returns true if any line break was printed; returns false otherwise.
+// Print as many newlines as necessary (but at least min newlines) to get to
+// the current line. ws is printed before the first line break. If newSection
+// is set, the first line break is printed as formfeed. Returns true if any
+// line break was printed; returns false otherwise.
 //
-// TODO(gri): Reconsider signature (provide position instead of line)
+// TODO(gri): linebreak may add too many lines if the next statement at "line"
+//            is preceeded by comments because the computation of n assumes
+//            the current position before the comment and the target position
+//            after the comment. Thus, after interspersing such comments, the
+//            space taken up by them is not considered to reduce the number of
+//            linebreaks. At the moment there is no easy way to know about
+//            future (not yet interspersed) comments in this function.
 //
-func (p *printer) linebreak(line, min, max int, ws whiteSpace, newSection bool) (printedBreak bool) {
-       n := line - p.pos.Line
-       switch {
-       case n < min:
-               n = min
-       case n > max:
-               n = max
-       }
-
+func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (printedBreak bool) {
+       n := p.nlines(line-p.pos.Line, min)
        if n > 0 {
                p.print(ws)
                if newSection {
                        p.print(formfeed)
                        n--
-                       printedBreak = true
                }
-       }
-       for ; n > 0; n-- {
-               p.print(newline)
+               for ; n > 0; n-- {
+                       p.print(newline)
+               }
                printedBreak = true
        }
        return
                // lines for them.
                linebreakMin = 0
        }
-       if prev.IsValid() && prev.Line < line && p.linebreak(line, linebreakMin, 2, ws, true) {
+       if prev.IsValid() && prev.Line < line && p.linebreak(line, linebreakMin, ws, true) {
                ws = ignore
                *multiLine = true
                prevBreak = 0
                                // unless forceFF is set or there are multiple expressions on
                                // the same line in which case formfeed is used
                                // broken with a formfeed
-                               if p.linebreak(line, linebreakMin, 2, ws, useFF || prevBreak+1 < i) {
+                               if p.linebreak(line, linebreakMin, ws, useFF || prevBreak+1 < i) {
                                        ws = ignore
                                        *multiLine = true
                                        prevBreak = i
 
 
 func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprContext) {
+       p.nesting++
+       defer func() {
+               p.nesting--
+       }()
+
        lbrace := fields.Opening
        list := fields.List
        rbrace := fields.Closing
                var ml bool
                for i, f := range list {
                        if i > 0 {
-                               p.linebreak(f.Pos().Line, 1, 2, ignore, ml)
+                               p.linebreak(f.Pos().Line, 1, ignore, ml)
                        }
                        ml = false
                        extraTabs := 0
                var ml bool
                for i, f := range list {
                        if i > 0 {
-                               p.linebreak(f.Pos().Line, 1, 2, ignore, ml)
+                               p.linebreak(f.Pos().Line, 1, ignore, ml)
                        }
                        ml = false
                        p.setComment(f.Doc)
        if xline != yline && xline > 0 && yline > 0 {
                // at least one line break, but respect an extra empty line
                // in the source
-               if p.linebreak(yline, 1, 2, ws, true) {
+               if p.linebreak(yline, 1, ws, true) {
                        ws = ignore
                        *multiLine = true
                        printBlank = false // no blank after line break
 // ----------------------------------------------------------------------------
 // Statements
 
-const maxStmtNewlines = 2 // maximum number of newlines between statements
-
 // Print the statement list indented, but without a newline after the last statement.
 // Extra line breaks between statements in the source are respected but at most one
 // empty line is printed between statements.
        for i, s := range list {
                // _indent == 0 only for lists of switch/select case clauses;
                // in those cases each clause is a new section
-               p.linebreak(s.Pos().Line, 1, maxStmtNewlines, ignore, i == 0 || _indent == 0 || multiLine)
+               p.linebreak(s.Pos().Line, 1, ignore, i == 0 || _indent == 0 || multiLine)
                multiLine = false
                p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine)
        }
 func (p *printer) block(s *ast.BlockStmt, indent int) {
        p.print(s.Pos(), token.LBRACE)
        p.stmtList(s.List, indent, true)
-       p.linebreak(s.Rbrace.Line, 1, maxStmtNewlines, ignore, true)
+       p.linebreak(s.Rbrace.Line, 1, ignore, true)
        p.print(s.Rbrace, token.RBRACE)
 }
 
                                break
                        }
                } else {
-                       p.print(newline)
+                       p.linebreak(s.Stmt.Pos().Line, 1, ignore, true)
                }
                p.stmt(s.Stmt, nextIsRBrace, multiLine)
 
                        var ml bool
                        for i, s := range d.Specs {
                                if i > 0 {
-                                       p.linebreak(s.Pos().Line, 1, 2, ignore, ml)
+                                       p.linebreak(s.Pos().Line, 1, ignore, ml)
                                }
                                ml = false
                                p.spec(s, len(d.Specs), false, &ml)
                return
        }
 
+       p.nesting++
+       defer func() {
+               p.nesting--
+       }()
+
        if p.isOneLineFunc(b, headerSize) {
                sep := vtab
                if isLit {
 // ----------------------------------------------------------------------------
 // Files
 
-const maxDeclNewlines = 3 // maximum number of newlines between declarations
-
 func declToken(decl ast.Decl) (tok token.Token) {
        tok = token.ILLEGAL
        switch d := decl.(type) {
                        if prev != tok {
                                min = 2
                        }
-                       p.linebreak(d.Pos().Line, min, maxDeclNewlines, ignore, false)
+                       p.linebreak(d.Pos().Line, min, ignore, false)
                        p.decl(d, ignoreMultiLine)
                }
        }
 
 )
 
 
-const (
-       debug       = false // enable for debugging
-       maxNewlines = 3     // maximum vertical white space
-)
+const debug = false // enable for debugging
 
 
 type whiteSpace int
        esc       = []byte{tabwriter.Escape}
        htab      = []byte{'\t'}
        htabs     = []byte("\t\t\t\t\t\t\t\t")
-       newlines  = []byte("\n\n\n\n\n\n\n\n") // more than maxNewlines
-       formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than maxNewlines
+       newlines  = []byte("\n\n\n\n\n\n\n\n") // more than the max determined by nlines
+       formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines
 
        esc_quot = []byte(""") // shorter than """
        esc_apos = []byte("'") // shorter than "'"
        errors chan os.Error
 
        // Current state
+       nesting int  // nesting level (0: top-level (package scope), >0: functions/decls.)
        written int  // number of bytes written
        indent  int  // current indentation
        escape  bool // true if in escape sequence
 }
 
 
+// nlines returns the adjusted number of linebreaks given the desired number
+// of breaks n such that min <= result <= max where max depends on the current
+// nesting level.
+//
+func (p *printer) nlines(n, min int) int {
+       if n < min {
+               return min
+       }
+       max := 3 // max. number of newlines at the top level (p.nesting == 0)
+       if p.nesting > 0 {
+               max = 2 // max. number of newlines everywhere else
+       }
+       if n > max {
+               return max
+       }
+       return n
+}
+
+
 // write0 writes raw (uninterpreted) data to p.output and handles errors.
 // write0 does not indent after newlines, and does not HTML-escape or update p.pos.
 //
 
 func (p *printer) writeNewlines(n int, useFF bool) {
        if n > 0 {
-               if n > maxNewlines {
-                       n = maxNewlines
-               }
+               n = p.nlines(n, 0)
                if useFF {
                        p.write(formfeeds[0:n])
                } else {
        }
 
        if pos.IsValid() && pos.Filename != p.last.Filename {
-               // comment in a different file - separate with newlines
-               p.writeNewlines(maxNewlines, true)
+               // comment in a different file - separate with newlines (writeNewlines will limit the number)
+               p.writeNewlines(10, true)
                return
        }
 
        go func() {
                switch n := node.(type) {
                case ast.Expr:
+                       p.nesting = 1
                        p.useNodeComments = true
                        p.expr(n, ignoreMultiLine)
                case ast.Stmt:
+                       p.nesting = 1
                        p.useNodeComments = true
                        // A labeled statement will un-indent to position the
                        // label. Set indent to 1 so we don't get indent "underflow".
                        }
                        p.stmt(n, false, ignoreMultiLine)
                case ast.Decl:
+                       p.nesting = 1
                        p.useNodeComments = true
                        p.decl(n, ignoreMultiLine)
                case ast.Spec:
+                       p.nesting = 1
                        p.useNodeComments = true
                        p.spec(n, 1, false, ignoreMultiLine)
                case *ast.File:
+                       p.nesting = 0
                        p.comments = n.Comments
                        p.useNodeComments = n.Comments == nil
                        p.file(n)