Skip to content

Commit c9d3461

Browse files
committed
chore: update merge list items
1 parent 9640de1 commit c9d3461

File tree

4 files changed

+159
-90
lines changed

4 files changed

+159
-90
lines changed

ast/util.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package ast
2+
3+
import "slices"
4+
5+
func IsListItemNode(node Node) bool {
6+
nodeType := node.Type()
7+
return slices.Contains([]NodeType{
8+
OrderedListItemNode, UnorderedListItemNode, TaskListItemNode,
9+
}, nodeType)
10+
}
11+
12+
func GetListItemKindAndIndent(node Node) (ListKind, int) {
13+
switch n := node.(type) {
14+
case *OrderedListItem:
15+
return OrderedList, n.Indent
16+
case *UnorderedListItem:
17+
return UnorderedList, n.Indent
18+
case *TaskListItem:
19+
return DescrpitionList, n.Indent
20+
default:
21+
return "", 0
22+
}
23+
}

parser/parser.go

Lines changed: 47 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package parser
22

33
import (
4-
"slices"
5-
64
"github.com/usememos/gomark/ast"
75
"github.com/usememos/gomark/parser/tokenizer"
86
)
@@ -55,7 +53,9 @@ func ParseBlockWithParsers(tokens []*tokenizer.Token, blockParsers []BlockParser
5553
}
5654
}
5755
}
58-
return mergeListItemNodes(nodes), nil
56+
57+
nodes = mergeListItemNodes(nodes)
58+
return nodes, nil
5959
}
6060

