From b3c21a25e8857b3a8d81a9f6115b32d3dcbcc178 Mon Sep 17 00:00:00 2001 From: fo-od Date: Fri, 6 Feb 2026 22:31:45 -0800 Subject: [PATCH 01/11] make max heading level 3 --- DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift | 2 +- .../Sources/DiscordMarkdownParser/Tokenizer.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift index ba39bd54..fe223847 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift @@ -140,7 +140,7 @@ public enum AST { public let children: [ASTNode] public let sourceLocation: SourceLocation? - /// Heading level (1-6) + /// Heading level (1-3) public let level: Int public init( diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/Tokenizer.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/Tokenizer.swift index 2650069e..81b0ac48 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/Tokenizer.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/Tokenizer.swift @@ -559,7 +559,7 @@ public final class MarkdownTokenizer { let startLocation = currentLocation var content = "" - while !isAtEnd && currentChar == "#" && content.count < 6 { + while !isAtEnd && currentChar == "#" && content.count < 3 { content.append(currentChar) advance() } From f73e283e951729d6238ca401ee358a2f1e03ce18 Mon Sep 17 00:00:00 2001 From: fo-od Date: Sat, 7 Feb 2026 01:48:59 -0800 Subject: [PATCH 02/11] try adding ordered lists and indentation (code block bug still exists) --- .../Sources/DiscordMarkdownParser/AST.swift | 10 +++- .../DiscordMarkdownParser/BlockParser.swift | 23 ++++++--- .../DiscordMarkdownParser.swift | 4 ++ .../Message Body/Markdown/MarkdownText.swift | 50 +++++++++++++------ 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift index fe223847..781fc194 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift @@ -200,10 +200,18 @@ public enum AST { public let nodeType: ASTNodeType = .listItem public let children: [ASTNode] public let sourceLocation: SourceLocation? + + /// Indentation level + public let level: Int + + /// Number of the item in the ordered list + public let listNumber: Int? - public init(children: [ASTNode], sourceLocation: SourceLocation? = nil) { + public init(level: Int, itemNumber: Int? = nil, children: [ASTNode], sourceLocation: SourceLocation? = nil) { self.children = children self.sourceLocation = sourceLocation + self.level = level + self.listNumber = itemNumber } } diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift index 5e198404..45e6c8e8 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift @@ -432,18 +432,20 @@ public final class BlockParser { let startLocation = tokenStream.current.location let firstMarker = tokenStream.current.content - let isOrdered = firstMarker.last == "." || firstMarker.last == ")" + let isOrdered = firstMarker.last == "." let startNumber = isOrdered ? Int(firstMarker.dropLast()) : nil let delimiter = isOrdered ? firstMarker.last : nil let bulletChar = isOrdered ? nil : firstMarker.first + var itemNumber = startNumber ?? 1 var items: [ASTNode] = [] + var whitespaceCount = 0 while !tokenStream.isAtEnd && tokenStream.check(.listMarker) { let marker = tokenStream.current.content // Check if this marker matches the list type - let markerIsOrdered = marker.last == "." || marker.last == ")" + let markerIsOrdered = marker.last == "." if markerIsOrdered != isOrdered { break } @@ -459,10 +461,14 @@ public final class BlockParser { } // Parse list item - let item = try parseListItem() + let item = try parseListItem(whitespaceCount, itemNumber: itemNumber) items.append(item) + + if isOrdered { + itemNumber += 1 + } - skipWhitespaceAndNewlines() + whitespaceCount = skipWhitespaceAndNewlines() } return AST.ListNode( @@ -473,7 +479,7 @@ public final class BlockParser { ) } - private func parseListItem() throws -> AST.ListItemNode { + private func parseListItem(_ whitespaceCount: Int, itemNumber: Int? = nil) throws -> AST.ListItemNode { let startLocation = tokenStream.current.location // Consume list marker @@ -525,7 +531,7 @@ public final class BlockParser { } } - return AST.ListItemNode(children: children, sourceLocation: startLocation) + return AST.ListItemNode(level: whitespaceCount/2, itemNumber: itemNumber, children: children, sourceLocation: startLocation) } private func isNextListItem() -> Bool { @@ -859,9 +865,12 @@ public final class BlockParser { } } - private func skipWhitespaceAndNewlines() { + private func skipWhitespaceAndNewlines() -> Int { + var count: Int = 0 while tokenStream.check(.whitespace) || tokenStream.check(.newline) { + count += tokenStream.current.length tokenStream.advance() } + return count } } diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift index 26f7bbe7..f5fcf300 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift @@ -279,6 +279,8 @@ public final class DiscordMarkdownParser: Sendable { ) return AST.ListItemNode( + level: listItem.level, + itemNumber: listItem.listNumber, children: processedChildren, sourceLocation: listItem.sourceLocation ) @@ -480,6 +482,8 @@ public final class DiscordMarkdownParser: Sendable { case .listItem: if let listItemNode = originalNode as? AST.ListItemNode { return AST.ListItemNode( + level: listItemNode.level, + itemNumber: listItemNode.listNumber, children: children, sourceLocation: listItemNode.sourceLocation ) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 18303902..3d89a684 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -219,11 +219,8 @@ struct MarkdownText: View, Equatable { if let children = block.children { VStack(alignment: .leading, spacing: 4) { ForEach(children) { child in - HStack(alignment: .top, spacing: 8) { - Text(verbatim: "•").font(.body) - BlockView(block: child) - .equatable() - } + BlockView(block: child) + .equatable() } } } @@ -233,10 +230,23 @@ struct MarkdownText: View, Equatable { let converted = AttributedString(attr) Text(converted) } else if let children = block.children { + // all the numbers + let isOrdered = block.isOrdered ?? false + let number = block.itemNumber ?? -1 + let level = block.level ?? 0 + + // string stuff + let bullet = level > 0 ? "◦" : "•" // hollow circle if nested, filled if not + let indentation = String(repeating: "\t", count: level) + let start = isOrdered ? "\(number)." : bullet + VStack(alignment: .leading, spacing: 4) { ForEach(children) { nested in - BlockView(block: nested) - .equatable() + HStack(alignment: .top, spacing: 8) { + Text(verbatim: indentation + start).font(.body) + BlockView(block: nested) + .equatable() + } } } } else { @@ -330,6 +340,7 @@ private struct BlockElement: Identifiable, Equatable, Hashable { let attributedContent: NSAttributedString? let isOrdered: Bool? let startingNumber: Int? + let itemNumber: Int? let codeContent: String? let language: String? let level: Int? @@ -491,6 +502,7 @@ class MarkdownRendererVM { attributedContent: attributed, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -511,6 +523,7 @@ class MarkdownRendererVM { attributedContent: attributed, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: heading.level, @@ -531,6 +544,7 @@ class MarkdownRendererVM { attributedContent: attributed, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -546,6 +560,7 @@ class MarkdownRendererVM { attributedContent: nil, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: code.content, language: code.language, level: nil, @@ -568,6 +583,7 @@ class MarkdownRendererVM { attributedContent: nil, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -593,11 +609,12 @@ class MarkdownRendererVM { ), nodeType: .listItem, attributedContent: nil, - isOrdered: nil, - startingNumber: nil, + isOrdered: list.isOrdered, + startingNumber: list.startNumber, + itemNumber: listItem.listNumber, codeContent: nil, language: nil, - level: nil, + level: listItem.level, children: listItemChildren, sourceLocation: listItem.sourceLocation ) @@ -611,8 +628,9 @@ class MarkdownRendererVM { id: makeID(base: sourceID(for: item), content: attr.string), nodeType: .listItem, attributedContent: attr, - isOrdered: nil, - startingNumber: nil, + isOrdered: list.isOrdered, + startingNumber: list.startNumber, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -626,8 +644,9 @@ class MarkdownRendererVM { id: makeID(base: baseIDSeed, content: items.map(\.id).description), nodeType: .list, attributedContent: nil, - isOrdered: nil, - startingNumber: nil, + isOrdered: list.isOrdered, + startingNumber: list.startNumber, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -645,6 +664,7 @@ class MarkdownRendererVM { attributedContent: attr, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -664,6 +684,7 @@ class MarkdownRendererVM { attributedContent: attr, isOrdered: nil, startingNumber: nil, + itemNumber: nil, codeContent: nil, language: nil, level: nil, @@ -1680,3 +1701,4 @@ final class EmojiAttachmentViewProvider: NSTextAttachmentViewProvider { container = nil } } + From ec262b369397c2083c2a6029d7bca0527bc3dad0 Mon Sep 17 00:00:00 2001 From: fo-od Date: Wed, 11 Feb 2026 08:55:51 -0800 Subject: [PATCH 03/11] start nested list impl (is broken and has debug stuff in it) --- .../Sources/DiscordMarkdownParser/AST.swift | 13 +-- .../DiscordMarkdownParser/BlockParser.swift | 23 ++++- .../DiscordMarkdownParser.swift | 3 +- .../Message Body/Markdown/MarkdownText.swift | 97 ++++++++++++++++--- 4 files changed, 108 insertions(+), 28 deletions(-) diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift index 781fc194..26a63fb5 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/AST.swift @@ -177,6 +177,9 @@ public enum AST { /// Starting number for ordered lists public let startNumber: Int? + + /// Nesting level + public let level: Int /// List items public let items: [ASTNode] @@ -184,11 +187,13 @@ public enum AST { public init( isOrdered: Bool, startNumber: Int? = nil, + level: Int, items: [ASTNode], sourceLocation: SourceLocation? = nil ) { self.isOrdered = isOrdered self.startNumber = startNumber + self.level = level self.items = items self.sourceLocation = sourceLocation self.children = items @@ -201,17 +206,13 @@ public enum AST { public let children: [ASTNode] public let sourceLocation: SourceLocation? - /// Indentation level - public let level: Int - /// Number of the item in the ordered list public let listNumber: Int? - public init(level: Int, itemNumber: Int? = nil, children: [ASTNode], sourceLocation: SourceLocation? = nil) { + public init(itemNumber: Int? = nil, children: [ASTNode], sourceLocation: SourceLocation? = nil) { + self.listNumber = itemNumber self.children = children self.sourceLocation = sourceLocation - self.level = level - self.listNumber = itemNumber } } diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift index 45e6c8e8..9b79a8fd 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift @@ -428,7 +428,7 @@ public final class BlockParser { // MARK: - List Parsers - private func parseList() throws -> AST.ListNode { + private func parseList(_ indentationLevel: Int = 0) throws -> AST.ListNode { let startLocation = tokenStream.current.location let firstMarker = tokenStream.current.content @@ -442,6 +442,11 @@ public final class BlockParser { var whitespaceCount = 0 while !tokenStream.isAtEnd && tokenStream.check(.listMarker) { + let level = whitespaceCount / 2 + if whitespaceCount != 0 && level < indentationLevel { + print(tokenStream.current.content) + break + } let marker = tokenStream.current.content // Check if this marker matches the list type @@ -461,7 +466,12 @@ public final class BlockParser { } // Parse list item - let item = try parseListItem(whitespaceCount, itemNumber: itemNumber) + var item: ASTNode + if whitespaceCount > 0 { + item = try parseList(indentationLevel == level ? indentationLevel : indentationLevel + 1) + } else { + item = try parseListItem(itemNumber: itemNumber) + } items.append(item) if isOrdered { @@ -474,12 +484,13 @@ public final class BlockParser { return AST.ListNode( isOrdered: isOrdered, startNumber: startNumber, + level: indentationLevel, items: items, sourceLocation: startLocation ) } - private func parseListItem(_ whitespaceCount: Int, itemNumber: Int? = nil) throws -> AST.ListItemNode { + private func parseListItem(itemNumber: Int? = nil) throws -> AST.ListItemNode { let startLocation = tokenStream.current.location // Consume list marker @@ -531,7 +542,7 @@ public final class BlockParser { } } - return AST.ListItemNode(level: whitespaceCount/2, itemNumber: itemNumber, children: children, sourceLocation: startLocation) + return AST.ListItemNode(itemNumber: itemNumber, children: children, sourceLocation: startLocation) } private func isNextListItem() -> Bool { @@ -868,7 +879,9 @@ public final class BlockParser { private func skipWhitespaceAndNewlines() -> Int { var count: Int = 0 while tokenStream.check(.whitespace) || tokenStream.check(.newline) { - count += tokenStream.current.length + if tokenStream.check(.whitespace) { + count += tokenStream.current.length + } tokenStream.advance() } return count diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift index f5fcf300..80945876 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/DiscordMarkdownParser.swift @@ -263,6 +263,7 @@ public final class DiscordMarkdownParser: Sendable { return AST.ListNode( isOrdered: list.isOrdered, startNumber: list.startNumber, + level: list.level, items: processedItems, sourceLocation: list.sourceLocation ) @@ -279,7 +280,6 @@ public final class DiscordMarkdownParser: Sendable { ) return AST.ListItemNode( - level: listItem.level, itemNumber: listItem.listNumber, children: processedChildren, sourceLocation: listItem.sourceLocation @@ -482,7 +482,6 @@ public final class DiscordMarkdownParser: Sendable { case .listItem: if let listItemNode = originalNode as? AST.ListItemNode { return AST.ListItemNode( - level: listItemNode.level, itemNumber: listItemNode.listNumber, children: children, sourceLocation: listItemNode.sourceLocation diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 3d89a684..342aac25 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -219,8 +219,12 @@ struct MarkdownText: View, Equatable { if let children = block.children { VStack(alignment: .leading, spacing: 4) { ForEach(children) { child in - BlockView(block: child) - .equatable() + HStack(alignment: .top, spacing: 0) { +// Text(verbatim: String(repeating: "\t", count: block.level ?? 0)) + Text(verbatim: String(block.level ?? -1)) + BlockView(block: child) + .equatable() + } } } } @@ -230,20 +234,12 @@ struct MarkdownText: View, Equatable { let converted = AttributedString(attr) Text(converted) } else if let children = block.children { - // all the numbers - let isOrdered = block.isOrdered ?? false - let number = block.itemNumber ?? -1 - let level = block.level ?? 0 - - // string stuff - let bullet = level > 0 ? "◦" : "•" // hollow circle if nested, filled if not - let indentation = String(repeating: "\t", count: level) - let start = isOrdered ? "\(number)." : bullet + let level = block.level ?? -1 VStack(alignment: .leading, spacing: 4) { ForEach(children) { nested in HStack(alignment: .top, spacing: 8) { - Text(verbatim: indentation + start).font(.body) + Text(verbatim: block.itemNumber != nil ? "\(block.itemNumber ?? -1)." : level > 0 ? "◦" : "•").font(.body) BlockView(block: nested) .equatable() } @@ -614,7 +610,7 @@ class MarkdownRendererVM { itemNumber: listItem.listNumber, codeContent: nil, language: nil, - level: listItem.level, + level: list.level, children: listItemChildren, sourceLocation: listItem.sourceLocation ) @@ -633,7 +629,7 @@ class MarkdownRendererVM { itemNumber: nil, codeContent: nil, language: nil, - level: nil, + level: list.level, children: nil, sourceLocation: item.sourceLocation ) @@ -649,7 +645,7 @@ class MarkdownRendererVM { itemNumber: nil, codeContent: nil, language: nil, - level: nil, + level: list.level, children: items, sourceLocation: node.sourceLocation ) @@ -1702,3 +1698,74 @@ final class EmojiAttachmentViewProvider: NSTextAttachmentViewProvider { } } +#Preview { + let msg = """ + 1. Item 1 + 3. Item 2 (3.) + 5. Item 2a (5.) + 2. Item 2b (2.) + 7. Item 3 (7.) + """ + let user = DiscordUser( + id: .init("381538809180848128"), + username: "markdown test", + discriminator: "0", + global_name: nil, + avatar: "df71b3f223666fd8331c9940c6f7cbd9", + banner: nil, + bot: false, + system: false, + mfa_enabled: true, + accent_color: nil, + locale: .englishUS, + verified: true, + email: nil, + flags: .init(rawValue: 4_194_352), + premium_type: nil, + public_flags: .init(rawValue: 4_194_304), + avatar_decoration_data: nil + ) + MessageCell( + for: .init( + id: try! .makeFake(), + channel_id: try! .makeFake(), + author: user, + content: msg, + timestamp: .init(date: .now), + edited_timestamp: nil, + tts: false, + mention_everyone: false, + mentions: [], + mention_roles: [], + mention_channels: nil, + attachments: [], + embeds: [], + reactions: nil, + nonce: nil, + pinned: false, + webhook_id: nil, + type: DiscordChannel.Message.Kind.default, + activity: nil, + application: nil, + application_id: nil, + message_reference: nil, + flags: [], + referenced_message: nil, + interaction: nil, + thread: nil, + components: nil, + sticker_items: nil, + stickers: nil, + position: nil, + role_subscription_data: nil, + resolved: nil, + poll: nil, + call: nil, + guild_id: nil, + member: nil + ), + prior: nil, + channel: .init(id: try! .makeFake()) + ) +} + From 61b8997b66789b35cfc47dc4ea4e37227fd94967 Mon Sep 17 00:00:00 2001 From: fo-od Date: Fri, 13 Feb 2026 20:39:45 -0800 Subject: [PATCH 04/11] fix nested list parsing (rendering still broken) --- .../Sources/DiscordMarkdownParser/BlockParser.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift index 9b79a8fd..b8deafc9 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/BlockParser.swift @@ -436,11 +436,11 @@ public final class BlockParser { let startNumber = isOrdered ? Int(firstMarker.dropLast()) : nil let delimiter = isOrdered ? firstMarker.last : nil let bulletChar = isOrdered ? nil : firstMarker.first - var itemNumber = startNumber ?? 1 var items: [ASTNode] = [] var whitespaceCount = 0 + var itemNumber = startNumber while !tokenStream.isAtEnd && tokenStream.check(.listMarker) { let level = whitespaceCount / 2 if whitespaceCount != 0 && level < indentationLevel { @@ -467,15 +467,15 @@ public final class BlockParser { // Parse list item var item: ASTNode - if whitespaceCount > 0 { + if whitespaceCount > 0 && level != indentationLevel { item = try parseList(indentationLevel == level ? indentationLevel : indentationLevel + 1) } else { - item = try parseListItem(itemNumber: itemNumber) + item = try parseListItem(itemNumber) } items.append(item) if isOrdered { - itemNumber += 1 + itemNumber! += 1 } whitespaceCount = skipWhitespaceAndNewlines() @@ -490,7 +490,7 @@ public final class BlockParser { ) } - private func parseListItem(itemNumber: Int? = nil) throws -> AST.ListItemNode { + private func parseListItem(_ itemNumber: Int?) throws -> AST.ListItemNode { let startLocation = tokenStream.current.location // Consume list marker From 8ccb6075ec8393a7ae70489c1b8ab9c0d54c12c4 Mon Sep 17 00:00:00 2001 From: fo-od Date: Wed, 18 Feb 2026 11:26:57 -0800 Subject: [PATCH 05/11] remove ')' delimiter for ordered lists (discord only uses '.') --- .../Sources/DiscordMarkdownParser/CommonMarkExtensions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/CommonMarkExtensions.swift b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/CommonMarkExtensions.swift index 5f8ac0e0..f8407346 100644 --- a/DiscordMarkdownParser/Sources/DiscordMarkdownParser/CommonMarkExtensions.swift +++ b/DiscordMarkdownParser/Sources/DiscordMarkdownParser/CommonMarkExtensions.swift @@ -183,7 +183,7 @@ public enum CommonMarkUtils { // Must be followed by . or ) guard index < trimmed.endIndex else { return nil } let delimiter = trimmed[index] - guard delimiter == "." || delimiter == ")" else { return nil } + guard delimiter == "." else { return nil } index = trimmed.index(after: index) @@ -242,7 +242,7 @@ public struct ListMarkerInfo: Sendable, Equatable { /// The number for ordered lists public let number: Int? - /// The delimiter for ordered lists ('.' or ')') + /// The delimiter for ordered lists ('.') public let delimiter: Character? /// The width of the marker in characters From 3a5d9f43f806714876f749ed230b4201040b3471 Mon Sep 17 00:00:00 2001 From: fo-od Date: Wed, 18 Feb 2026 18:28:41 -0800 Subject: [PATCH 06/11] remove debug stuff --- .../Message Body/Markdown/MarkdownText.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 342aac25..a544132d 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -219,12 +219,8 @@ struct MarkdownText: View, Equatable { if let children = block.children { VStack(alignment: .leading, spacing: 4) { ForEach(children) { child in - HStack(alignment: .top, spacing: 0) { -// Text(verbatim: String(repeating: "\t", count: block.level ?? 0)) - Text(verbatim: String(block.level ?? -1)) - BlockView(block: child) - .equatable() - } + BlockView(block: child) + .equatable() } } } @@ -235,11 +231,13 @@ struct MarkdownText: View, Equatable { Text(converted) } else if let children = block.children { let level = block.level ?? -1 + let number = block.itemNumber ?? -1 + let ordered = block.isOrdered ?? false VStack(alignment: .leading, spacing: 4) { ForEach(children) { nested in HStack(alignment: .top, spacing: 8) { - Text(verbatim: block.itemNumber != nil ? "\(block.itemNumber ?? -1)." : level > 0 ? "◦" : "•").font(.body) + Text(verbatim: ordered ? "\(number)." : level > 0 ? "◦" : "•").font(.body) BlockView(block: nested) .equatable() } From 20c8f5805ade105001ca1ebf5a3a7538b1ea0284 Mon Sep 17 00:00:00 2001 From: fo-od Date: Wed, 18 Feb 2026 18:57:44 -0800 Subject: [PATCH 07/11] fix nested list parsing yay --- .../Message Body/Markdown/MarkdownText.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index a544132d..32e86896 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -613,6 +613,30 @@ class MarkdownRendererVM { sourceLocation: listItem.sourceLocation ) items.append(itemBlock) + } else if let nestedList = item as? AST.ListNode { + var nestedListChildren: [BlockElement] = [] + for c in nestedList.children { + if let blockChild = makeBlock(from: c) { + nestedListChildren.append(blockChild) + } + } + let itemBlock = BlockElement( + id: makeID( + base: sourceID(for: nestedList), + content: nestedListChildren.map(\.id).description + ), + nodeType: .list, + attributedContent: nil, + isOrdered: nestedList.isOrdered, + startingNumber: nestedList.startNumber, + itemNumber: nil, + codeContent: nil, + language: nil, + level: nestedList.level, + children: nestedListChildren, + sourceLocation: nestedList.sourceLocation + ) + items.append(itemBlock) } else { let attr = renderInlinesToNSAttributedString( nodes: item.children, From 73c476a6c89cb44c463c926e99f5f3e5081ca6cb Mon Sep 17 00:00:00 2001 From: fo-od Date: Wed, 18 Feb 2026 19:06:31 -0800 Subject: [PATCH 08/11] add indentation for nested lists --- .../Chat/Messages/Message Body/Markdown/MarkdownText.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 32e86896..30864dca 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -217,10 +217,13 @@ struct MarkdownText: View, Equatable { case .list: if let children = block.children { + HStack(alignment: .top, spacing: 0) { + Text(verbatim: String(repeating: "\t", count: block.level ?? 0)) VStack(alignment: .leading, spacing: 4) { ForEach(children) { child in BlockView(block: child) .equatable() + } } } } From 44a7f28a7e6fa0c0049efeaac8eb926da1fc2ca1 Mon Sep 17 00:00:00 2001 From: fo-od Date: Mon, 23 Feb 2026 08:56:18 -0800 Subject: [PATCH 09/11] how do i do this --- .../Message Body/Markdown/MarkdownText.swift | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 30864dca..1aaeaa8c 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -617,17 +617,55 @@ class MarkdownRendererVM { ) items.append(itemBlock) } else if let nestedList = item as? AST.ListNode { - var nestedListChildren: [BlockElement] = [] - for c in nestedList.children { - if let blockChild = makeBlock(from: c) { - nestedListChildren.append(blockChild) + var nestedItems: [BlockElement] = [] + for item in nestedList.items { + if let listItem = item as? AST.ListItemNode { + var nestedListItemChildren: [BlockElement] = [] + for c in listItem.children { + if let blockChild = makeBlock(from: c) { + nestedListItemChildren.append(blockChild) + } + } + let itemBlock = BlockElement( + id: makeID( + base: sourceID(for: listItem), + content: nestedListItemChildren.map(\.id).description + ), + nodeType: .listItem, + attributedContent: nil, + isOrdered: nestedList.isOrdered, + startingNumber: nestedList.startNumber, + itemNumber: listItem.listNumber, + codeContent: nil, + language: nil, + level: nestedList.level, + children: nestedListItemChildren, + sourceLocation: listItem.sourceLocation + ) + nestedItems.append(itemBlock) + } else { + let attr = renderInlinesToNSAttributedString( + nodes: item.children, + baseStyle: .body + ) + let itemBlock = BlockElement( + id: makeID(base: sourceID(for: item), content: attr.string), + nodeType: .listItem, + attributedContent: attr, + isOrdered: nestedList.isOrdered, + startingNumber: nestedList.startNumber, + itemNumber: nil, + codeContent: nil, + language: nil, + level: nestedList.level, + children: nil, + sourceLocation: item.sourceLocation + ) + nestedItems.append(itemBlock) } } - let itemBlock = BlockElement( - id: makeID( - base: sourceID(for: nestedList), - content: nestedListChildren.map(\.id).description - ), + let nestedListBlock = BlockElement( + id: makeID(base: baseIDSeed, content: nestedItems.map(\.id).description), nodeType: .list, attributedContent: nil, isOrdered: nestedList.isOrdered, @@ -636,10 +674,10 @@ class MarkdownRendererVM { codeContent: nil, language: nil, level: nestedList.level, - children: nestedListChildren, - sourceLocation: nestedList.sourceLocation + children: nestedItems, + sourceLocation: node.sourceLocation ) - items.append(itemBlock) + items.append(nestedListBlock) } else { let attr = renderInlinesToNSAttributedString( nodes: item.children, From e20b3cd4e9a6e12352e6bee01ea4feb5ec8389b6 Mon Sep 17 00:00:00 2001 From: fo-od Date: Mon, 23 Feb 2026 09:00:57 -0800 Subject: [PATCH 10/11] ok nvm its like this --- .../Message Body/Markdown/MarkdownText.swift | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index 1aaeaa8c..c99c7894 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -617,67 +617,9 @@ class MarkdownRendererVM { ) items.append(itemBlock) } else if let nestedList = item as? AST.ListNode { - var nestedItems: [BlockElement] = [] - for item in nestedList.items { - if let listItem = item as? AST.ListItemNode { - var nestedListItemChildren: [BlockElement] = [] - for c in listItem.children { - if let blockChild = makeBlock(from: c) { - nestedListItemChildren.append(blockChild) - } - } - let itemBlock = BlockElement( - id: makeID( - base: sourceID(for: listItem), - content: nestedListItemChildren.map(\.id).description - ), - nodeType: .listItem, - attributedContent: nil, - isOrdered: nestedList.isOrdered, - startingNumber: nestedList.startNumber, - itemNumber: listItem.listNumber, - codeContent: nil, - language: nil, - level: nestedList.level, - children: nestedListItemChildren, - sourceLocation: listItem.sourceLocation - ) - nestedItems.append(itemBlock) - } else { - let attr = renderInlinesToNSAttributedString( - nodes: item.children, - baseStyle: .body - ) - let itemBlock = BlockElement( - id: makeID(base: sourceID(for: item), content: attr.string), - nodeType: .listItem, - attributedContent: attr, - isOrdered: nestedList.isOrdered, - startingNumber: nestedList.startNumber, - itemNumber: nil, - codeContent: nil, - language: nil, - level: nestedList.level, - children: nil, - sourceLocation: item.sourceLocation - ) - nestedItems.append(itemBlock) - } + if let nestedListBlock = makeBlock(from: nestedList) { + items.append(nestedListBlock) } - let nestedListBlock = BlockElement( - id: makeID(base: baseIDSeed, content: nestedItems.map(\.id).description), - nodeType: .list, - attributedContent: nil, - isOrdered: nestedList.isOrdered, - startingNumber: nestedList.startNumber, - itemNumber: nil, - codeContent: nil, - language: nil, - level: nestedList.level, - children: nestedItems, - sourceLocation: node.sourceLocation - ) - items.append(nestedListBlock) } else { let attr = renderInlinesToNSAttributedString( nodes: item.children, From 8d4a1d4b210afbf3da962f2d181675f9e30b869a Mon Sep 17 00:00:00 2001 From: fo-od Date: Sat, 28 Feb 2026 10:30:20 -0800 Subject: [PATCH 11/11] ok its not super laggy anymore :) --- .../Messages/Message Body/Markdown/MarkdownText.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift index c99c7894..90f34464 100644 --- a/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift +++ b/Paicord/Common/Chat/Messages/Message Body/Markdown/MarkdownText.swift @@ -217,15 +217,12 @@ struct MarkdownText: View, Equatable { case .list: if let children = block.children { - HStack(alignment: .top, spacing: 0) { - Text(verbatim: String(repeating: "\t", count: block.level ?? 0)) VStack(alignment: .leading, spacing: 4) { ForEach(children) { child in BlockView(block: child) .equatable() - } } - } + }.padding(.leading, CGFloat(block.level ?? 0) * AppKitOrUIKitFont.labelFontSize * 2) } case .listItem: @@ -233,9 +230,9 @@ struct MarkdownText: View, Equatable { let converted = AttributedString(attr) Text(converted) } else if let children = block.children { - let level = block.level ?? -1 - let number = block.itemNumber ?? -1 + let level = block.level ?? 0 let ordered = block.isOrdered ?? false + let number = block.itemNumber ?? 1 VStack(alignment: .leading, spacing: 4) { ForEach(children) { nested in