diff --git a/_test/extra.txt b/_test/extra.txt
index 4f9499c..eefec94 100644
--- a/_test/extra.txt
+++ b/_test/extra.txt
@@ -159,3 +159,60 @@ bbb

//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+13: fenced code block starting with tab inside list
+//- - - - - - - - -//
+* foo
+ ```Makefile
+ foo
+ foo
+ ```
+//- - - - - - - - -//
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+14: fenced code block inside list, mismatched tab start
+//- - - - - - - - -//
+* foo
+ ```Makefile
+ foo
+ foo
+ ```
+//- - - - - - - - -//
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
+
+
+15: fenced code block inside nested list
+//- - - - - - - - -//
+* foo
+ - bar
+ ```Makefile
+ foo
+ foo
+ ```
+//- - - - - - - - -//
+
+//= = = = = = = = = = = = = = = = = = = = = = = =//
diff --git a/extension/definition_list.go b/extension/definition_list.go
index eb16dd0..35adeee 100644
--- a/extension/definition_list.go
+++ b/extension/definition_list.go
@@ -81,8 +81,8 @@ func (b *definitionListParser) Continue(node gast.Node, reader text.Reader, pc p
if w < list.Offset {
return parser.Close
}
- pos, padding := util.IndentPosition(line, reader.LineOffset(), list.Offset)
- reader.AdvanceAndSetPadding(pos, padding)
+ pos, padding, chars := util.IndentPosition(line, reader.LineOffset(), list.Offset)
+ reader.AdvanceAndSetPadding(pos, padding, chars)
return parser.Continue | parser.HasChildren
}
@@ -137,8 +137,8 @@ func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader,
}
para.Parent().RemoveChild(para.Parent(), para)
}
- cpos, padding := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
- reader.AdvanceAndSetPadding(cpos, padding)
+ cpos, padding, chars := util.IndentPosition(line[pos+1:], pos+1, list.Offset-pos-1)
+ reader.AdvanceAndSetPadding(cpos, padding, chars)
return ast.NewDefinitionDescription(), parser.HasChildren
}
diff --git a/extension/footnote.go b/extension/footnote.go
index 62f5ee6..2ec9dbd 100644
--- a/extension/footnote.go
+++ b/extension/footnote.go
@@ -66,7 +66,7 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
reader.Advance(pos)
return item, parser.NoChildren
}
- reader.AdvanceAndSetPadding(pos, padding)
+ reader.AdvanceAndSetPadding(pos, padding, segment.PaddingChars)
return item, parser.HasChildren
}
@@ -75,11 +75,11 @@ func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc pa
if util.IsBlank(line) {
return parser.Continue | parser.HasChildren
}
- childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
+ childpos, padding, paddingChars := util.IndentPosition(line, reader.LineOffset(), 4)
if childpos < 0 {
return parser.Close
}
- reader.AdvanceAndSetPadding(childpos, padding)
+ reader.AdvanceAndSetPadding(childpos, padding, paddingChars)
return parser.Continue | parser.HasChildren
}
diff --git a/parser/blockquote.go b/parser/blockquote.go
index e7778dc..36b0a14 100644
--- a/parser/blockquote.go
+++ b/parser/blockquote.go
@@ -33,7 +33,7 @@ func (b *blockquoteParser) process(reader text.Reader) bool {
}
reader.Advance(pos)
if line[pos-1] == '\t' {
- reader.SetPadding(2)
+ reader.SetPadding(2, []byte(" "))
}
return true
}
diff --git a/parser/code_block.go b/parser/code_block.go
index d02c21f..13f230e 100644
--- a/parser/code_block.go
+++ b/parser/code_block.go
@@ -24,17 +24,18 @@ func (b *codeBlockParser) Trigger() []byte {
func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine()
- pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
+ pos, padding, chars := util.IndentPosition(line, reader.LineOffset(), 4)
if pos < 0 || util.IsBlank(line) {
return nil, NoChildren
}
node := ast.NewCodeBlock()
- reader.AdvanceAndSetPadding(pos, padding)
+
+
+ reader.AdvanceAndSetPadding(pos, padding, chars)
_, segment = reader.PeekLine()
- node.Lines().Append(segment)
+ node.Lines().Append(segment.WithRenderPaddingTabs())
reader.Advance(segment.Len() - 1)
return node, NoChildren
-
}
func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
@@ -43,13 +44,13 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
node.Lines().Append(segment.TrimLeftSpaceWidth(4, reader.Source()))
return Continue | NoChildren
}
- pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
+ pos, padding, chars := util.IndentPosition(line, reader.LineOffset(), 4)
if pos < 0 {
return Close
}
- reader.AdvanceAndSetPadding(pos, padding)
+ reader.AdvanceAndSetPadding(pos, padding, chars)
_, segment = reader.PeekLine()
- node.Lines().Append(segment)
+ node.Lines().Append(segment.WithRenderPaddingTabs())
reader.Advance(segment.Len() - 1)
return Continue | NoChildren
}
diff --git a/parser/fcode_block.go b/parser/fcode_block.go
index f5b83ee..dd20486 100644
--- a/parser/fcode_block.go
+++ b/parser/fcode_block.go
@@ -70,6 +70,7 @@ func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Con
func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
line, segment := reader.PeekLine()
+
fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData)
w, pos := util.IndentWidth(line, reader.LineOffset())
if w < 4 {
@@ -86,11 +87,12 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
return Close
}
}
- pos, padding := util.DedentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent)
- seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
+ pos, padding, chars := util.DedentPositionPadding(line, reader.LineOffset(), segment.Padding, fdata.indent, segment.PaddingChars)
+ seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding, chars)
+ seg = seg.WithRenderPaddingTabs()
node.Lines().Append(seg)
- reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
+ reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding, chars)
return Continue | NoChildren
}
diff --git a/parser/list_item.go b/parser/list_item.go
index 4a698d8..4c2b097 100644
--- a/parser/list_item.go
+++ b/parser/list_item.go
@@ -44,9 +44,9 @@ func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (
return node, NoChildren
}
- pos, padding := util.IndentPosition(line[match[4]:], match[4], itemOffset)
+ pos, padding, chars := util.IndentPosition(line[match[4]:], match[4], itemOffset)
child := match[3] + pos
- reader.AdvanceAndSetPadding(child, padding)
+ reader.AdvanceAndSetPadding(child, padding, chars)
return node, HasChildren
}
@@ -66,8 +66,8 @@ func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context)
}
return Close
}
- pos, padding := util.IndentPosition(line, reader.LineOffset(), offset)
- reader.AdvanceAndSetPadding(pos, padding)
+ pos, padding, paddingChars := util.IndentPosition(line, reader.LineOffset(), offset)
+ reader.AdvanceAndSetPadding(pos, padding, paddingChars)
return Continue | HasChildren
}
diff --git a/text/reader.go b/text/reader.go
index df25e54..1659907 100644
--- a/text/reader.go
+++ b/text/reader.go
@@ -45,14 +45,14 @@ type Reader interface {
SetPosition(int, Segment)
// SetPadding sets padding to the reader.
- SetPadding(int)
+ SetPadding(int, []byte)
// Advance advances the internal pointer.
Advance(int)
// AdvanceAndSetPadding advances the internal pointer and add padding to the
// reader.
- AdvanceAndSetPadding(int, int)
+ AdvanceAndSetPadding(int, int, []byte)
// AdvanceLine advances the internal pointer to the next line head.
AdvanceLine()
@@ -120,7 +120,7 @@ func (r *reader) Peek() byte {
func (r *reader) PeekLine() ([]byte, Segment) {
if r.pos.Start >= 0 && r.pos.Start < r.sourceLength {
if r.peekedLine == nil {
- r.peekedLine = r.pos.Value(r.Source())
+ r.peekedLine = r.pos.ValueKeepTabs(r.Source())
}
return r.peekedLine, r.pos
}
@@ -169,9 +169,11 @@ func (r *reader) Advance(n int) {
if n < len(r.peekedLine) && r.pos.Padding == 0 {
r.pos.Start += n
r.peekedLine = nil
+
return
}
r.peekedLine = nil
+
l := r.sourceLength
for ; n > 0 && r.pos.Start < l; n-- {
if r.pos.Padding != 0 {
@@ -186,16 +188,19 @@ func (r *reader) Advance(n int) {
}
}
-func (r *reader) AdvanceAndSetPadding(n, padding int) {
+func (r *reader) AdvanceAndSetPadding(n, padding int, chars []byte) {
r.Advance(n)
if padding > r.pos.Padding {
- r.SetPadding(padding)
+ r.SetPadding(padding, chars)
}
+ // always set the chars
+ r.pos.PaddingChars = chars
}
func (r *reader) AdvanceLine() {
r.lineOffset = -1
r.peekedLine = nil
+
r.pos.Start = r.pos.Stop
r.head = r.pos.Start
if r.pos.Start < 0 {
@@ -223,8 +228,9 @@ func (r *reader) SetPosition(line int, pos Segment) {
r.pos = pos
}
-func (r *reader) SetPadding(v int) {
+func (r *reader) SetPadding(v int, chars []byte) {
r.pos.Padding = v
+ r.pos.PaddingChars = chars
}
func (r *reader) SkipSpaces() (Segment, int, bool) {
@@ -380,7 +386,7 @@ func (r *blockReader) Peek() byte {
func (r *blockReader) PeekLine() ([]byte, Segment) {
if r.line < r.segmentsLength && r.pos.Start >= 0 && r.pos.Start < r.last {
- return r.pos.Value(r.source), r.pos
+ return r.pos.ValueKeepTabs(r.source), r.pos
}
return nil, r.pos
}
@@ -406,10 +412,10 @@ func (r *blockReader) Advance(n int) {
}
}
-func (r *blockReader) AdvanceAndSetPadding(n, padding int) {
+func (r *blockReader) AdvanceAndSetPadding(n, padding int, chars []byte) {
r.Advance(n)
if padding > r.pos.Padding {
- r.SetPadding(padding)
+ r.SetPadding(padding, chars)
}
}
@@ -440,9 +446,10 @@ func (r *blockReader) SetPosition(line int, pos Segment) {
}
}
-func (r *blockReader) SetPadding(v int) {
+func (r *blockReader) SetPadding(v int, chars []byte) {
r.lineOffset = -1
r.pos.Padding = v
+ r.pos.PaddingChars = chars
}
func (r *blockReader) SkipSpaces() (Segment, int, bool) {
diff --git a/text/segment.go b/text/segment.go
index badd4bc..ce4cad4 100644
--- a/text/segment.go
+++ b/text/segment.go
@@ -18,6 +18,10 @@ type Segment struct {
// Padding is a padding length of the segment.
Padding int
+
+ PaddingChars []byte
+
+ RenderPaddingTabs bool
}
// NewSegment return a new Segment.
@@ -30,16 +34,25 @@ func NewSegment(start, stop int) Segment {
}
// NewSegmentPadding returns a new Segment with the given padding.
-func NewSegmentPadding(start, stop, n int) Segment {
+func NewSegmentPadding(start, stop, n int, chars []byte) Segment {
return Segment{
- Start: start,
- Stop: stop,
- Padding: n,
+ Start: start,
+ Stop: stop,
+ Padding: n,
+ PaddingChars: chars,
}
}
+func (t Segment) WithRenderPaddingTabs() Segment {
+ t.RenderPaddingTabs = true
+ return t
+}
+
// Value returns a value of the segment.
func (t *Segment) Value(buffer []byte) []byte {
+ if t.RenderPaddingTabs {
+ return t.ValueKeepTabs(buffer)
+ }
if t.Padding == 0 {
return buffer[t.Start:t.Stop]
}
@@ -48,6 +61,15 @@ func (t *Segment) Value(buffer []byte) []byte {
return append(result, buffer[t.Start:t.Stop]...)
}
+func (t *Segment) ValueKeepTabs(buffer []byte) []byte {
+ if t.Padding == 0 {
+ return buffer[t.Start:t.Stop]
+ }
+ result := make([]byte, 0, t.Padding+t.Stop-t.Start+1)
+ result = append(result, t.PaddingChars...)
+ return append(result, buffer[t.Start:t.Stop]...)
+}
+
// Len returns a length of the segment.
func (t *Segment) Len() int {
return t.Stop - t.Start + t.Padding
@@ -62,6 +84,8 @@ func (t *Segment) Between(other Segment) Segment {
t.Start,
other.Start,
t.Padding-other.Padding,
+ // ???? no idea what here, just put spaces there
+ bytes.Repeat([]byte{' '}, t.Padding-other.Padding),
)
}
@@ -78,7 +102,7 @@ func (t *Segment) TrimRightSpace(buffer []byte) Segment {
if l == len(v) {
return NewSegment(t.Start, t.Start)
}
- return NewSegmentPadding(t.Start, t.Stop-l, t.Padding)
+ return NewSegmentPadding(t.Start, t.Stop-l, t.Padding, t.PaddingChars)
}
// TrimLeftSpace returns a new segment by slicing off all leading
@@ -89,10 +113,39 @@ func (t *Segment) TrimLeftSpace(buffer []byte) Segment {
return NewSegment(t.Start+l, t.Stop)
}
+func trimWidthPaddingChars(origStartPos int, cut, goal int, chars []byte) []byte {
+ bytesPos := origStartPos - len(chars)
+ var i = 0
+ for i < cut {
+ if len(chars) == 0 {
+ // ???
+ return nil
+ }
+ b := chars[0]
+ if b == ' ' {
+ chars = chars[1:]
+ i++
+ bytesPos++
+ } else {
+ tw := util.TabWidth(bytesPos)
+ chars = chars[1:]
+ i += tw
+ bytesPos++
+ }
+ }
+ // if I can cut exactly, return the cut chars, otherwise just give up and put spaces
+ if i == cut {
+ return chars
+ } else {
+ return bytes.Repeat([]byte{' '}, goal)
+ }
+}
+
// TrimLeftSpaceWidth returns a new segment by slicing off leading space
// characters until the given width.
func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
padding := t.Padding
+ origWidth := width
for ; width > 0; width-- {
if padding == 0 {
break
@@ -100,8 +153,10 @@ func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
padding--
}
if width == 0 {
- return NewSegmentPadding(t.Start, t.Stop, padding)
+ paddingChars := trimWidthPaddingChars(t.Start, origWidth, padding, t.PaddingChars)
+ return NewSegmentPadding(t.Start, t.Stop, padding, paddingChars)
}
+ newPaddingChars := []byte{}
text := buffer[t.Start:t.Stop]
start := t.Start
for _, c := range text {
@@ -110,8 +165,14 @@ func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
}
if c == ' ' {
width--
+ if width < 0 {
+ newPaddingChars = append(newPaddingChars, ' ')
+ }
} else if c == '\t' {
width -= 4
+ if width < 0 {
+ newPaddingChars = append(newPaddingChars, '\t')
+ }
} else {
break
}
@@ -119,18 +180,20 @@ func (t *Segment) TrimLeftSpaceWidth(width int, buffer []byte) Segment {
}
if width < 0 {
padding = width * -1
+ return NewSegmentPadding(start, t.Stop, padding, newPaddingChars)
}
- return NewSegmentPadding(start, t.Stop, padding)
+ paddingChars := trimWidthPaddingChars(t.Start, origWidth, padding, t.PaddingChars)
+ return NewSegmentPadding(start, t.Stop, padding, paddingChars)
}
// WithStart returns a new Segment with same value except Start.
func (t *Segment) WithStart(v int) Segment {
- return NewSegmentPadding(v, t.Stop, t.Padding)
+ return NewSegmentPadding(v, t.Stop, t.Padding, t.PaddingChars)
}
// WithStop returns a new Segment with same value except Stop.
func (t *Segment) WithStop(v int) Segment {
- return NewSegmentPadding(t.Start, v, t.Padding)
+ return NewSegmentPadding(t.Start, v, t.Padding, t.PaddingChars)
}
// ConcatPadding concats the padding to the given slice.
diff --git a/util/util.go b/util/util.go
index 3ec73f5..df7b440 100644
--- a/util/util.go
+++ b/util/util.go
@@ -148,19 +148,36 @@ func TabWidth(currentPos int) int {
//
// width=2 is in the tab character. In this case, IndentPosition returns
// (pos=1, padding=2)
-func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
+func IndentPosition(bs []byte, currentPos, width int) (pos, padding int, paddingChars []byte) {
if width == 0 {
- return 0, 0
+ return 0, 0, nil
}
w := 0
l := len(bs)
i := 0
hasTab := false
+
+ firstOver := true
+
for ; i < l; i++ {
+ if w > width && firstOver {
+ firstOver = false
+ for j := 0; j < w-width; j++ {
+ paddingChars = append(paddingChars, ' ')
+ }
+ }
if bs[i] == '\t' {
+ if w >= width {
+ firstOver = false
+ paddingChars = append(paddingChars, '\t')
+ }
w += TabWidth(currentPos + w)
hasTab = true
} else if bs[i] == ' ' {
+ if w >= width {
+ firstOver = false
+ paddingChars = append(paddingChars, ' ')
+ }
w++
} else {
break
@@ -168,85 +185,56 @@ func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
}
if w >= width {
if !hasTab {
- return width, 0
+ return width, 0, nil
}
- return i, w - width
+ return i, w - width, paddingChars
}
- return -1, -1
-}
-
-// IndentPositionPadding searches an indent position with the given width for the given line.
-// This function is mostly same as IndentPosition except this function
-// takes account into additional paddings.
-func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) {
- if width == 0 {
- return 0, paddingv
- }
- w := 0
- i := 0
- l := len(bs)
- for ; i < l; i++ {
- if bs[i] == '\t' {
- w += TabWidth(currentPos + w)
- } else if bs[i] == ' ' {
- w++
- } else {
- break
- }
- }
- if w >= width {
- return i - paddingv, w - width
- }
- return -1, -1
-}
-
-// DedentPosition dedents lines by the given width.
-func DedentPosition(bs []byte, currentPos, width int) (pos, padding int) {
- if width == 0 {
- return 0, 0
- }
- w := 0
- l := len(bs)
- i := 0
- for ; i < l; i++ {
- if bs[i] == '\t' {
- w += TabWidth(currentPos + w)
- } else if bs[i] == ' ' {
- w++
- } else {
- break
- }
- }
- if w >= width {
- return i, w - width
- }
- return i, 0
+ return -1, -1, nil
}
// DedentPositionPadding dedents lines by the given width.
-// This function is mostly same as DedentPosition except this function
-// takes account into additional paddings.
-func DedentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, padding int) {
+// It takes account into additional paddings.
+func DedentPositionPadding(bs []byte, currentPos, paddingv, width int, origChars []byte) (pos, padding int, paddingChars []byte) {
if width == 0 {
- return 0, paddingv
+ return 0, paddingv, origChars
}
w := 0
i := 0
l := len(bs)
+
+ firstOver := true
+
for ; i < l; i++ {
+ if w > width && firstOver {
+ firstOver = false
+ for j := 0; j < w-width; j++ {
+ paddingChars = append(paddingChars, ' ')
+ }
+ }
+
if bs[i] == '\t' {
+ if w >= width {
+ firstOver = false
+ paddingChars = append(paddingChars, '\t')
+ }
+
w += TabWidth(currentPos + w)
} else if bs[i] == ' ' {
+ if w >= width {
+ firstOver = false
+ paddingChars = append(paddingChars, ' ')
+ }
+
w++
} else {
break
}
}
if w >= width {
- return i - paddingv, w - width
+ return i - paddingv, w - width, paddingChars
}
- return i - paddingv, 0
+ return i - paddingv, 0, nil
}
// IndentWidth calculate an indent width for the given line.
@@ -267,23 +255,6 @@ func IndentWidth(bs []byte, currentPos int) (width, pos int) {
return
}
-// FirstNonSpacePosition returns a position line that is a first nonspace
-// character.
-func FirstNonSpacePosition(bs []byte) int {
- i := 0
- for ; i < len(bs); i++ {
- c := bs[i]
- if c == ' ' || c == '\t' {
- continue
- }
- if c == '\n' {
- return -1
- }
- return i
- }
- return -1
-}
-
// FindClosure returns a position that closes the given opener.
// If codeSpan is set true, it ignores characters in code spans.
// If allowNesting is set true, closures correspond to nested opener will be
@@ -382,11 +353,6 @@ func TrimLeftLength(source, s []byte) int {
return len(source) - len(TrimLeft(source, s))
}
-// TrimRightLength returns a length of trailing specified characters.
-func TrimRightLength(source, s []byte) int {
- return len(source) - len(TrimRight(source, s))
-}
-
// TrimLeftSpaceLength returns a length of leading space characters.
func TrimLeftSpaceLength(source []byte) int {
i := 0