github.com/vnforks/kid@v5.11.1+incompatible/utils/markdown/list.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package markdown
     5  
     6  import (
     7  	"strings"
     8  )
     9  
    10  type ListItem struct {
    11  	blockBase
    12  	markdown                    string
    13  	hasTrailingBlankLine        bool
    14  	hasBlankLineBetweenChildren bool
    15  
    16  	Indentation int
    17  	Children    []Block
    18  }
    19  
    20  func (b *ListItem) Continuation(indentation int, r Range) *continuation {
    21  	s := b.markdown[r.Position:r.End]
    22  	if strings.TrimSpace(s) == "" {
    23  		if b.Children == nil {
    24  			return nil
    25  		}
    26  		return &continuation{
    27  			Remaining: r,
    28  		}
    29  	}
    30  	if indentation < b.Indentation {
    31  		return nil
    32  	}
    33  	return &continuation{
    34  		Indentation: indentation - b.Indentation,
    35  		Remaining:   r,
    36  	}
    37  }
    38  
    39  func (b *ListItem) AddChild(openBlocks []Block) []Block {
    40  	b.Children = append(b.Children, openBlocks[0])
    41  	if b.hasTrailingBlankLine {
    42  		b.hasBlankLineBetweenChildren = true
    43  	}
    44  	b.hasTrailingBlankLine = false
    45  	return openBlocks
    46  }
    47  
    48  func (b *ListItem) AddLine(indentation int, r Range) bool {
    49  	isBlank := strings.TrimSpace(b.markdown[r.Position:r.End]) == ""
    50  	if isBlank {
    51  		b.hasTrailingBlankLine = true
    52  	}
    53  	return false
    54  }
    55  
    56  func (b *ListItem) HasTrailingBlankLine() bool {
    57  	return b.hasTrailingBlankLine || (len(b.Children) > 0 && b.Children[len(b.Children)-1].HasTrailingBlankLine())
    58  }
    59  
    60  func (b *ListItem) isLoose() bool {
    61  	if b.hasBlankLineBetweenChildren {
    62  		return true
    63  	}
    64  	for i, child := range b.Children {
    65  		if i < len(b.Children)-1 && child.HasTrailingBlankLine() {
    66  			return true
    67  		}
    68  	}
    69  	return false
    70  }
    71  
    72  type List struct {
    73  	blockBase
    74  	markdown                    string
    75  	hasTrailingBlankLine        bool
    76  	hasBlankLineBetweenChildren bool
    77  
    78  	IsLoose           bool
    79  	IsOrdered         bool
    80  	OrderedStart      int
    81  	BulletOrDelimiter byte
    82  	Children          []*ListItem
    83  }
    84  
    85  func (b *List) Continuation(indentation int, r Range) *continuation {
    86  	s := b.markdown[r.Position:r.End]
    87  	if strings.TrimSpace(s) == "" {
    88  		return &continuation{
    89  			Remaining: r,
    90  		}
    91  	}
    92  	return &continuation{
    93  		Indentation: indentation,
    94  		Remaining:   r,
    95  	}
    96  }
    97  
    98  func (b *List) AddChild(openBlocks []Block) []Block {
    99  	if item, ok := openBlocks[0].(*ListItem); ok {
   100  		b.Children = append(b.Children, item)
   101  		if b.hasTrailingBlankLine {
   102  			b.hasBlankLineBetweenChildren = true
   103  		}
   104  		b.hasTrailingBlankLine = false
   105  		return openBlocks
   106  	} else if list, ok := openBlocks[0].(*List); ok {
   107  		if len(list.Children) == 1 && list.IsOrdered == b.IsOrdered && list.BulletOrDelimiter == b.BulletOrDelimiter {
   108  			return b.AddChild(openBlocks[1:])
   109  		}
   110  	}
   111  	return nil
   112  }
   113  
   114  func (b *List) AddLine(indentation int, r Range) bool {
   115  	isBlank := strings.TrimSpace(b.markdown[r.Position:r.End]) == ""
   116  	if isBlank {
   117  		b.hasTrailingBlankLine = true
   118  	}
   119  	return false
   120  }
   121  
   122  func (b *List) HasTrailingBlankLine() bool {
   123  	return b.hasTrailingBlankLine || (len(b.Children) > 0 && b.Children[len(b.Children)-1].HasTrailingBlankLine())
   124  }
   125  
   126  func (b *List) isLoose() bool {
   127  	if b.hasBlankLineBetweenChildren {
   128  		return true
   129  	}
   130  	for i, child := range b.Children {
   131  		if child.isLoose() || (i < len(b.Children)-1 && child.HasTrailingBlankLine()) {
   132  			return true
   133  		}
   134  	}
   135  	return false
   136  }
   137  
   138  func (b *List) Close() {
   139  	b.IsLoose = b.isLoose()
   140  }
   141  
   142  func parseListMarker(markdown string, r Range) (success, isOrdered bool, orderedStart int, bulletOrDelimiter byte, markerWidth int, remaining Range) {
   143  	digits := 0
   144  	n := 0
   145  	for i := r.Position; i < r.End && markdown[i] >= '0' && markdown[i] <= '9'; i++ {
   146  		digits++
   147  		n = n*10 + int(markdown[i]-'0')
   148  	}
   149  	if digits > 0 {
   150  		if digits > 9 || r.Position+digits >= r.End {
   151  			return
   152  		}
   153  		next := markdown[r.Position+digits]
   154  		if next != '.' && next != ')' {
   155  			return
   156  		}
   157  		return true, true, n, next, digits + 1, Range{r.Position + digits + 1, r.End}
   158  	}
   159  	if r.Position >= r.End {
   160  		return
   161  	}
   162  	next := markdown[r.Position]
   163  	if next != '-' && next != '+' && next != '*' {
   164  		return
   165  	}
   166  	return true, false, 0, next, 1, Range{r.Position + 1, r.End}
   167  }
   168  
   169  func listStart(markdown string, indent int, r Range, matchedBlocks, unmatchedBlocks []Block) []Block {
   170  	afterList := false
   171  	if len(matchedBlocks) > 0 {
   172  		_, afterList = matchedBlocks[len(matchedBlocks)-1].(*List)
   173  	}
   174  	if !afterList && indent > 3 {
   175  		return nil
   176  	}
   177  
   178  	success, isOrdered, orderedStart, bulletOrDelimiter, markerWidth, remaining := parseListMarker(markdown, r)
   179  	if !success {
   180  		return nil
   181  	}
   182  
   183  	isBlank := strings.TrimSpace(markdown[remaining.Position:remaining.End]) == ""
   184  	if len(matchedBlocks) > 0 && len(unmatchedBlocks) == 0 {
   185  		if _, ok := matchedBlocks[len(matchedBlocks)-1].(*Paragraph); ok {
   186  			if isBlank || (isOrdered && orderedStart != 1) {
   187  				return nil
   188  			}
   189  		}
   190  	}
   191  
   192  	indentAfterMarker, indentBytesAfterMarker := countIndentation(markdown, remaining)
   193  	if !isBlank && indentAfterMarker < 1 {
   194  		return nil
   195  	}
   196  
   197  	remaining = Range{remaining.Position + indentBytesAfterMarker, remaining.End}
   198  	consumedIndentAfterMarker := indentAfterMarker
   199  	if isBlank || indentAfterMarker >= 5 {
   200  		consumedIndentAfterMarker = 1
   201  	}
   202  
   203  	listItem := &ListItem{
   204  		markdown:    markdown,
   205  		Indentation: indent + markerWidth + consumedIndentAfterMarker,
   206  	}
   207  	list := &List{
   208  		markdown:          markdown,
   209  		IsOrdered:         isOrdered,
   210  		OrderedStart:      orderedStart,
   211  		BulletOrDelimiter: bulletOrDelimiter,
   212  		Children:          []*ListItem{listItem},
   213  	}
   214  	ret := []Block{list, listItem}
   215  	if descendants := blockStartOrParagraph(markdown, indentAfterMarker-consumedIndentAfterMarker, remaining, nil, nil); descendants != nil {
   216  		listItem.Children = append(listItem.Children, descendants[0])
   217  		ret = append(ret, descendants...)
   218  	}
   219  	return ret
   220  }