github.com/levb/mattermost-server@v5.3.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 }