liveBook · Manning

IndentDoc

As explained in Fabulous Adventures in Data Structures and Algorithms

IndentDoc is the book's Doc variant that associates an indentation amount with a child document's line breaks.

IndentDoc is a document node that wraps a child document and causes every line break produced inside that child to be followed by indentation. The implementation fixes indentation at four spaces. This makes layout preferences composable: place an IndentDoc around a parenthesized expression, continuation, or nested group rather than inserting spaces manually. section 9.3.1

IndentDoc does not itself decide whether an optional break becomes a line break. GroupDoc and the pretty-printing process make that decision; IndentDoc carries the indentation level that applies when a break occurs. The recursive structure is evaluated iteratively by passing indentation through an explicit work stack. section 9.3.3 section 9.3.4

Indenting a formatted call

Wrapping the contents of a call in IndentDoc keeps the argument on the same line when it fits, but moves it four spaces right when the group breaks: Foo.Bar(\n xx + yy * zz). A narrower width can produce nested indentation: Foo.Bar(\n xx +\n yy * zz). section 9.3.1 section 9.3.4

Listing 9.4 A nonrecursive Pretty method
using System.Text;
abstract record class Doc
{
    // […]
    public string Pretty(int width)
    {
        var sb = new StringBuilder();
        int used = 0; #1
        var docs = new Stack<(bool, int, Doc)>(); #2
        docs.Push((true, 0, new GroupDoc(this))); #3
        while (docs.Count > 0) #4
        {
            (bool fits, int indent, Doc doc) = docs.Pop(); #5
            switch (doc)
            {
                case TextDoc td:
                    sb.Append(td.text); #6
                    used += td.text.Length;
                    break;
                case GroupDoc gd: #7
                    docs.Push((gd.doc.Fits(width - used), indent, gd.doc)); 
                    break;
                case IndentDoc id: #8
                    docs.Push((fits, indent + IndentSpaces, id.doc));
                    break;
                case OptBreakDoc bd:
                    if (fits) #9
                    {
                        sb.Append(bd.text);
                        used += bd.text.Length;
                    }
                    else #10
                    {
                        sb.AppendLine();
                        sb.Append(' ', indent);
                        used = indent;
                    }
                    break;
                case ConcatDoc cd: #11
                    docs.Push((fits, indent, cd.right));
                    docs.Push((fits, indent, cd.left));
                    break;

            }
        }
        return sb.ToString();
    } }

Inspect the nonrecursive Pretty implementation, where IndentDoc increases the carried indentation value and optional breaks emit that indentation after line breaks. section 9.3.4

How Fabulous Adventures in Data Structures and Algorithms covers it

How deep it goes
Defines the node, shows four-space behavior, and traces iterative indentation state, but defers general indentation configuration.
Examples
Demonstrates IndentDoc with Foo.Bar(xx + yy * zz) at widths 30, 20, and 15.
What it assumes
Assumes familiarity with document trees, optional breaks, grouping, concatenation, and stack-based traversal.
What you can do after
Builds composable formatted documents and predicts indentation across optional line breaks.
How it connects
Connects IndentDoc to Doc, GroupDoc, OptBreakDoc, Fits, Pretty, explicit stacks, and StringBuilder.

This explainer was generated by Manning's knowledge graph from the text of Fabulous Adventures in Data Structures and Algorithms. The section links point to the parts of the book it draws on.