6161
var defaultInlineParsers = []InlineParser{
@@ -101,81 +101,59 @@ func ParseInlineWithParsers(tokens []*tokenizer.Token, inlineParsers []InlinePar
101101
}
102102

103103
func mergeListItemNodes(nodes []ast.Node) []ast.Node {
104-
if len(nodes) == 0 {
105-
return nodes
106-
}
107-
result := []ast.Node{}
108-
for i := 0; i < len(nodes); i++ {
109-
var prevNode, nextNode, prevResultNode ast.Node
110-
if i > 0 {
111-
prevNode = nodes[i-1]
112-
}
113-
if i < len(nodes)-1 {
114-
nextNode = nodes[i+1]
115-
}
116-
if len(result) > 0 {
117-
prevResultNode = result[len(result)-1]
118-
}
119-
switch nodes[i].(type) {
120-
case *ast.OrderedListItem, *ast.UnorderedListItem, *ast.TaskListItem:
121-
var listKind ast.ListKind
122-
var indent int
123-
switch item := nodes[i].(type) {
124-
case *ast.OrderedListItem:
125-
listKind = ast.OrderedList
126-
indent = item.Indent
127-
case *ast.UnorderedListItem:
128-
listKind = ast.UnorderedList
129-
indent = item.Indent
130-
case *ast.TaskListItem:
131-
listKind = ast.DescrpitionList
132-
indent = item.Indent
104+
var result []ast.Node
105+
var stack []*ast.List
106+
107+
for _, node := range nodes {
108+
nodeType := node.Type()
109+
110+
// Handle line breaks.
111+
if nodeType == ast.LineBreakNode {
112+
// If the stack is not empty and the last node is a list node, add the line break to the list.
113+
if len(stack) > 0 && len(result) > 0 && result[len(result)-1].Type() == ast.ListNode {
114+
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
115+
} else {
116+
result = append(result, node)
133117
}
118+
continue
119+
}
134120

135-
indent /= 2
136-
if prevResultNode == nil || prevResultNode.Type() != ast.ListNode || prevResultNode.(*ast.List).Kind != listKind || prevResultNode.(*ast.List).Indent > indent {
137-
prevResultNode = &ast.List{
138-
BaseBlock: ast.BaseBlock{},
139-
Kind: listKind,
140-
Indent: indent,
141-
Children: []ast.Node{nodes[i]},
121+
if ast.IsListItemNode(node) {
122+
itemKind, itemIndent := ast.GetListItemKindAndIndent(node)
123+
124+
// Create a new List node if the stack is empty or the current item should be a child of the last item.
125+
if len(stack) == 0 || (itemKind != stack[len(stack)-1].Kind || itemIndent > stack[len(stack)-1].Indent) {
126+
newList := &ast.List{
127+
Kind: itemKind,
128+
Indent: itemIndent,
129+
Children: []ast.Node{node},
142130
}
143-
result = append(result, prevResultNode)
144-
continue
145-
}
146131

147-
listNode, ok := prevResultNode.(*ast.List)
148-
if !ok {
149-
continue
150-
}
151-
if listNode.Indent != indent {
152-
parent := findListPossibleParent(listNode, indent)
153-
if parent == nil {
154-
parent = &ast.List{
155-
BaseBlock: ast.BaseBlock{},
156-
Kind: listKind,
157-
Indent: indent,
158-
}
159-
listNode.Children = append(listNode.Children, parent)
132+
// Add the new List node to the stack or the result.
133+
if len(stack) > 0 && itemIndent > stack[len(stack)-1].Indent {
134+
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, newList)
135+
} else {
136+
result = append(result, newList)
160137
}
161-
parent.Children = append(parent.Children, nodes[i])
138+
stack = append(stack, newList)
162139
} else {
163-
listNode.Children = append(listNode.Children, nodes[i])
164-
}
165-
case *ast.LineBreak:
166-
if prevResultNode != nil && prevResultNode.Type() == ast.ListNode &&
167-
// Check if the prev node is not a line break node.
168-
(prevNode == nil || prevNode.Type() != ast.LineBreakNode) &&
169-
// Check if the next node is a list item node.
170-
(nextNode == nil || slices.Contains([]ast.NodeType{ast.OrderedListItemNode, ast.UnorderedListItemNode, ast.TaskListItemNode}, nextNode.Type())) {
171-
prevResultNode.(*ast.List).Children = append(prevResultNode.(*ast.List).Children, nodes[i])
172-
} else {
173-
result = append(result, nodes[i])
140+
// Pop the stack until the current item should be a sibling of the last item.
141+
for len(stack) > 0 && (itemKind != stack[len(stack)-1].Kind || itemIndent < stack[len(stack)-1].Indent) {
142+
stack = stack[:len(stack)-1]
143+
}
144+
145+
// Add the current item to the last List node in the stack or the result.
146+
if len(stack) > 0 {
147+
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
148+
} else {
149+
result = append(result, node)
150+
}
174151
}
175-
default:
176-
result = append(result, nodes[i])
152+
} else {
153+
result = append(result, node)
177154
}
178155
}
156+
179157
return result
180158
}
181159

@@ -193,20 +171,3 @@ func mergeTextNodes(nodes []ast.Node) []ast.Node {
193171
}
194172
return result
195173
}
196-
197-
func findListPossibleParent(listNode *ast.List, indent int) *ast.List {
198-
if listNode.Indent == indent {
199-
return listNode
200-
}
201-
if listNode.Indent < indent {
202-
return nil
203-
}
204-
if len(listNode.Children) == 0 {
205-
return nil
206-
}
207-
lastChild := listNode.Children[len(listNode.Children)-1]
208-
if lastChild.Type() != ast.ListNode {
209-
return nil
210-
}
211-
return findListPossibleParent(lastChild.(*ast.List), indent)
212-
}

parser/tests/list_test.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestListParser(t *testing.T) {
6060
&ast.LineBreak{},
6161
&ast.List{
6262
Kind: ast.OrderedList,
63-
Indent: 1,
63+
Indent: 2,
6464
Children: []ast.Node{
6565
&ast.OrderedListItem{
6666
Number: "2",
@@ -78,7 +78,7 @@ func TestListParser(t *testing.T) {
7878
},
7979
},
8080
{
81-
text: "* hello\n * world\n* gomark",
81+
text: "* hello\n * world\n * gomark",
8282
nodes: []ast.Node{
8383
&ast.List{
8484
Kind: ast.UnorderedList,
@@ -94,7 +94,7 @@ func TestListParser(t *testing.T) {
9494
&ast.LineBreak{},
9595
&ast.List{
9696
Kind: ast.UnorderedList,
97-
Indent: 1,
97+
Indent: 2,
9898
Children: []ast.Node{
9999
&ast.UnorderedListItem{
100100
Symbol: "*",
@@ -105,9 +105,53 @@ func TestListParser(t *testing.T) {
105105
},
106106
},
107107
},
108+
&ast.LineBreak{},
109+
&ast.UnorderedListItem{
110+
Symbol: "*",
111+
Indent: 2,
112+
Children: []ast.Node{
113+
&ast.Text{
114+
Content: "gomark",
115+
},
116+
},
117+
},
118+
},
119+
},
120+
},
121+
},
122+
},
123+
},
124+
{
125+
text: "* hello\n * world\n* gomark",
126+
nodes: []ast.Node{
127+
&ast.List{
128+
Kind: ast.UnorderedList,
129+
Children: []ast.Node{
130+
&ast.UnorderedListItem{
131+
Symbol: "*",
132+
Children: []ast.Node{
133+
&ast.Text{
134+
Content: "hello",
135+
},
108136
},
109137
},
110138
&ast.LineBreak{},
139+
&ast.List{
140+
Kind: ast.UnorderedList,
141+
Indent: 2,
142+
Children: []ast.Node{
143+
&ast.UnorderedListItem{
144+
Symbol: "*",
145+
Indent: 2,
146+
Children: []ast.Node{
147+
&ast.Text{
148+
Content: "world",
149+
},
150+
},
151+
},
152+
&ast.LineBreak{},
153+
},
154+
},
111155
&ast.UnorderedListItem{
112156
Symbol: "*",
113157
Children: []ast.Node{
@@ -125,6 +169,6 @@ func TestListParser(t *testing.T) {
125169
for _, test := range tests {
126170
tokens := tokenizer.Tokenize(test.text)
127171
nodes, _ := parser.Parse(tokens)
128-
require.Equal(t, test.nodes, nodes, fmt.Sprintf("Test case: %s", test.text))
172+
require.ElementsMatch(t, test.nodes, nodes, fmt.Sprintf("Test case: %s", test.text))
129173
}
130174
}

parser/tests/parser_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,47 @@ func TestParser(t *testing.T) {
334334
},
335335
},
336336
},
337+
&ast.LineBreak{},
338+
&ast.LineBreak{},
339+
},
340+
},
341+
&ast.List{
342+
Kind: ast.OrderedList,
343+
Children: []ast.Node{
344+
&ast.OrderedListItem{
345+
Number: "1",
346+
Children: []ast.Node{
347+
&ast.Text{
348+
Content: "ordered list item",
349+
},
350+
},
351+
},
352+
},
353+
},
354+
},
355+
},
356+
{
357+
text: "* unordered list item\nparagraph\n\n1. ordered list item",
358+
nodes: []ast.Node{
359+
&ast.List{
360+
Kind: ast.UnorderedList,
361+
Children: []ast.Node{
362+
&ast.UnorderedListItem{
363+
Symbol: tokenizer.Asterisk,
364+
Children: []ast.Node{
365+
&ast.Text{
366+
Content: "unordered list item",
367+
},
368+
},
369+
},
370+
&ast.LineBreak{},
371+
},
372+
},
373+
&ast.Paragraph{
374+
Children: []ast.Node{
375+
&ast.Text{
376+
Content: "paragraph",
377+
},
337378
},
338379
},
339380
&ast.LineBreak{},

0 commit comments

Comments
 (0)