case line == "":
// emit nothing
+ case isIndented(line):
+ b, lines = d.code(lines)
+
case (len(lines) == 1 || lines[1] == "") && !didHeading && isOldHeading(line, all, len(all)-n):
b = d.oldHeading(line)
didHeading = true
return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}}
}
+// code returns a code block built from the indented text
+// at the start of lines, along with the remainder of the lines.
+// If there is no indented text at the start, or if the indented
+// text consists only of empty lines, code returns a nil Block.
+func (d *parseDoc) code(lines []string) (b Block, rest []string) {
+ lines, rest = indented(lines)
+ body := unindent(lines)
+ if len(body) == 0 {
+ return nil, rest
+ }
+ body = append(body, "") // to get final \n from Join
+ return &Code{Text: strings.Join(body, "\n")}, rest
+}
+
+// isIndented reports whether the line is indented,
+// meaning it starts with a space or tab.
+func isIndented(line string) bool {
+ return line != "" && (line[0] == ' ' || line[0] == '\t')
+}
+
+// indented splits lines into an initial indented section
+// and the remaining lines, returning the two halves.
+func indented(lines []string) (indented, rest []string) {
+ // Blank lines mid-run are OK, but not at the end.
+ i := 0
+ for i < len(lines) && (isIndented(lines[i]) || lines[i] == "") {
+ i++
+ }
+ for i > 0 && lines[i-1] == "" {
+ i--
+ }
+ return lines[:i], lines[i:]
+}
+
// paragraph returns a paragraph block built from the
// unindented text at the start of lines, along with the remainder of the lines.
// If there is no unindented text at the start of lines,
// then paragraph returns a nil Block.
func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) {
- // TODO: Paragraph should be interrupted by any indented line,
+ // Paragraph is interrupted by any indented line,
// which is either a list or a code block,
// and of course by a blank line.
- // It should not be interrupted by a # line - headings must stand alone.
+ // It is not interrupted by a # line - headings must stand alone.
i := 0
- for i < len(lines) && lines[i] != "" {
+ for i < len(lines) && lines[i] != "" && !isIndented(lines[i]) {
i++
}
lines, rest = lines[:i], lines[i:]
// A textPrinter holds the state needed for printing a Doc as plain text.
type textPrinter struct {
*Printer
- long strings.Builder
- prefix string
- width int
+ long strings.Builder
+ prefix string
+ codePrefix string
+ width int
}
// Text returns a textual formatting of the Doc.
// See the [Printer] documentation for ways to customize the text output.
func (p *Printer) Text(d *Doc) []byte {
tp := &textPrinter{
- Printer: p,
- prefix: p.TextPrefix,
- width: p.TextWidth,
+ Printer: p,
+ prefix: p.TextPrefix,
+ codePrefix: p.TextCodePrefix,
+ width: p.TextWidth,
+ }
+ if tp.codePrefix == "" {
+ tp.codePrefix = p.TextPrefix + "\t"
}
if tp.width == 0 {
tp.width = 80 - utf8.RuneCountInString(tp.prefix)
var out bytes.Buffer
for i, x := range d.Content {
if i > 0 && blankBefore(x) {
+ out.WriteString(tp.prefix)
writeNL(&out)
}
tp.block(&out, x)
out.WriteString(p.prefix)
out.WriteString("# ")
p.text(out, x.Text)
+
+ case *Code:
+ text := x.Text
+ for text != "" {
+ var line string
+ line, text, _ = strings.Cut(text, "\n")
+ if line != "" {
+ out.WriteString(p.codePrefix)
+ out.WriteString(line)
+ }
+ writeNL(out)
+ }
}
}