build(markdown): add no_syntax_highlighting tag to build without chroma (#849)
This commit is contained in:
parent
cfb31cf2f3
commit
1bcd7650c8
5 changed files with 115 additions and 86 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Test
|
||||
run: |
|
||||
go test ${{ matrix.os != 'windows-11-arm' && '-race' || '' }} -v ./...
|
||||
go test ${{ matrix.os != 'windows-11-arm' && '-race' || '' }} -v -tags no_spoof_tls_fingerprint,no_voice ./...
|
||||
go test ${{ matrix.os != 'windows-11-arm' && '-race' || '' }} -v -tags no_spoof_tls_fingerprint,no_voice,no_syntax_highlighting ./...
|
||||
|
||||
- name: Build
|
||||
run: go build -trimpath -ldflags=-s .
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ go build .
|
|||
|
||||
- `no_voice` to build without voice support.
|
||||
- `no_spoof_tls_fingerprint` to build without TLS fingerprint spoofing.
|
||||
- `no_syntax_highlighting` to build without syntax highlighting in code blocks.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
|||
85
internal/markdown/highlight.go
Normal file
85
internal/markdown/highlight.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//go:build !no_syntax_highlighting
|
||||
|
||||
package markdown
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
func (r *Renderer) writeCodeBlock(builder *Builder, code string, language string, base tview.Style) {
|
||||
lexer := lexers.Get(language)
|
||||
declaredLanguageSupported := lexer != nil
|
||||
|
||||
// Detect the language from its content.
|
||||
var analyzed bool
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(code)
|
||||
analyzed = lexer != nil
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
// Some lexers emit many tiny tokens; coalesce runs of the same type.
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
|
||||
var header string
|
||||
switch {
|
||||
case analyzed:
|
||||
header = "code: analyzed"
|
||||
case language == "":
|
||||
header = "code"
|
||||
case !declaredLanguageSupported:
|
||||
header = "code: " + language
|
||||
}
|
||||
if header != "" {
|
||||
builder.Write(codeBlockIndent+header, base.Dim(true))
|
||||
builder.NewLine()
|
||||
}
|
||||
|
||||
iterator, err := lexer.Tokenise(nil, code)
|
||||
if err != nil {
|
||||
builder.Write(codeBlockIndent, base)
|
||||
writeCode(builder, code, base, base)
|
||||
return
|
||||
}
|
||||
|
||||
theme := styles.Get(r.cfg.Markdown.Theme)
|
||||
if theme == nil {
|
||||
theme = styles.Fallback
|
||||
}
|
||||
|
||||
builder.Write(codeBlockIndent, base)
|
||||
for token := iterator(); token != chroma.EOF; token = iterator() {
|
||||
style := applyChromaStyle(base, theme.Get(token.Type))
|
||||
writeCode(builder, token.Value, style, base)
|
||||
}
|
||||
}
|
||||
|
||||
func applyChromaStyle(base tview.Style, entry chroma.StyleEntry) tview.Style {
|
||||
style := base
|
||||
if entry.Colour.IsSet() {
|
||||
style = style.Foreground(tcell.NewRGBColor(
|
||||
int32(entry.Colour.Red()),
|
||||
int32(entry.Colour.Green()),
|
||||
int32(entry.Colour.Blue()),
|
||||
))
|
||||
}
|
||||
// Token background colors are not applied, so code blocks keep the user's terminal/chat background.
|
||||
|
||||
// An attribute chroma passes on is left as it is.
|
||||
if entry.Bold != chroma.Pass {
|
||||
style = style.Bold(entry.Bold == chroma.Yes)
|
||||
}
|
||||
if entry.Italic != chroma.Pass {
|
||||
style = style.Italic(entry.Italic == chroma.Yes)
|
||||
}
|
||||
if entry.Underline != chroma.Pass {
|
||||
style = style.Underline(entry.Underline == chroma.Yes)
|
||||
}
|
||||
return style
|
||||
}
|
||||
17
internal/markdown/highlight_none.go
Normal file
17
internal/markdown/highlight_none.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//go:build no_syntax_highlighting
|
||||
|
||||
package markdown
|
||||
|
||||
import "github.com/ayn2op/tview"
|
||||
|
||||
func (r *Renderer) writeCodeBlock(builder *Builder, code string, language string, base tview.Style) {
|
||||
header := "code"
|
||||
if language != "" {
|
||||
header += ": " + language
|
||||
}
|
||||
builder.Write(codeBlockIndent+header, base.Dim(true))
|
||||
builder.NewLine()
|
||||
|
||||
builder.Write(codeBlockIndent, base)
|
||||
writeCode(builder, code, base, base)
|
||||
}
|
||||
|
|
@ -4,14 +4,10 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
md "github.com/ayn2op/arikawa/v3/markdown"
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/text"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/yuin/goldmark/v2/ast"
|
||||
)
|
||||
|
||||
|
|
@ -124,69 +120,23 @@ func (r *Renderer) RenderText(source []byte, node ast.Node, base tview.Style, em
|
|||
|
||||
func (r *Renderer) renderCodeBlock(builder *Builder, source []byte, node *ast.CodeBlock, base tview.Style) {
|
||||
var code strings.Builder
|
||||
lines := node.Value.Segments()
|
||||
for _, line := range lines {
|
||||
for _, line := range node.Value.Segments() {
|
||||
code.Write(line.Bytes(source))
|
||||
}
|
||||
|
||||
language, _ := node.Language(source)
|
||||
language = strings.TrimSpace(language)
|
||||
lexer := lexers.Get(language)
|
||||
declaredLanguageSupported := lexer != nil
|
||||
r.writeCodeBlock(builder, code.String(), strings.TrimSpace(language), base)
|
||||
}
|
||||
|
||||
// Detect the language from its content.
|
||||
var analyzed bool
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(code.String())
|
||||
analyzed = lexer != nil
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
|
||||
// Some lexers emit many tiny tokens; coalesce runs of the same type.
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
|
||||
var header string
|
||||
switch {
|
||||
case analyzed:
|
||||
header = "code: analyzed"
|
||||
case language == "":
|
||||
header = "code"
|
||||
case !declaredLanguageSupported:
|
||||
header = "code: " + language
|
||||
}
|
||||
if header != "" {
|
||||
builder.Write(codeBlockIndent+header, base.Dim(true))
|
||||
builder.NewLine()
|
||||
}
|
||||
|
||||
iterator, err := lexer.Tokenise(nil, code.String())
|
||||
if err != nil {
|
||||
for _, line := range lines {
|
||||
builder.Write(codeBlockIndent+string(line.Bytes(source)), base)
|
||||
// writeCode writes s in style, starting each line after the first with the code block indent in base.
|
||||
func writeCode(builder *Builder, s string, style, base tview.Style) {
|
||||
for i, part := range strings.Split(s, "\n") {
|
||||
if i > 0 {
|
||||
builder.NewLine()
|
||||
builder.Write(codeBlockIndent, base)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
theme := styles.Get(r.cfg.Markdown.Theme)
|
||||
if theme == nil {
|
||||
theme = styles.Fallback
|
||||
}
|
||||
|
||||
builder.Write(codeBlockIndent, base)
|
||||
for token := iterator(); token != chroma.EOF; token = iterator() {
|
||||
style := applyChromaStyle(base, theme.Get(token.Type))
|
||||
// Chroma tokens may include embedded newlines, so split and re-emit with indentation on each visual line.
|
||||
parts := strings.Split(token.Value, "\n")
|
||||
for i, part := range parts {
|
||||
if i > 0 {
|
||||
builder.NewLine()
|
||||
builder.Write(codeBlockIndent, base)
|
||||
}
|
||||
if part != "" {
|
||||
builder.Write(part, style)
|
||||
}
|
||||
if part != "" {
|
||||
builder.Write(part, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,30 +160,6 @@ func listMarker(item *ast.ListItem) string {
|
|||
return strconv.Itoa(number) + ". "
|
||||
}
|
||||
|
||||
func applyChromaStyle(base tview.Style, entry chroma.StyleEntry) tview.Style {
|
||||
style := base
|
||||
if entry.Colour.IsSet() {
|
||||
style = style.Foreground(tcell.NewRGBColor(
|
||||
int32(entry.Colour.Red()),
|
||||
int32(entry.Colour.Green()),
|
||||
int32(entry.Colour.Blue()),
|
||||
))
|
||||
}
|
||||
// Token background colors are not applied, so code blocks keep the user's terminal/chat background.
|
||||
|
||||
// An attribute chroma passes on is left as it is.
|
||||
if entry.Bold != chroma.Pass {
|
||||
style = style.Bold(entry.Bold == chroma.Yes)
|
||||
}
|
||||
if entry.Italic != chroma.Pass {
|
||||
style = style.Italic(entry.Italic == chroma.Yes)
|
||||
}
|
||||
if entry.Underline != chroma.Pass {
|
||||
style = style.Underline(entry.Underline == chroma.Yes)
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
func mentionText(node *md.Mention) string {
|
||||
switch {
|
||||
case node.Channel != nil:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue