This commit inlines https://github.com/tj/go-termd for a few reasons: - off the shelf it relies on a broken, erroneously released version of blackfriday (a markdown parser) - based on discussion with ampinsk, there are some tweaks we'd like to make to markdown rendering beyond what the library exposes now - it's a small library (around 300 sloc) This commit only: - messes with go.mod to fix the blackfriday issues - adds an inclusion note - renames the package
202 lines
6.7 KiB
Go
202 lines
6.7 KiB
Go
// This package is sourced from https://github.com/tj/go-termd under the terms of the MIT license.
|
|
// It was inlined to work around some dependency issues.
|
|
package markdown
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/alecthomas/chroma"
|
|
"github.com/aybabtme/rgbterm"
|
|
"github.com/tj/go-css/csshex"
|
|
)
|
|
|
|
// Style is the configuration used to style a particular token.
|
|
type Style struct {
|
|
Color string `json:"color"`
|
|
Background string `json:"background"`
|
|
Bold bool `json:"bold"`
|
|
Faint bool `json:"faint"`
|
|
Italic bool `json:"italic"`
|
|
Underline bool `json:"underline"`
|
|
}
|
|
|
|
// apply returns a string with the style applied.
|
|
func (s Style) apply(v string) string {
|
|
if s.Bold {
|
|
v = escape(1, v)
|
|
}
|
|
|
|
if s.Faint {
|
|
v = escape(2, v)
|
|
}
|
|
|
|
if s.Italic {
|
|
v = escape(3, v)
|
|
}
|
|
|
|
if s.Underline {
|
|
v = escape(4, v)
|
|
}
|
|
|
|
if s.Color != "" {
|
|
v = foreground(v, s.Color)
|
|
}
|
|
|
|
if s.Background != "" {
|
|
v = background(v, s.Background)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// SyntaxTheme is a map of token names to style configurations.
|
|
type SyntaxTheme map[string]Style
|
|
|
|
// Token implementation.
|
|
func (c SyntaxTheme) Token(t chroma.Token) string {
|
|
// specific
|
|
if s, ok := c.mapped(t.Type, t.Value); ok {
|
|
return s
|
|
}
|
|
|
|
// sub-category
|
|
if s, ok := c.mapped(t.Type.SubCategory(), t.Value); ok {
|
|
return s
|
|
}
|
|
|
|
// category
|
|
if s, ok := c.mapped(t.Type.Category(), t.Value); ok {
|
|
return s
|
|
}
|
|
|
|
return t.Value
|
|
}
|
|
|
|
// mapped returns a string mapped to its style, or returns the input string as-is.
|
|
func (c SyntaxTheme) mapped(t chroma.TokenType, v string) (string, bool) {
|
|
// check if the key is valid
|
|
k, ok := themeKeys[t]
|
|
if !ok {
|
|
return v, false
|
|
}
|
|
|
|
// check if the style is mapped
|
|
s, ok := c[k]
|
|
if !ok {
|
|
return v, false
|
|
}
|
|
|
|
return s.apply(v), true
|
|
}
|
|
|
|
// escape returns an ansi escape sequence with the given code.
|
|
func escape(code int, s string) string {
|
|
return fmt.Sprintf("\033[%dm%s\033[m", code, s)
|
|
}
|
|
|
|
// foreground color.
|
|
func foreground(s, color string) string {
|
|
r, g, b, ok := csshex.Parse(color)
|
|
if !ok {
|
|
return s
|
|
}
|
|
return rgbterm.FgString(s, r, g, b)
|
|
}
|
|
|
|
// background color.
|
|
func background(s, color string) string {
|
|
r, g, b, ok := csshex.Parse(color)
|
|
if !ok {
|
|
return s
|
|
}
|
|
return rgbterm.BgString(s, r, g, b)
|
|
}
|
|
|
|
// themeKeys is the map of token types to names.
|
|
var themeKeys = map[chroma.TokenType]string{
|
|
chroma.Keyword: "keyword",
|
|
chroma.KeywordConstant: "keyword.constant",
|
|
chroma.KeywordDeclaration: "keyword.declaration",
|
|
chroma.KeywordNamespace: "keyword.namespace",
|
|
chroma.KeywordPseudo: "keyword.pseudo",
|
|
chroma.KeywordReserved: "keyword.reserved",
|
|
chroma.KeywordType: "keyword.type",
|
|
chroma.Name: "name",
|
|
chroma.NameAttribute: "name.attribute",
|
|
chroma.NameBuiltin: "name.builtin",
|
|
chroma.NameBuiltinPseudo: "name.builtin.pseudo",
|
|
chroma.NameClass: "name.class",
|
|
chroma.NameConstant: "name.constant",
|
|
chroma.NameDecorator: "name.decorator",
|
|
chroma.NameEntity: "name.entity",
|
|
chroma.NameException: "name.exception",
|
|
chroma.NameFunction: "name.function",
|
|
chroma.NameFunctionMagic: "name.function.magic",
|
|
chroma.NameKeyword: "name.keyword",
|
|
chroma.NameLabel: "name.label",
|
|
chroma.NameNamespace: "name.namespace",
|
|
chroma.NameOperator: "name.operator",
|
|
chroma.NameOther: "name.other",
|
|
chroma.NamePseudo: "name.pseudo",
|
|
chroma.NameProperty: "name.property",
|
|
chroma.NameTag: "name.tag",
|
|
chroma.NameVariable: "name.variable",
|
|
chroma.NameVariableAnonymous: "name.variable.anonymous",
|
|
chroma.NameVariableClass: "name.variable.class",
|
|
chroma.NameVariableGlobal: "name.variable.global",
|
|
chroma.NameVariableInstance: "name.variable.instance",
|
|
chroma.NameVariableMagic: "name.variable.magic",
|
|
chroma.Literal: "literal",
|
|
chroma.LiteralDate: "literal.date",
|
|
chroma.LiteralOther: "literal.other",
|
|
chroma.LiteralString: "literal.string",
|
|
chroma.LiteralStringAffix: "literal.string.affix",
|
|
chroma.LiteralStringAtom: "literal.string.atom",
|
|
chroma.LiteralStringBacktick: "literal.string.backtick",
|
|
chroma.LiteralStringBoolean: "literal.string.boolean",
|
|
chroma.LiteralStringChar: "literal.string.char",
|
|
chroma.LiteralStringDelimiter: "literal.string.delimiter",
|
|
chroma.LiteralStringDoc: "literal.string.doc",
|
|
chroma.LiteralStringDouble: "literal.string.double",
|
|
chroma.LiteralStringEscape: "literal.string.escape",
|
|
chroma.LiteralStringHeredoc: "literal.string.heredoc",
|
|
chroma.LiteralStringInterpol: "literal.string.interpol",
|
|
chroma.LiteralStringName: "literal.string.name",
|
|
chroma.LiteralStringOther: "literal.string.other",
|
|
chroma.LiteralStringRegex: "literal.string.regex",
|
|
chroma.LiteralStringSingle: "literal.string.single",
|
|
chroma.LiteralStringSymbol: "literal.string.symbol",
|
|
chroma.LiteralNumber: "literal.number",
|
|
chroma.LiteralNumberBin: "literal.number.bin",
|
|
chroma.LiteralNumberFloat: "literal.number.float",
|
|
chroma.LiteralNumberHex: "literal.number.hex",
|
|
chroma.LiteralNumberInteger: "literal.number.integer",
|
|
chroma.LiteralNumberIntegerLong: "literal.number.integer.long",
|
|
chroma.LiteralNumberOct: "literal.number.oct",
|
|
chroma.Operator: "operator",
|
|
chroma.OperatorWord: "operator.word",
|
|
chroma.Punctuation: "punctuation",
|
|
chroma.Comment: "comment",
|
|
chroma.CommentHashbang: "comment.hashbang",
|
|
chroma.CommentMultiline: "comment.multiline",
|
|
chroma.CommentSingle: "comment.single",
|
|
chroma.CommentSpecial: "comment.special",
|
|
chroma.CommentPreproc: "comment.preproc",
|
|
chroma.CommentPreprocFile: "comment.preproc.file",
|
|
chroma.Generic: "generic",
|
|
chroma.GenericDeleted: "generic.deleted",
|
|
chroma.GenericEmph: "generic.emph",
|
|
chroma.GenericError: "generic.error",
|
|
chroma.GenericHeading: "generic.heading",
|
|
chroma.GenericInserted: "generic.inserted",
|
|
chroma.GenericOutput: "generic.output",
|
|
chroma.GenericPrompt: "generic.prompt",
|
|
chroma.GenericStrong: "generic.strong",
|
|
chroma.GenericSubheading: "generic.subheading",
|
|
chroma.GenericTraceback: "generic.traceback",
|
|
chroma.GenericUnderline: "generic.underline",
|
|
chroma.Text: "text",
|
|
chroma.TextWhitespace: "text.whitespace",
|
|
chroma.TextSymbol: "text.symbol",
|
|
chroma.TextPunctuation: "text.punctuation",
|
|
}
|