blob: 47ae9c18b801904bb1b899e4f334ab023d952a1c [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"bytes"
"fmt"
"strings"
)
type CodeBlock struct {
Position
Fence string
Info string
Text []string
}
func (b *CodeBlock) PrintHTML(buf *bytes.Buffer) {
if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' {
buf.WriteString("\n")
}
buf.WriteString("<pre><code")
if b.Info != "" {
// https://spec.commonmark.org/0.30/#info-string
// “The first word of the info string is typically used to
// specify the language of the code sample...”
// No definition of what “first word” means though.
// The Dingus splits on isUnicodeSpace, but Goldmark only uses space.
lang := b.Info
for i, c := range lang {
if isUnicodeSpace(c) {
lang = lang[:i]
break
}
}
fmt.Fprintf(buf, " class=\"language-%s\"", htmlQuoteEscaper.Replace(lang))
}
buf.WriteString(">")
if b.Fence == "" { // TODO move
for len(b.Text) > 0 && trimSpaceTab(b.Text[len(b.Text)-1]) == "" {
b.Text = b.Text[:len(b.Text)-1]
}
}
for _, s := range b.Text {
buf.WriteString(htmlEscaper.Replace(s))
buf.WriteString("\n")
}
buf.WriteString("</code></pre>\n")
}
// func initialSpaces(s string) int {
// for i := 0; i < len(s); i++ {
// if s[i] != ' ' {
// return i
// }
// }
// return len(s)
// }
func (b *CodeBlock) printMarkdown(buf *bytes.Buffer, s mdState) {
prefix1 := s.prefix1
if prefix1 == "" {
prefix1 = s.prefix
}
if b.Fence == "" {
for i, line := range b.Text {
// Ignore final empty line (why is it even there?).
if i == len(b.Text)-1 && len(line) == 0 {
break
}
// var iline string
// is := initialSpaces(line)
// if is < 4 {
// iline = " " + line
// } else {
// iline = "\t" + line[4:]
// }
// Indent by 4 spaces.
pre := s.prefix
if i == 0 {
pre = prefix1
}
fmt.Fprintf(buf, "%s%s%s\n", pre, " ", line)
}
} else {
fmt.Fprintf(buf, "%s%s\n", prefix1, b.Fence)
for _, line := range b.Text {
fmt.Fprintf(buf, "%s%s\n", s.prefix, line)
}
fmt.Fprintf(buf, "%s%s\n", s.prefix, b.Fence)
}
}
func newPre(p *parseState, s line) (line, bool) {
peek2 := s
if p.para() == nil && peek2.trimSpace(4, 4, false) && !peek2.isBlank() {
b := &preBuilder{ /*indent: strings.TrimSuffix(s.string(), peek2.string())*/ }
p.addBlock(b)
p.corner = p.corner || peek2.nl != '\n' // goldmark does not normalize to \n
b.text = append(b.text, peek2.string())
return line{}, true
}
return s, false
}
func newFence(p *parseState, s line) (line, bool) {
var fence, info string
var n int
peek := s
if peek.trimFence(&fence, &info, &n) {
if fence[0] == '~' && info != "" {
// goldmark does not handle info after ~~~
p.corner = true
} else if info != "" && !isLetter(info[0]) {
// goldmark does not allow numbered info.
// goldmark does not treat a tab as introducing a new word.
p.corner = true
}
for _, c := range info {
if isUnicodeSpace(c) {
if c != ' ' {
// goldmark only breaks on space
p.corner = true
}
break
}
}
p.addBlock(&fenceBuilder{fence, info, n, nil})
return line{}, true
}
return s, false
}
func (s *line) trimFence(fence, info *string, n *int) bool {
t := *s
*n = 0
for *n < 3 && t.trimSpace(1, 1, false) {
*n++
}
switch c := t.peek(); c {
case '`', '~':
f := t.string()
n := 0
for i := 0; ; i++ {
if !t.trim(c) {
if i >= 3 {
break
}
return false
}
n++
}
txt := mdUnescaper.Replace(t.trimString())
if c == '`' && strings.Contains(txt, "`") {
return false
}
txt = trimSpaceTab(txt)
*info = txt
*fence = f[:n]
*s = line{}
return true
}
return false
}
// For indented code blocks.
type preBuilder struct {
indent string
text []string
}
func (c *preBuilder) extend(p *parseState, s line) (line, bool) {
if !s.trimSpace(4, 4, true) {
return s, false
}
c.text = append(c.text, s.string())
p.corner = p.corner || s.nl != '\n' // goldmark does not normalize to \n
return line{}, true
}
func (b *preBuilder) build(p buildState) Block {
return &CodeBlock{p.pos(), "", "", b.text}
}
type fenceBuilder struct {
fence string
info string
n int
text []string
}
func (c *fenceBuilder) extend(p *parseState, s line) (line, bool) {
var fence, info string
var n int
if t := s; t.trimFence(&fence, &info, &n) && strings.HasPrefix(fence, c.fence) && info == "" {
return line{}, false
}
if !s.trimSpace(c.n, c.n, false) {
p.corner = true // goldmark mishandles fenced blank lines with not enough spaces
s.trimSpace(0, c.n, false)
}
c.text = append(c.text, s.string())
p.corner = p.corner || s.nl != '\n' // goldmark does not normalize to \n
return line{}, true
}
func (c *fenceBuilder) build(p buildState) Block {
return &CodeBlock{
p.pos(),
c.fence,
c.info,
c.text,
}
}