// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strconv"
var listTerminatedBy []BlockRule
func skipBulletListMarker(s *StateBlock, startLine int) int {
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
src := s.Src
if pos >= max {
return -1
}
marker := src[pos]
if marker != '*' && marker != '-' && marker != '+' {
return -1
}
pos++
if pos < max && !byteIsSpace(src[pos]) {
return -1
}
return pos
}
func skipOrderedListMarker(s *StateBlock, startLine int) int {
start := s.BMarks[startLine] + s.TShift[startLine]
pos := start
max := s.EMarks[startLine]
if pos+1 >= max {
return -1
}
src := s.Src
ch := src[pos]
if ch < '0' || ch > '9' {
return -1
}
pos++
for {
if pos >= max {
return -1
}
ch = src[pos]
pos++
if ch >= '0' && ch <= '9' {
if pos-start >= 10 {
return -1
}
continue
}
if ch == ')' || ch == '.' {
break
}
return -1
}
if pos < max && !byteIsSpace(src[pos]) {
return -1
}
return pos
}
func markParagraphsTight(s *StateBlock, idx int) {
level := s.Level + 2
tokens := s.Tokens
for i := idx + 2; i < len(tokens)-2; i++ {
if tokens[i].Level() == level {
if tok, ok := tokens[i].(*ParagraphOpen); ok {
tok.Hidden = true
i += 2
tokens[i].(*ParagraphClose).Hidden = true
}
}
}
}
func ruleList(s *StateBlock, startLine, endLine int, silent bool) bool {
isTerminatingParagraph := false
tight := true
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
src := s.Src
if silent && s.ParentType == ptParagraph {
if s.TShift[startLine] >= s.BlkIndent {
isTerminatingParagraph = true
}
}
var start int
var markerValue int
isOrdered := false
posAfterMarker := skipOrderedListMarker(s, startLine)
if posAfterMarker > 0 {
isOrdered = true
start = s.BMarks[startLine] + s.TShift[startLine]
markerValue, _ = strconv.Atoi(src[start : posAfterMarker-1])
if isTerminatingParagraph && markerValue != 1 {
return false
}
} else {
posAfterMarker = skipBulletListMarker(s, startLine)
if posAfterMarker < 0 {
return false
}
}
if isTerminatingParagraph {
if s.SkipSpaces(posAfterMarker) >= s.EMarks[startLine] {
return false
}
}
markerChar := src[posAfterMarker-1]
if silent {
return true
}
tokenIdx := len(s.Tokens)
var listMap *[2]int
if isOrdered {
tok := &OrderedListOpen{
Order: markerValue,
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
listMap = &tok.Map
} else {
tok := &BulletListOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
listMap = &tok.Map
}
nextLine := startLine
prevEmptyEnd := false
oldParentType := s.ParentType
s.ParentType = ptList
var pos int
var contentStart int
outer:
for nextLine < endLine {
pos = posAfterMarker
max := s.EMarks[nextLine]
initial := s.SCount[nextLine] + posAfterMarker - (s.BMarks[startLine] + s.TShift[startLine])
offset := initial
loop:
for pos < max {
switch src[pos] {
case '\t':
offset += 4 - (offset+s.BSCount[nextLine])%4
case ' ':
offset++
default:
break loop
}
pos++
}
contentStart = pos
indentAfterMarker := 1
if contentStart < max {
if iam := offset - initial; iam <= 4 {
indentAfterMarker = iam
}
}
indent := initial + indentAfterMarker
tok := &ListItemOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
itemMap := &tok.Map
oldIndent := s.BlkIndent
oldTight := s.Tight
oldTShift := s.TShift[startLine]
oldLIndent := s.SCount[startLine]
s.BlkIndent = indent
s.Tight = true
s.TShift[startLine] = contentStart - s.BMarks[startLine]
s.SCount[startLine] = offset
if contentStart >= max && s.IsLineEmpty(startLine+1) {
s.Line = min(s.Line+2, endLine)
} else {
s.Md.Block.Tokenize(s, startLine, endLine)
}
if !s.Tight || prevEmptyEnd {
tight = false
}
prevEmptyEnd = s.Line-startLine > 1 && s.IsLineEmpty(s.Line-1)
s.BlkIndent = oldIndent
s.TShift[startLine] = oldTShift
s.SCount[startLine] = oldLIndent
s.Tight = oldTight
s.PushClosingToken(&ListItemClose{})
startLine = s.Line
nextLine = startLine
(*itemMap)[1] = nextLine
if nextLine >= endLine {
break
}
contentStart = s.BMarks[startLine]
if s.SCount[nextLine] < s.BlkIndent {
break
}
for _, r := range listTerminatedBy {
if r(s, nextLine, endLine, true) {
break outer
}
}
if isOrdered {
posAfterMarker = skipOrderedListMarker(s, nextLine)
if posAfterMarker < 0 {
break
}
} else {
posAfterMarker = skipBulletListMarker(s, nextLine)
if posAfterMarker < 0 {
break
}
}
if markerChar != src[posAfterMarker-1] {
break
}
}
if isOrdered {
s.PushClosingToken(&OrderedListClose{})
} else {
s.PushClosingToken(&BulletListClose{})
}
(*listMap)[1] = nextLine
s.Line = nextLine
s.ParentType = oldParentType
if tight {
markParagraphsTight(s, tokenIdx)
}
return true
}