Skip to content

Commit

Permalink
fix: many rendering issues (#317)
Browse files Browse the repository at this point in the history
* wip

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* wip

* wip

* wip

* fix: autolink

closes #290

* fix: escape characters

closes #106
closes #274
closes #311

* fix: table

* ci: golangci lint update

* feat: use x/golden

* test: #106

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: #290

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: #312

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: #257

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* test: #149

* fix: #239

* feat: use lipgloss table

closes #262

Co-authored-by: bashbunni <bunni@bashbunni.dev>

* fix: codespan is not a block

* test: #315

* test: #316

* fix: #316

* test: table

* fix: codespans, tables

* test: table

* test: #117

* test: #60

* fix: rm stylewriter

* fix: #313

* fix: margin

* fix: blocks and word wrap

* fix: build

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* chore: gitattributes

* fix: test opt

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: stable lipgloss

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

* fix: double styles

* fix: tables

* fix: tables

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Co-authored-by: bashbunni <bunni@bashbunni.dev>
  • Loading branch information
caarlos0 and bashbunni committed Jul 31, 2024
1 parent 47b99e4 commit 5f5965e
Show file tree
Hide file tree
Showing 92 changed files with 858 additions and 573 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.golden linguist-generated
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
cmd/
!*.test
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ linters:
- godot
- godox
- goimports
- gomnd
- mnd
- goprintffuncname
- gosec
- misspell
Expand Down
55 changes: 44 additions & 11 deletions ansi/baseelement.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ func renderText(w io.Writer, p termenv.Profile, rules StylePrimitive, s string)
}

out := termenv.String(s)

if rules.Upper != nil && *rules.Upper {
out = termenv.String(strings.ToUpper(s))
}
Expand Down Expand Up @@ -79,35 +78,69 @@ func renderText(w io.Writer, p termenv.Profile, rules StylePrimitive, s string)
_, _ = io.WriteString(w, out.String())
}

func (e *BaseElement) StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error {
bs := ctx.blockStack
st1 := cascadeStylePrimitives(bs.Current().Style.StylePrimitive, style)
st2 := cascadeStylePrimitives(bs.With(e.Style), style)

return e.doRender(w, ctx.options.ColorProfile, st1, st2)
}

func (e *BaseElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack
st1 := bs.Current().Style.StylePrimitive
st2 := bs.With(e.Style)
return e.doRender(w, ctx.options.ColorProfile, st1, st2)
}

renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Prefix)
func (e *BaseElement) doRender(w io.Writer, p termenv.Profile, st1, st2 StylePrimitive) error {
renderText(w, p, st1, e.Prefix)
defer func() {
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, e.Suffix)
renderText(w, p, st1, e.Suffix)
}()

rules := bs.With(e.Style)
// render unstyled prefix/suffix
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
renderText(w, p, st1, st2.BlockPrefix)
defer func() {
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
renderText(w, p, st1, st2.BlockSuffix)
}()

// render styled prefix/suffix
renderText(w, ctx.options.ColorProfile, rules, rules.Prefix)
renderText(w, p, st2, st2.Prefix)
defer func() {
renderText(w, ctx.options.ColorProfile, rules, rules.Suffix)
renderText(w, p, st2, st2.Suffix)
}()

s := e.Token
if len(rules.Format) > 0 {
if len(st2.Format) > 0 {
var err error
s, err = formatToken(rules.Format, s)
s, err = formatToken(st2.Format, s)
if err != nil {
return err
}
}
renderText(w, ctx.options.ColorProfile, rules, s)
renderText(w, p, st2, escapeReplacer.Replace(s))
return nil
}

// https://www.markdownguide.org/basic-syntax/#characters-you-can-escape
var escapeReplacer = strings.NewReplacer(
"\\\\", "\\",
"\\`", "`",
"\\*", "*",
"\\_", "_",
"\\{", "{",
"\\}", "}",
"\\[", "[",
"\\]", "]",
"\\<", "<",
"\\>", ">",
"\\(", "(",
"\\)", ")",
"\\#", "#",
"\\+", "+",
"\\-", "-",
"\\.", ".",
"\\!", "!",
"\\|", "|",
)
12 changes: 8 additions & 4 deletions ansi/blockelement.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"bytes"
"io"

