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
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
go test ${{ matrix.os != 'windows-11-arm' && '-race' || '' }} -v ./...
|
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
|
- name: Build
|
||||||
run: go build -trimpath -ldflags=-s .
|
run: go build -trimpath -ldflags=-s .
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ go build .
|
||||||
|
|
||||||
- `no_voice` to build without voice support.
|
- `no_voice` to build without voice support.
|
||||||
- `no_spoof_tls_fingerprint` to build without TLS fingerprint spoofing.
|
- `no_spoof_tls_fingerprint` to build without TLS fingerprint spoofing.
|
||||||
|
- `no_syntax_highlighting` to build without syntax highlighting in code blocks.
|
||||||
|
|
||||||
## Usage
|
## 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"
|
"strconv"
|
||||||
"strings"
|
"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"
|
md "github.com/ayn2op/arikawa/v3/markdown"
|
||||||
"github.com/ayn2op/discordo/internal/config"
|
"github.com/ayn2op/discordo/internal/config"
|
||||||
"github.com/ayn2op/tview"
|
"github.com/ayn2op/tview"
|
||||||
"github.com/ayn2op/tview/text"
|
"github.com/ayn2op/tview/text"
|
||||||
"github.com/gdamore/tcell/v3"
|
|
||||||
"github.com/yuin/goldmark/v2/ast"
|
"github.com/yuin/goldmark/v2/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -124,62 +120,17 @@ 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) {
|
func (r *Renderer) renderCodeBlock(builder *Builder, source []byte, node *ast.CodeBlock, base tview.Style) {
|
||||||
var code strings.Builder
|
var code strings.Builder
|
||||||
lines := node.Value.Segments()
|
for _, line := range node.Value.Segments() {
|
||||||
for _, line := range lines {
|
|
||||||
code.Write(line.Bytes(source))
|
code.Write(line.Bytes(source))
|
||||||
}
|
}
|
||||||
|
|
||||||
language, _ := node.Language(source)
|
language, _ := node.Language(source)
|
||||||
language = strings.TrimSpace(language)
|
r.writeCodeBlock(builder, code.String(), strings.TrimSpace(language), base)
|
||||||
lexer := lexers.Get(language)
|
|
||||||
declaredLanguageSupported := lexer != nil
|
|
||||||
|
|
||||||
// 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.
|
// writeCode writes s in style, starting each line after the first with the code block indent in base.
|
||||||
lexer = chroma.Coalesce(lexer)
|
func writeCode(builder *Builder, s string, style, base tview.Style) {
|
||||||
|
for i, part := range strings.Split(s, "\n") {
|
||||||
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)
|
|
||||||
}
|
|
||||||
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 {
|
if i > 0 {
|
||||||
builder.NewLine()
|
builder.NewLine()
|
||||||
builder.Write(codeBlockIndent, base)
|
builder.Write(codeBlockIndent, base)
|
||||||
|
|
@ -189,7 +140,6 @@ func (r *Renderer) renderCodeBlock(builder *Builder, source []byte, node *ast.Co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// shouldMask reports whether node is a spoiler shown as "[spoiler]" in place of what it hides.
|
// shouldMask reports whether node is a spoiler shown as "[spoiler]" in place of what it hides.
|
||||||
func (r *Renderer) shouldMask(node *md.Inline) bool {
|
func (r *Renderer) shouldMask(node *md.Inline) bool {
|
||||||
|
|
@ -210,30 +160,6 @@ func listMarker(item *ast.ListItem) string {
|
||||||
return strconv.Itoa(number) + ". "
|
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 {
|
func mentionText(node *md.Mention) string {
|
||||||
switch {
|
switch {
|
||||||
case node.Channel != nil:
|
case node.Channel != nil:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue