Skip to content

Commit

Permalink
chore: update merge list items
Browse files Browse the repository at this point in the history
  • Loading branch information
boojack committed Sep 21, 2024
1 parent 9640de1 commit c9d3461
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 90 deletions.
23 changes: 23 additions & 0 deletions ast/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ast

import "slices"

func IsListItemNode(node Node) bool {
nodeType := node.Type()
return slices.Contains([]NodeType{
OrderedListItemNode, UnorderedListItemNode, TaskListItemNode,
}, nodeType)
}

func GetListItemKindAndIndent(node Node) (ListKind, int) {
switch n := node.(type) {
case *OrderedListItem:
return OrderedList, n.Indent
case *UnorderedListItem:
return UnorderedList, n.Indent
case *TaskListItem:
return DescrpitionList, n.Indent
default:
return "", 0
}
}
133 changes: 47 additions & 86 deletions parser/parser.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package parser

import (
"slices"

"github.com/usememos/gomark/ast"
"github.com/usememos/gomark/parser/tokenizer"
)
Expand Down Expand Up @@ -55,7 +53,9 @@ func ParseBlockWithParsers(tokens []*tokenizer.Token, blockParsers []BlockParser
}
}
}
return mergeListItemNodes(nodes), nil

nodes = mergeListItemNodes(nodes)
return nodes, nil
}

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

