@@ -73,10 +73,16 @@ object SpannedFormatter {
7373 formatArgs : Array <out Any >
7474 ): SpannableStringBuilder {
7575 val state = FormatState (locale, formatArgs)
76-
77- FORMAT_REGEX .findAll(this , 0 ).forEach { match ->
78- replace(state, match)
79- }
76+ var startIndex = 0
77+
78+ // Manually iterate over the matches for 2 reasons:
79+ // 1. The `FORMAT_REGEX.findAll()` wasn't correctly capturing all cases (e.g. %1$d%% only caught the first match)
80+ // 2. Use the `startIndex` so that a replacement of another format doesn't work (e.g. %d -> %d) since that would cause a cycle
81+ do {
82+ val match = FORMAT_REGEX .find(this , startIndex)?.also {
83+ startIndex = replace(state, it)
84+ }
85+ } while (match != null )
8086
8187 return this
8288 }
@@ -87,12 +93,14 @@ object SpannedFormatter {
8793 *
8894 * @param state The [FormatState] to use for replacing the format placeholder from [match]
8995 * @param match The [MatchResult] holding a match from the [FORMAT_REGEX] which should be replaced
96+ *
97+ * @return The end index of the replacement
9098 */
9199 @Suppress(" MoveVariableDeclarationIntoWhen" )
92100 private fun SpannableStringBuilder.replace (
93101 state : FormatState ,
94102 match : MatchResult
95- ) {
103+ ): Int {
96104 val argIndex = match.groupValues[1 ]
97105 val format = match.groupValues[2 ]
98106 val conversion = match.groupValues[3 ]
@@ -104,6 +112,7 @@ object SpannedFormatter {
104112 }
105113
106114 replace(match.range.first, match.range.last + 1 , replacementValue)
115+ return match.range.first + replacementValue.length
107116 }
108117
109118 /* *
0 commit comments