fix: apply addStyle after all text mutations in OutputTransformation
TextFieldBuffer.addStyle() positions are not adjusted by subsequent replace() calls. When multiple mentions had different-length display names, styles for later mentions became misaligned. Split into two phases: all replace() calls first, then all addStyle() calls with cumulative-shift-corrected positions. https://claude.ai/code/session_01SSsxsfJLbRiesBBhQFEVUd
This commit is contained in:
+16
-7
@@ -41,6 +41,11 @@ class UrlUserTagOutputTransformation(
|
|||||||
val mentionRegex = Regex("(?:@|nostr:)(?:npub1[a-z0-9]{58}|nprofile1[a-z0-9]+)")
|
val mentionRegex = Regex("(?:@|nostr:)(?:npub1[a-z0-9]{58}|nprofile1[a-z0-9]+)")
|
||||||
val matches = mentionRegex.findAll(text).toList().reversed()
|
val matches = mentionRegex.findAll(text).toList().reversed()
|
||||||
|
|
||||||
|
// Phase 1: Replace all mentions (reverse order keeps indices valid for replace).
|
||||||
|
// Collect replacement info because addStyle must be called after all text mutations.
|
||||||
|
// (originalStart, originalMatchLength, displayNameLength)
|
||||||
|
val replacements = mutableListOf<Triple<Int, Int, Int>>()
|
||||||
|
|
||||||
for (match in matches) {
|
for (match in matches) {
|
||||||
try {
|
try {
|
||||||
val bech32 =
|
val bech32 =
|
||||||
@@ -52,18 +57,22 @@ class UrlUserTagOutputTransformation(
|
|||||||
val displayName = "@${user.toBestDisplayName()}"
|
val displayName = "@${user.toBestDisplayName()}"
|
||||||
|
|
||||||
replace(match.range.first, match.range.last + 1, displayName)
|
replace(match.range.first, match.range.last + 1, displayName)
|
||||||
|
replacements.add(Triple(match.range.first, match.range.last + 1 - match.range.first, displayName.length))
|
||||||
// Apply color styling to the replaced display name
|
|
||||||
addStyle(
|
|
||||||
SpanStyle(color = color, textDecoration = TextDecoration.None),
|
|
||||||
match.range.first,
|
|
||||||
match.range.first + displayName.length,
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 2: Apply styles after all text mutations are finalized.
|
||||||
|
// Iterate in forward document order, tracking cumulative shift from prior replacements.
|
||||||
|
val style = SpanStyle(color = color, textDecoration = TextDecoration.None)
|
||||||
|
var cumulativeShift = 0
|
||||||
|
for ((originalStart, originalLen, newLen) in replacements.reversed()) {
|
||||||
|
val adjustedStart = originalStart + cumulativeShift
|
||||||
|
addStyle(style, adjustedStart, adjustedStart + newLen)
|
||||||
|
cumulativeShift += newLen - originalLen
|
||||||
|
}
|
||||||
|
|
||||||
// Highlight URLs in remaining text
|
// Highlight URLs in remaining text
|
||||||
highlightUrls(color)
|
highlightUrls(color)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user