package main import ( "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/lexers" ) type editorMarkdownStyle uint8 const ( editorMarkdownPlain editorMarkdownStyle = iota editorMarkdownHeading editorMarkdownStrong editorMarkdownEmphasis editorMarkdownCode editorMarkdownLink editorMarkdownDestination editorMarkdownQuote editorMarkdownComment ) func editorMarkdownStyles(value string) []editorMarkdownStyle { runes := []rune(value) styles := make([]editorMarkdownStyle, len(runes)) lexer := lexers.Get("markdown") if lexer == nil { highlightEditorHTMLComments(runes, styles) return styles } tokens, err := chroma.Tokenise(lexer, nil, value) if err != nil { highlightEditorHTMLComments(runes, styles) return styles } offset := 0 for _, token := range tokens { style := editorMarkdownStyleForToken(token.Type) for range []rune(token.Value) { if offset >= len(styles) { return styles } styles[offset] = style offset++ } } highlightEditorHTMLComments(runes, styles) return styles } func highlightEditorHTMLComments(runes []rune, styles []editorMarkdownStyle) { startMarker, endMarker := []rune("") for offset := 0; offset < len(runes); { start := findEditorRunes(runes, startMarker, offset) if start < 0 { return } end := findEditorRunes(runes, endMarker, start+len(startMarker)) if end < 0 { end = len(runes) } else { end += len(endMarker) } for index := start; index < end; index++ { styles[index] = editorMarkdownComment } offset = end } } func findEditorRunes(value, target []rune, offset int) int { for index := max(0, offset); index+len(target) <= len(value); index++ { matches := true for targetIndex := range target { if value[index+targetIndex] != target[targetIndex] { matches = false break } } if matches { return index } } return -1 } func editorMarkdownStyleForToken(token chroma.TokenType) editorMarkdownStyle { switch { case token == chroma.GenericHeading || token == chroma.GenericSubheading: return editorMarkdownHeading case token == chroma.GenericStrong: return editorMarkdownStrong case token == chroma.GenericEmph: return editorMarkdownEmphasis case token == chroma.LiteralStringBacktick: return editorMarkdownCode case token == chroma.NameTag: return editorMarkdownLink case token == chroma.NameAttribute: return editorMarkdownDestination case token == chroma.Keyword: return editorMarkdownQuote case token.InSubCategory(chroma.Comment): return editorMarkdownComment default: return editorMarkdownPlain } } func editorMarkdownStyleStart(style editorMarkdownStyle) string { if style == editorMarkdownPlain { return "" } if currentThemeName == "no-color" { switch style { case editorMarkdownHeading, editorMarkdownStrong: return "\x1b[1m" case editorMarkdownEmphasis: return "\x1b[3m" case editorMarkdownLink: return "\x1b[4m" case editorMarkdownComment: return "\x1b[2m" default: return "" } } authors := editorMarkdownTheme.AuthorPalette author := func(index int) string { if len(authors) == 0 { return editorMarkdownTheme.Text } return authors[index%len(authors)] } switch style { case editorMarkdownHeading: return "\x1b[1m" + foregroundSequence(editorMarkdownTheme.Title) case editorMarkdownStrong: return "\x1b[1m" + foregroundSequence(author(1)) case editorMarkdownEmphasis: return "\x1b[3m" + foregroundSequence(editorMarkdownTheme.Text) case editorMarkdownCode: return foregroundSequence(editorMarkdownTheme.Success) case editorMarkdownLink: return "\x1b[4m" + foregroundSequence(author(0)) case editorMarkdownDestination: return foregroundSequence(author(2)) case editorMarkdownQuote: return foregroundSequence(editorMarkdownTheme.Warning) case editorMarkdownComment: return "\x1b[2m" + foregroundSequence(editorMarkdownTheme.Dim) default: return "" } } func editorMarkdownStyleEnd(active bool) string { foreground := "\x1b[39m" if active && colorEnabled { foreground = foregroundSequence(editorMarkdownTheme.EditorForeground) } return "\x1b[22;23;24m" + foreground }