Skip to content

Commit

Permalink
Add code block indentation token support
Browse files Browse the repository at this point in the history
This change incorporates the specified indentation token when rendering
code blocks. It includes fixes to how the margin and indentation tokens
are rendered globally. When both are specified, the margin is rendered
first as an empty space.

This PR is another attempt at adding the functionality from
#299
  • Loading branch information
jahvon committed Aug 3, 2024
1 parent 1fcce6f commit f579199
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 36 deletions.
23 changes: 5 additions & 18 deletions ansi/codeblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/quick"
"github.com/alecthomas/chroma/v2/styles"
"github.com/muesli/reflow/indent"
"github.com/muesli/termenv"
)

Expand Down Expand Up @@ -62,16 +61,7 @@ func chromaStyle(style StylePrimitive) string {

func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
bs := ctx.blockStack

var indentation uint
var margin uint
rules := ctx.options.Styles.CodeBlock
if rules.Indent != nil {
indentation = *rules.Indent
}
if rules.Margin != nil {
margin = *rules.Margin
}
theme := rules.Theme

if rules.Chroma != nil && ctx.options.ColorProfile != termenv.Ascii {
Expand Down Expand Up @@ -118,18 +108,15 @@ func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
mutex.Unlock()
}

iw := indent.NewWriterPipe(w, indentation+margin, func(wr io.Writer) {
renderText(w, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, " ")
})

mw := NewMarginWriter(ctx, w, rules.StyleBlock)
if len(theme) > 0 {
renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)
renderText(mw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockPrefix)

err := quick.Highlight(iw, e.Code, e.Language, "terminal256", theme)
err := quick.Highlight(mw, e.Code, e.Language, "terminal256", theme)
if err != nil {
return err
}
renderText(iw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
renderText(mw, ctx.options.ColorProfile, bs.Current().Style.StylePrimitive, rules.BlockSuffix)
return nil
}

Expand All @@ -139,5 +126,5 @@ func (e *CodeBlockElement) Render(w io.Writer, ctx RenderContext) error {
Style: rules.StylePrimitive,
}

return el.Render(iw, ctx)
return el.Render(mw, ctx)
}
59 changes: 41 additions & 18 deletions ansi/margin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,71 @@ import (

"github.com/muesli/reflow/indent"
"github.com/muesli/reflow/padding"
"github.com/muesli/termenv"
)

// MarginWriter is a Writer that applies indentation and padding around
// whatever you write to it.
type MarginWriter struct {
indentation, margin uint
indentPos, marginPos uint
indentToken string

profile termenv.Profile
rules StylePrimitive

w io.Writer
pw *padding.Writer
iw *indent.Writer
}

// NewMarginWriter returns a new MarginWriter.
func NewMarginWriter(ctx RenderContext, w io.Writer, rules StyleBlock) *MarginWriter {
mw := &MarginWriter{w: w}
bs := ctx.blockStack

var indentation uint
var margin uint
if rules.Indent != nil {
indentation = *rules.Indent
mw.indentation = *rules.Indent
mw.indentToken = " "
if rules.IndentToken != nil {
mw.indentToken = *rules.IndentToken
}
}
if rules.Margin != nil {
margin = *rules.Margin
mw.margin = *rules.Margin
}

pw := padding.NewWriterPipe(w, bs.Width(ctx), func(wr io.Writer) {
mw.pw = padding.NewWriterPipe(w, bs.Width(ctx), func(wr io.Writer) {
renderText(w, ctx.options.ColorProfile, rules.StylePrimitive, " ")
})

ic := " "
if rules.IndentToken != nil {
ic = *rules.IndentToken
}
iw := indent.NewWriterPipe(pw, indentation+margin, func(wr io.Writer) {
renderText(w, ctx.options.ColorProfile, bs.Parent().Style.StylePrimitive, ic)
})

return &MarginWriter{
w: w,
pw: pw,
iw: iw,
}
mw.iw = indent.NewWriterPipe(mw.pw, mw.indentation+mw.margin, mw.indentFunc)
return mw
}

func (w *MarginWriter) Write(b []byte) (int, error) {
return w.iw.Write(b)
}

// indentFunc is called when writing each the margin and indentation tokens.
// The margin is written first, using an empty space character as the token.
// The indentation is written next, using the token specified in the rules.
func (mw *MarginWriter) indentFunc(w io.Writer) {
ic := " "
switch {
case mw.margin == 0 && mw.indentation == 0:
return
case mw.margin >= 1 && mw.indentation == 0:
break
case mw.margin >= 1 && mw.marginPos < mw.margin:
mw.marginPos++
case mw.indentation >= 1 && mw.indentPos < mw.indentation:
mw.indentPos++
ic = mw.indentToken
if mw.indentPos == mw.indentation {
mw.marginPos = 0
mw.indentPos = 0
}
}
renderText(w, mw.profile, mw.rules, ic)
}

0 comments on commit f579199

Please sign in to comment.