Skip to content

Commit

Permalink
allow indentation in fences
Browse files Browse the repository at this point in the history
  • Loading branch information
statup-github committed Jan 16, 2023
1 parent df7461d commit 4ae084e
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 14 deletions.
13 changes: 13 additions & 0 deletions gen_random_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package fences

import "math/rand"

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func genRandomString(n int) string {
bytes := make([]byte, n)
for i := range bytes {
bytes[i] = letters[rand.Intn(len(letters))]
}
return string(bytes)
}
66 changes: 52 additions & 14 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ func NewFencedContainerParser() parser.BlockParser {
}

type fenceData struct {
char byte
indent int
length int
node ast.Node
char byte // Currently, this is always ":"
indent int // The indentation of the opening (and closing) tags (:::{})
length int // The length of the fence, e.g. is it ::: or ::::?
node ast.Node // The node of the fence
contentIndent int // The indentation of the content relative to the previous fenced block. The first line of the content is taken as its indentation. If you want a fence with just a code block you need to use backticks
contentHasStarted bool // Only used as an indicator if contentIndent has been set already
}

var fencedContainerInfoKey = parser.NewContextKey()
Expand All @@ -47,7 +49,9 @@ func (b *fencedContainerParser) Open(parent ast.Node, reader text.Reader, pc par
return nil, parser.NoChildren
}

node := NewFencedContainer()
// ========================================================================== //
// Without attributes we return

if i >= len(line)-1 {
// If there are no attributes we can't create a div because we won't know
// if a ":::" ends the last fenced container or opens a new one
Expand All @@ -65,23 +69,38 @@ func (b *fencedContainerParser) Open(parent ast.Node, reader text.Reader, pc par
return nil, parser.NoChildren
}

// ========================================================================== //
// With attributes we construct the node

reader.Advance(left)
attrs, ok := parser.ParseAttributes(reader)
node := NewFencedContainer()

fenceID := genRandomString(24)
node.SetAttributeString("data-fenceid", []byte(fenceID))

attrs, ok := parser.ParseAttributes(reader)
if ok {
for _, attr := range attrs {
node.SetAttribute(attr.Name, attr.Value)
}
}

fdata := &fenceData{fenceChar, findent, oFenceLength, node}
var fdataMap []*fenceData
fdata := &fenceData{
char: fenceChar,
indent: findent,
length: oFenceLength,
node: node,
contentIndent: 0,
contentHasStarted: false,
}

var fdataMap map[string]*fenceData

if oldData := pc.Get(fencedContainerInfoKey); oldData != nil {
fdataMap = oldData.([]*fenceData)
fdataMap = append(fdataMap, fdata)
fdataMap = oldData.(map[string]*fenceData)
fdataMap[fenceID] = fdata
} else {
fdataMap = []*fenceData{fdata}
fdataMap = map[string]*fenceData{fenceID: fdata}
}
pc.Set(fencedContainerInfoKey, fdataMap)

Expand All @@ -98,15 +117,30 @@ func (b *fencedContainerParser) Open(parent ast.Node, reader text.Reader, pc par

func (b *fencedContainerParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
rawdata := pc.Get(fencedContainerInfoKey)
fdataMap := rawdata.([]*fenceData)
fdata := fdataMap[len(fdataMap)-1]
fdataMap := rawdata.(map[string]*fenceData)

rawFenceID, ok := node.AttributeString("data-fenceid")
if !ok {
// huhu: don't panic in production
panic("fenceID is missing")
}
fenceID := string(rawFenceID.([]byte))
fdata := fdataMap[fenceID]

line, segment := reader.PeekLine()
w, pos := util.IndentWidth(line, reader.LineOffset())

if !fdata.contentHasStarted && !util.IsBlank(line[pos:]) {
fdata.contentHasStarted = true
fdata.contentIndent = w

fdataMap[fenceID] = fdata
pc.Set(fencedContainerInfoKey, fdataMap)
}

if close, newline := b.closes(line, segment, w, pos, node, fdata); close {
reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
fdataMap = fdataMap[:len(fdataMap)-1]
delete(fdataMap, fenceID)

if len(fdataMap) == 0 {
return parser.Close
Expand All @@ -116,6 +150,10 @@ func (b *fencedContainerParser) Continue(node ast.Node, reader text.Reader, pc p
}
}

if fdata.contentIndent > 0 {
reader.Advance(fdata.contentIndent)
}

return parser.Continue | parser.HasChildren
}

Expand Down
76 changes: 76 additions & 0 deletions tests/indent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package fences_test

import (
"os"
"strings"

fences "github.com/stefanfritsch/goldmark-fences"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/text"
)

func Example_indent() {
srcString := strings.ReplaceAll(`
## Hello
This is outside
§§§
this is unindented code
§§§
this is indented code
:::{#big-div .add-border}
This is indented
:::{.background-green .font-big}
## This is indented within indented
§§§
this is unindented code in an indented block
§§§
This is indented code in an indented block
:::
::: {.background-yellow}
This is not indented
:::
this is not indented enough
:::`, "§", "`")
src := []byte(srcString)

markdown := goldmark.New(
goldmark.WithExtensions(
&fences.Extender{},
),
)

doc := markdown.Parser().Parse(text.NewReader(src))
markdown.Renderer().Render(os.Stdout, src, doc)

// Output:
// <h2>Hello</h2>
// <p>This is outside</p>
// <pre><code>this is unindented code
// </code></pre>
// <pre><code>this is indented code
// </code></pre>
// <div data-fenceid="XVlBzgbaiCMRAjWwhTHctcuA" id="big-div" class="add-border">
// <p>This is indented</p>
// <div data-fenceid="xhxKQFDaFpLSjFbcXoEFfRsW" class="background-green font-big">
// <h2>This is indented within indented</h2>
// <pre><code>this is unindented code in an indented block
// </code></pre>
// <pre><code>This is indented code in an indented block
// </code></pre>
// </div>
// <div data-fenceid="xPLDnJObCsNVlgTeMaPEZQle" class="background-yellow">
// <p>This is not indented</p>
// </div>
// <p>s is not indented enough</p>
// </div>
}

0 comments on commit 4ae084e

Please sign in to comment.