Skip to content
quarry Artifact [6800d634fe]
Login

Artifact [6800d634fe]

Artifact 6800d634fe3ecc275a4bf6b6b9382896db22da9854a7c751c0f9b9767a7dee4c:


package markup

import (
	"strconv"

	gast "github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/util"
)

// slugIDs hands out heading anchors like goldmark's own generator — the
// same slug, then -1, -2 … for a repeat — but remembers where each
// repeat count stopped. Goldmark rescans from -1 on every repeat, so a
// document with n headings under the same name costs n² lookups: a
// megabyte of small "Notes" sections took minutes to render.
type slugIDs struct {
	used map[string]bool
	next map[string]int // slug -> the suffix to try next
}

func newSlugIDs() parser.IDs {
	return &slugIDs{used: map[string]bool{}, next: map[string]int{}}
}

func (s *slugIDs) Put(value []byte) { s.used[string(value)] = true }

func (s *slugIDs) Generate(value []byte, kind gast.NodeKind) []byte {
	value = util.TrimLeftSpace(value)
	value = util.TrimRightSpace(value)
	var slug []byte
	for i := 0; i < len(value); {
		v := value[i]
		l := util.UTF8Len(v)
		i += int(l)
		if l != 1 {
			continue
		}
		if util.IsAlphaNumeric(v) {
			if 'A' <= v && v <= 'Z' {
				v += 'a' - 'A'
			}
			slug = append(slug, v)
		} else if util.IsSpace(v) || v == '-' || v == '_' {
			slug = append(slug, '-')
		}
	}
	if len(slug) == 0 {
		if kind == gast.KindHeading {
			slug = []byte("heading")
		} else {
			slug = []byte("id")
		}
	}
	base := string(slug)
	if !s.used[base] {
		s.used[base] = true
		return slug
	}
	for i := max(s.next[base], 1); ; i++ {
		id := base + "-" + strconv.Itoa(i)
		if !s.used[id] {
			s.used[id] = true
			s.next[base] = i + 1
			return []byte(id)
		}
	}
}