mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-08-29 07:27:16 +00:00
fix(formatting): mirror the overlap resolution in ChatMessageFormatter
ChatMessageFormatter.formatMessageAsText -- the production chat-row rendering path (TextMessageView -> ChatViewModel.formatMessageAsText) -- has its own hand-copied matcher with the same gap as MessageFormattingEngine: url and cashu (and lightning/bolt11/lnurl) are never cross-checked against each other, only against mentions. Here the corruption shows differently: this formatter renders cashu and lightning-family matches as a single spacer character rather than their matched text, so a URL whose path embeds a "cashuA..."-shaped token gets an extra, spurious space injected right after the fully-rendered URL instead of duplicated text. Same fix as MessageFormattingEngine: sort by start position, then greedily keep only non-overlapping matches. I could not add an equivalent regression test here: unlike MessageFormattingEngine (formatted through a small, mockable MessageFormattingContext protocol), ChatMessageFormatter has no existing test target and is constructed against a live ChatViewModel with a large dependency graph (mesh service, identity bridge, active channel, etc.). I don't have a Swift toolchain available to build a new test harness for it with confidence, so I've left that to whoever reviews this with the ChatViewModel construction context in hand -- happy to add one if pointed at an existing lightweight construction path I've missed.
This commit is contained in:
parent
216ab73d20
commit
7ba358ce46
@ -190,11 +190,29 @@ final class ChatMessageFormatter {
|
||||
}
|
||||
allMatches.sort { $0.range.location < $1.range.location }
|
||||
|
||||
// Drop any match that overlaps one already kept. The
|
||||
// per-type checks above only guard specific known pairs
|
||||
// (e.g. bolt11/lnurl against url+lightning); they don't
|
||||
// cover every combination, so an ordinary message can still
|
||||
// produce overlapping matches of two OTHER types (e.g. a URL
|
||||
// whose path embeds a "cashuA..."-shaped token). The render
|
||||
// loop below assumes non-overlapping, strictly-increasing
|
||||
// ranges; without this pass a nested cashu/lightning match
|
||||
// renders as an extra spacer character injected into
|
||||
// already-shown text instead of being skipped.
|
||||
var resolvedMatches: [(range: NSRange, type: String)] = []
|
||||
var occupiedUntil = 0
|
||||
for match in allMatches {
|
||||
guard match.range.location >= occupiedUntil else { continue }
|
||||
resolvedMatches.append(match)
|
||||
occupiedUntil = match.range.location + match.range.length
|
||||
}
|
||||
|
||||
var lastEnd = content.startIndex
|
||||
let myNickname = viewModel.nickname.normalizedNickname
|
||||
let isMentioned = message.mentions?.contains { $0.normalizedNickname == myNickname } ?? false
|
||||
|
||||
for (range, type) in allMatches {
|
||||
for (range, type) in resolvedMatches {
|
||||
guard let swiftRange = Range(range, in: content) else { continue }
|
||||
|
||||
if lastEnd < swiftRange.lowerBound {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user