package renderkit
import (
stdhtml "html"
"path"
"regexp"
"strings"
)
// Snippet describes one fenced code block in an assistant reply that is
// worth offering as a download.
type Snippet struct {
Index int // position among ALL fenced blocks in the reply (matches the DOM order of .code-block)
Lang string // lower-cased fence language
Ext string // file extension including the dot, e.g. ".go"
Label string // display name of the type, e.g. "Go"
Lines int
Bytes int
// Filename is set when the block's first line is a comment naming a
// file with the matching extension (e.g. "// internal/db/users.go"),
// which models often write; only the base name is kept.
Filename string
}
var firstLineFilename = regexp.MustCompile(`^\s*(?://|#|--|/\*|)?\s*$`)
func filenameHint(body, ext string) string {
first, _, _ := strings.Cut(body, "\n")
m := firstLineFilename.FindStringSubmatch(first)
if m == nil {
return ""
}
base := path.Base(m[1])
if path.Ext(base) != ext || base == ext {
return ""
}
return base
}
type snippetType struct{ ext, label string }
var snippetTypes = map[string]snippetType{
"go": {".go", "Go"}, "golang": {".go", "Go"},
"js": {".js", "JavaScript"}, "javascript": {".js", "JavaScript"},
"jsx": {".jsx", "JSX"}, "ts": {".ts", "TypeScript"}, "typescript": {".ts", "TypeScript"}, "tsx": {".tsx", "TSX"},
"py": {".py", "Python"}, "python": {".py", "Python"},
"sh": {".sh", "Shell"}, "bash": {".sh", "Shell"}, "shell": {".sh", "Shell"}, "zsh": {".sh", "Shell"},
"json": {".json", "JSON"}, "sql": {".sql", "SQL"},
"c": {".c", "C"}, "h": {".h", "C header"}, "cpp": {".cpp", "C++"}, "c++": {".cpp", "C++"},
"java": {".java", "Java"}, "rust": {".rs", "Rust"}, "rs": {".rs", "Rust"},
"diff": {".patch", "Patch"}, "patch": {".patch", "Patch"},
"yaml": {".yml", "YAML"}, "yml": {".yml", "YAML"}, "toml": {".toml", "TOML"},
"html": {".html", "HTML"}, "css": {".css", "CSS"},
"md": {".md", "Markdown"}, "markdown": {".md", "Markdown"},
}
// Snippets lists the fenced code blocks in src that have a recognised
// language, in the order Markdown renders them. Blocks with no language (or
// an unknown one) are skipped, but still counted in Index.
func Snippets(src string) []Snippet {
var out []Snippet
for i, m := range fencedCodeBlock.FindAllStringSubmatch(rawMarkdown(src), -1) {
lang := strings.ToLower(m[1])
t, ok := snippetTypes[lang]
if !ok {
continue
}
body := stdhtml.UnescapeString(m[2])
out = append(out, Snippet{
Index: i, Lang: lang, Ext: t.ext, Label: t.label,
Lines: strings.Count(strings.TrimRight(body, "\n"), "\n") + 1,
Bytes: len(body), Filename: filenameHint(body, t.ext),
})
}
return out
}