Loading core/java/android/text/SpannableStringBuilder.java +35 −17 Original line number Original line Diff line number Diff line Loading @@ -130,14 +130,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private void resizeFor(int size) { private void resizeFor(int size) { final int oldLength = mText.length; final int oldLength = mText.length; final int newLength = ArrayUtils.idealCharArraySize(size + 1); final int newLength = ArrayUtils.idealCharArraySize(size + 1); final int after = oldLength - (mGapStart + mGapLength); final int delta = newLength - oldLength; if (delta == 0) return; char[] newText = new char[newLength]; char[] newText = new char[newLength]; System.arraycopy(mText, 0, newText, 0, mGapStart); System.arraycopy(mText, 0, newText, 0, mGapStart); final int after = oldLength - (mGapStart + mGapLength); System.arraycopy(mText, oldLength - after, newText, newLength - after, after); System.arraycopy(mText, oldLength - after, newText, newLength - after, after); mText = newText; mText = newText; final int delta = newLength - oldLength; mGapLength += delta; mGapLength += delta; if (mGapLength < 1) if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); new Exception("mGapLength < 1").printStackTrace(); Loading Loading @@ -305,6 +306,26 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable resizeFor(mText.length + nbNewChars - mGapLength); resizeFor(mText.length + nbNewChars - mGapLength); } } // The removal pass needs to be done before the gap is updated in order to broadcast the // correct previous positions to the correct intersecting SpanWatchers if (end > start) { // no need for span fixup on pure insertion // A for loop will not work because the array is being modified // Do not iterate in reverse to keep the SpanWatchers notified in ordering // Also, a removed SpanWatcher should not get notified of removed spans located // further in the span array. int i = 0; while (i < mSpanCount) { if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength && mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { removeSpan(i); } else { i++; } } } mGapStart += nbNewChars; mGapStart += nbNewChars; mGapLength -= nbNewChars; mGapLength -= nbNewChars; Loading @@ -313,11 +334,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable TextUtils.getChars(cs, csStart, csEnd, mText, start); TextUtils.getChars(cs, csStart, csEnd, mText, start); if (end > start) { if (end > start) { // no need for span fixup on pure insertion // no need for span fixup on pure insertion final boolean atEnd = (mGapStart + mGapLength == mText.length); boolean atEnd = (mGapStart + mGapLength == mText.length); for (int i = mSpanCount - 1; i >= 0; i--) { for (int i = 0; i < mSpanCount; i++) { if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; Loading @@ -331,16 +351,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & END_MASK); int flag = (mSpanFlags[i] & END_MASK); if (flag == POINT || (flag == PARAGRAPH && atEnd)) if (flag == POINT || (flag == PARAGRAPH && atEnd)) { mSpanEnds[i] = mGapStart + mGapLength; mSpanEnds[i] = mGapStart + mGapLength; else } else { mSpanEnds[i] = start; mSpanEnds[i] = start; } } // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE, which are POINT_MARK and could // get their boundaries swapped by the above code if (mSpanEnds[i] < mSpanStarts[i]) { removeSpan(i); } } } } } } Loading @@ -359,7 +374,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (en > csEnd) en = csEnd; if (en > csEnd) en = csEnd; // Add span only if this object is not yet used as a span in this string // Add span only if this object is not yet used as a span in this string if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) { if (getSpanStart(spans[i]) < 0) { setSpan(false, spans[i], st - csStart + start, en - csStart + start, setSpan(false, spans[i], st - csStart + start, en - csStart + start, sp.getSpanFlags(spans[i])); sp.getSpanFlags(spans[i])); } } Loading Loading @@ -465,6 +480,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable int newReplaceEnd = replaceEnd + nbNewChars; int newReplaceEnd = replaceEnd + nbNewChars; boolean spanChanged = false; boolean spanChanged = false; int previousSpanStart = spanStart; int previousSpanStart = spanStart; if (spanStart > newReplaceEnd) { if (spanStart > newReplaceEnd) { if (nbNewChars != 0) { if (nbNewChars != 0) { Loading @@ -477,11 +493,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && (spanStart != newReplaceEnd || (spanStart != newReplaceEnd || ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { // TODO previousSpanStart is incorrect, but we would need to save all the // TODO A correct previousSpanStart cannot be computed at this point. // previous spans' positions before replace to provide it // It would require to save all the previous spans' positions before the replace // Using an invalid -1 value to convey this would break the broacast range spanChanged = true; spanChanged = true; } } } } int previousSpanEnd = spanEnd; int previousSpanEnd = spanEnd; if (spanEnd > newReplaceEnd) { if (spanEnd > newReplaceEnd) { if (nbNewChars != 0) { if (nbNewChars != 0) { Loading Loading
core/java/android/text/SpannableStringBuilder.java +35 −17 Original line number Original line Diff line number Diff line Loading @@ -130,14 +130,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private void resizeFor(int size) { private void resizeFor(int size) { final int oldLength = mText.length; final int oldLength = mText.length; final int newLength = ArrayUtils.idealCharArraySize(size + 1); final int newLength = ArrayUtils.idealCharArraySize(size + 1); final int after = oldLength - (mGapStart + mGapLength); final int delta = newLength - oldLength; if (delta == 0) return; char[] newText = new char[newLength]; char[] newText = new char[newLength]; System.arraycopy(mText, 0, newText, 0, mGapStart); System.arraycopy(mText, 0, newText, 0, mGapStart); final int after = oldLength - (mGapStart + mGapLength); System.arraycopy(mText, oldLength - after, newText, newLength - after, after); System.arraycopy(mText, oldLength - after, newText, newLength - after, after); mText = newText; mText = newText; final int delta = newLength - oldLength; mGapLength += delta; mGapLength += delta; if (mGapLength < 1) if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); new Exception("mGapLength < 1").printStackTrace(); Loading Loading @@ -305,6 +306,26 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable resizeFor(mText.length + nbNewChars - mGapLength); resizeFor(mText.length + nbNewChars - mGapLength); } } // The removal pass needs to be done before the gap is updated in order to broadcast the // correct previous positions to the correct intersecting SpanWatchers if (end > start) { // no need for span fixup on pure insertion // A for loop will not work because the array is being modified // Do not iterate in reverse to keep the SpanWatchers notified in ordering // Also, a removed SpanWatcher should not get notified of removed spans located // further in the span array. int i = 0; while (i < mSpanCount) { if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength && mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { removeSpan(i); } else { i++; } } } mGapStart += nbNewChars; mGapStart += nbNewChars; mGapLength -= nbNewChars; mGapLength -= nbNewChars; Loading @@ -313,11 +334,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable TextUtils.getChars(cs, csStart, csEnd, mText, start); TextUtils.getChars(cs, csStart, csEnd, mText, start); if (end > start) { if (end > start) { // no need for span fixup on pure insertion // no need for span fixup on pure insertion final boolean atEnd = (mGapStart + mGapLength == mText.length); boolean atEnd = (mGapStart + mGapLength == mText.length); for (int i = mSpanCount - 1; i >= 0; i--) { for (int i = 0; i < mSpanCount; i++) { if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; Loading @@ -331,16 +351,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & END_MASK); int flag = (mSpanFlags[i] & END_MASK); if (flag == POINT || (flag == PARAGRAPH && atEnd)) if (flag == POINT || (flag == PARAGRAPH && atEnd)) { mSpanEnds[i] = mGapStart + mGapLength; mSpanEnds[i] = mGapStart + mGapLength; else } else { mSpanEnds[i] = start; mSpanEnds[i] = start; } } // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE, which are POINT_MARK and could // get their boundaries swapped by the above code if (mSpanEnds[i] < mSpanStarts[i]) { removeSpan(i); } } } } } } Loading @@ -359,7 +374,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (en > csEnd) en = csEnd; if (en > csEnd) en = csEnd; // Add span only if this object is not yet used as a span in this string // Add span only if this object is not yet used as a span in this string if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) { if (getSpanStart(spans[i]) < 0) { setSpan(false, spans[i], st - csStart + start, en - csStart + start, setSpan(false, spans[i], st - csStart + start, en - csStart + start, sp.getSpanFlags(spans[i])); sp.getSpanFlags(spans[i])); } } Loading Loading @@ -465,6 +480,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable int newReplaceEnd = replaceEnd + nbNewChars; int newReplaceEnd = replaceEnd + nbNewChars; boolean spanChanged = false; boolean spanChanged = false; int previousSpanStart = spanStart; int previousSpanStart = spanStart; if (spanStart > newReplaceEnd) { if (spanStart > newReplaceEnd) { if (nbNewChars != 0) { if (nbNewChars != 0) { Loading @@ -477,11 +493,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && (spanStart != newReplaceEnd || (spanStart != newReplaceEnd || ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { // TODO previousSpanStart is incorrect, but we would need to save all the // TODO A correct previousSpanStart cannot be computed at this point. // previous spans' positions before replace to provide it // It would require to save all the previous spans' positions before the replace // Using an invalid -1 value to convey this would break the broacast range spanChanged = true; spanChanged = true; } } } } int previousSpanEnd = spanEnd; int previousSpanEnd = spanEnd; if (spanEnd > newReplaceEnd) { if (spanEnd > newReplaceEnd) { if (nbNewChars != 0) { if (nbNewChars != 0) { Loading