"github.com/muesli/reflow/wordwrap"
"github.com/charmbracelet/x/ansi"
)

// BlockElement provides a render buffer for children of a block element.
Expand All @@ -30,10 +30,14 @@ func (e *BlockElement) Finish(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack

if e.Margin {
s := ansi.Wordwrap(
bs.Current().Block.String(),
int(bs.Width(ctx)),
" ,.;-+|",
)

mw := NewMarginWriter(ctx, w, bs.Current().Style)
_, err := mw.Write(
wordwrap.Bytes(bs.Current().Block.Bytes(), int(bs.Width(ctx))))
if err != nil {
if _, err := io.WriteString(mw, s); err != nil {
return err
}

Expand Down
4 changes: 2 additions & 2 deletions ansi/blockstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ func (s BlockStack) Margin() uint {

// Width returns the available rendering width.
func (s BlockStack) Width(ctx RenderContext) uint {
if s.Indent()+s.Margin()*2 > uint(ctx.options.WordWrap) {
if s.Indent()*s.Margin() > uint(ctx.options.WordWrap) {
return 0
}
return uint(ctx.options.WordWrap) - s.Indent() - s.Margin()*2
return uint(ctx.options.WordWrap) - s.Indent()*s.Margin()
}

// Parent returns the current BlockElement's parent.
Expand Down
1 change: 1 addition & 0 deletions ansi/codeblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {

if len(theme) > 0 {
renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)

err := quick.Highlight(iw, e.Code, e.Language, "terminal256", theme)
if err != nil {
return err
Expand Down
14 changes: 14 additions & 0 deletions ansi/codespan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ansi

import "io"

// A CodeSpanElement is used to render codespan.
type CodeSpanElement struct {
Text string
Style StylePrimitive
}

func (e *CodeSpanElement) Render(w io.Writer, ctx RenderContext) error {
renderText(w, ctx.options.ColorProfile, e.Style, e.Style.Prefix+e.Text+e.Style.Suffix)
return nil
}
132 changes: 69 additions & 63 deletions ansi/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ type ElementRenderer interface {
Render(w io.Writer, ctx RenderContext) error
}

type StyleOverriderElementRenderer interface {
StyleOverrideRender(w io.Writer, ctx RenderContext, style StylePrimitive) error
}

// ElementFinisher is called when leaving a markdown node.
type ElementFinisher interface {
Finish(w io.Writer, ctx RenderContext) error
Expand Down Expand Up @@ -63,8 +67,11 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {

// Paragraph
case ast.KindParagraph:
if node.Parent() != nil && node.Parent().Kind() == ast.KindListItem {
return Element{}
if node.Parent() != nil {
kind := node.Parent().Kind()
if kind == ast.KindListItem {
return Element{}
}
}
return Element{
Renderer: &ParagraphElement{
Expand All @@ -76,10 +83,9 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
// Blockquote
case ast.KindBlockquote:
e := &BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
Margin: true,
Newline: true,
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.BlockQuote, false),
Margin: true,
}
return Element{
Entering: "\n",
Expand Down Expand Up @@ -176,16 +182,16 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {

case ast.KindEmphasis:
n := node.(*ast.Emphasis)
s := string(n.Text(source))
style := ctx.options.Styles.Emph
if n.Level > 1 {
style = ctx.options.Styles.Strong
var children []ElementRenderer
nn := n.FirstChild()
for nn != nil {
children = append(children, tr.NewElement(nn, source).Renderer)
nn = nn.NextSibling()
}

return Element{
Renderer: &BaseElement{
Token: html.UnescapeString(s),
Style: style,
Renderer: &EmphasisElement{
Level: n.Level,
Children: children,
},
}

Expand Down Expand Up @@ -213,26 +219,45 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
// Links
case ast.KindLink:
n := node.(*ast.Link)
var children []ElementRenderer
nn := n.FirstChild()
for nn != nil {
children = append(children, tr.NewElement(nn, source).Renderer)
nn = nn.NextSibling()
}
return Element{
Renderer: &LinkElement{
Text: textFromChildren(node, source),
BaseURL: ctx.options.BaseURL,
URL: string(n.Destination),
BaseURL: ctx.options.BaseURL,
URL: string(n.Destination),
Children: children,
},
}
case ast.KindAutoLink:
n := node.(*ast.AutoLink)
u := string(n.URL(source))
label := string(n.Label(source))

var children []ElementRenderer
nn := n.FirstChild()
for nn != nil {
children = append(children, tr.NewElement(nn, source).Renderer)
nn = nn.NextSibling()
}

if len(children) == 0 {
children = append(children, &BaseElement{
Token: u,
})
}

if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(u), "mailto:") {
u = "mailto:" + u
}

return Element{
Renderer: &LinkElement{
Text: label,
BaseURL: ctx.options.BaseURL,
URL: u,
Children: children,
BaseURL: ctx.options.BaseURL,
URL: u,
},
}

Expand Down Expand Up @@ -281,45 +306,43 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
}

case ast.KindCodeSpan:
// n := node.(*ast.CodeSpan)
e := &BlockElement{
Block: &bytes.Buffer{},
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false),
}
n := node.(*ast.CodeSpan)
s := string(n.Text(source))
return Element{
Renderer: e,
Finisher: e,
Renderer: &CodeSpanElement{
Text: html.UnescapeString(s),
Style: cascadeStyle(ctx.blockStack.Current().Style, ctx.options.Styles.Code, false).StylePrimitive,
},
}

// Tables
case astext.KindTable:
table := node.(*astext.Table)
te := &TableElement{table: table}
te := &TableElement{
table: table,
}
return Element{
Entering: "\n",
Exiting: "\n",
Renderer: te,
Finisher: te,
}

case astext.KindTableCell:
s := ""
n := node.FirstChild()
for n != nil {
switch t := n.(type) {
case *ast.AutoLink:
s += string(t.Label(source))
default:
s += string(n.Text(source))
}

n = n.NextSibling()
n := node.(*astext.TableCell)
var children []ElementRenderer
nn := n.FirstChild()
for nn != nil {
children = append(children, tr.NewElement(nn, source).Renderer)
nn = nn.NextSibling()
}

r := &TableCellElement{
Children: children,
Head: node.Parent().Kind() == astext.KindTableHeader,
}
return Element{
Renderer: &TableCellElement{
Text: s,
Head: node.Parent().Kind() == astext.KindTableHeader,
},
Renderer: r,
}

case astext.KindTableHeader:
Expand Down Expand Up @@ -358,20 +381,21 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
Newline: true,
}
return Element{
Entering: "\n",
Renderer: e,
Finisher: e,
}

case astext.KindDefinitionTerm:
return Element{
Entering: "\n",
Renderer: &BaseElement{
Style: ctx.options.Styles.DefinitionTerm,
},
}

case astext.KindDefinitionDescription:
return Element{
Exiting: "\n",
Renderer: &BaseElement{
Style: ctx.options.Styles.DefinitionDescription,
},
Expand All @@ -398,21 +422,3 @@ func (tr *ANSIRenderer) NewElement(node ast.Node, source []byte) Element {
return Element{}
}
}

func textFromChildren(node ast.Node, source []byte) string {
var s string
for c := node.FirstChild(); c != nil; c = c.NextSibling() {
if c.Kind() == ast.KindText {
cn := c.(*ast.Text)
s += string(cn.Segment.Value(source))

if cn.HardLineBreak() || (cn.SoftLineBreak()) {
s += "\n"
}
} else {
s += string(c.Text(source))
}
}

return s
}
Loading

0 comments on commit 5f5965e

Please sign in to comment.