func mergeListItemNodes(nodes []ast.Node) []ast.Node {
if len(nodes) == 0 {
return nodes
}
result := []ast.Node{}
for i := 0; i < len(nodes); i++ {
var prevNode, nextNode, prevResultNode ast.Node
if i > 0 {
prevNode = nodes[i-1]
}
if i < len(nodes)-1 {
nextNode = nodes[i+1]
}
if len(result) > 0 {
prevResultNode = result[len(result)-1]
}
switch nodes[i].(type) {
case *ast.OrderedListItem, *ast.UnorderedListItem, *ast.TaskListItem:
var listKind ast.ListKind
var indent int
switch item := nodes[i].(type) {
case *ast.OrderedListItem:
listKind = ast.OrderedList
indent = item.Indent
case *ast.UnorderedListItem:
listKind = ast.UnorderedList
indent = item.Indent
case *ast.TaskListItem:
listKind = ast.DescrpitionList
indent = item.Indent
var result []ast.Node
var stack []*ast.List

for _, node := range nodes {
nodeType := node.Type()

// Handle line breaks.
if nodeType == ast.LineBreakNode {
// If the stack is not empty and the last node is a list node, add the line break to the list.
if len(stack) > 0 && len(result) > 0 && result[len(result)-1].Type() == ast.ListNode {
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
} else {
result = append(result, node)
}
continue
}

indent /= 2
if prevResultNode == nil || prevResultNode.Type() != ast.ListNode || prevResultNode.(*ast.List).Kind != listKind || prevResultNode.(*ast.List).Indent > indent {
prevResultNode = &ast.List{
BaseBlock: ast.BaseBlock{},
Kind: listKind,
Indent: indent,
Children: []ast.Node{nodes[i]},
if ast.IsListItemNode(node) {
itemKind, itemIndent := ast.GetListItemKindAndIndent(node)

// Create a new List node if the stack is empty or the current item should be a child of the last item.
if len(stack) == 0 || (itemKind != stack[len(stack)-1].Kind || itemIndent > stack[len(stack)-1].Indent) {
newList := &ast.List{
Kind: itemKind,
Indent: itemIndent,
Children: []ast.Node{node},
}
result = append(result, prevResultNode)
continue
}

listNode, ok := prevResultNode.(*ast.List)
if !ok {
continue
}
if listNode.Indent != indent {
parent := findListPossibleParent(listNode, indent)
if parent == nil {
parent = &ast.List{
BaseBlock: ast.BaseBlock{},
Kind: listKind,
Indent: indent,
}
listNode.Children = append(listNode.Children, parent)
// Add the new List node to the stack or the result.
if len(stack) > 0 && itemIndent > stack[len(stack)-1].Indent {
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, newList)
} else {
result = append(result, newList)
}
parent.Children = append(parent.Children, nodes[i])
stack = append(stack, newList)
} else {
listNode.Children = append(listNode.Children, nodes[i])
}
case *ast.LineBreak:
if prevResultNode != nil && prevResultNode.Type() == ast.ListNode &&
// Check if the prev node is not a line break node.
(prevNode == nil || prevNode.Type() != ast.LineBreakNode) &&
// Check if the next node is a list item node.
(nextNode == nil || slices.Contains([]ast.NodeType{ast.OrderedListItemNode, ast.UnorderedListItemNode, ast.TaskListItemNode}, nextNode.Type())) {
prevResultNode.(*ast.List).Children = append(prevResultNode.(*ast.List).Children, nodes[i])
} else {
result = append(result, nodes[i])
// Pop the stack until the current item should be a sibling of the last item.
for len(stack) > 0 && (itemKind != stack[len(stack)-1].Kind || itemIndent < stack[len(stack)-1].Indent) {
stack = stack[:len(stack)-1]
}

// Add the current item to the last List node in the stack or the result.
if len(stack) > 0 {
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
} else {
result = append(result, node)
}
}
default:
result = append(result, nodes[i])
} else {
result = append(result, node)
}
}

return result
}

Expand All @@ -193,20 +171,3 @@ func mergeTextNodes(nodes []ast.Node) []ast.Node {
}
return result
}

func findListPossibleParent(listNode *ast.List, indent int) *ast.List {
if listNode.Indent == indent {
return listNode
}
if listNode.Indent < indent {
return nil
}
if len(listNode.Children) == 0 {
return nil
}
lastChild := listNode.Children[len(listNode.Children)-1]
if lastChild.Type() != ast.ListNode {
return nil
}
return findListPossibleParent(lastChild.(*ast.List), indent)
}
52 changes: 48 additions & 4 deletions parser/tests/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestListParser(t *testing.T) {
&ast.LineBreak{},
&ast.List{
Kind: ast.OrderedList,
Indent: 1,
Indent: 2,
Children: []ast.Node{
&ast.OrderedListItem{
Number: "2",
Expand All @@ -78,7 +78,7 @@ func TestListParser(t *testing.T) {
},
},
{
text: "* hello\n * world\n* gomark",
text: "* hello\n * world\n * gomark",
nodes: []ast.Node{
&ast.List{
Kind: ast.UnorderedList,
Expand All @@ -94,7 +94,7 @@ func TestListParser(t *testing.T) {
&ast.LineBreak{},
&ast.List{
Kind: ast.UnorderedList,
Indent: 1,
Indent: 2,
Children: []ast.Node{
&ast.UnorderedListItem{
Symbol: "*",
Expand All @@ -105,9 +105,53 @@ func TestListParser(t *testing.T) {
},
},
},
&ast.LineBreak{},
&ast.UnorderedListItem{
Symbol: "*",
Indent: 2,
Children: []ast.Node{
&ast.Text{
Content: "gomark",
},
},
},
},
},
},
},
},
},
{
text: "* hello\n * world\n* gomark",
nodes: []ast.Node{
&ast.List{
Kind: ast.UnorderedList,
Children: []ast.Node{
&ast.UnorderedListItem{
Symbol: "*",
Children: []ast.Node{
&ast.Text{
Content: "hello",
},
},
},
&ast.LineBreak{},
&ast.List{
Kind: ast.UnorderedList,
Indent: 2,
Children: []ast.Node{
&ast.UnorderedListItem{
Symbol: "*",
Indent: 2,
Children: []ast.Node{
&ast.Text{
Content: "world",
},
},
},
&ast.LineBreak{},
},
},
&ast.UnorderedListItem{
Symbol: "*",
Children: []ast.Node{
Expand All @@ -125,6 +169,6 @@ func TestListParser(t *testing.T) {
for _, test := range tests {
tokens := tokenizer.Tokenize(test.text)
nodes, _ := parser.Parse(tokens)
require.Equal(t, test.nodes, nodes, fmt.Sprintf("Test case: %s", test.text))
require.ElementsMatch(t, test.nodes, nodes, fmt.Sprintf("Test case: %s", test.text))
}
}
41 changes: 41 additions & 0 deletions parser/tests/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,47 @@ func TestParser(t *testing.T) {
},
},
},
&ast.LineBreak{},
&ast.LineBreak{},
},
},
&ast.List{
Kind: ast.OrderedList,
Children: []ast.Node{
&ast.OrderedListItem{
Number: "1",
Children: []ast.Node{
&ast.Text{
Content: "ordered list item",
},
},
},
},
},
},
},
{
text: "* unordered list item\nparagraph\n\n1. ordered list item",
nodes: []ast.Node{
&ast.List{
Kind: ast.UnorderedList,
Children: []ast.Node{
&ast.UnorderedListItem{
Symbol: tokenizer.Asterisk,
Children: []ast.Node{
&ast.Text{
Content: "unordered list item",
},
},
},
&ast.LineBreak{},
},
},
&ast.Paragraph{
Children: []ast.Node{
&ast.Text{
Content: "paragraph",
},
},
},
&ast.LineBreak{},
Expand Down

0 comments on commit c9d3461

Please sign in to comment.