Browse Source

Add `ChangeFormatting()` method to `MutableText`

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
13be338d5f
  1. 38
      sources/Text/MutableText.uc
  2. 69
      sources/Text/Tests/TEST_Text.uc
  3. 104
      sources/Text/Text.uc

38
sources/Text/MutableText.uc

@ -419,6 +419,44 @@ public final function MutableText Replace(
return self; return self;
} }
/**
* Changes formatting for characters with indices in range, specified as
* `[startIndex; startIndex + maxLength - 1]` to `newFormatting` parameter.
*
* If provided parameters `startPosition` and `maxLength` define a range that
* goes beyond `[0; self.GetLength() - 1]`, then intersection with a valid
* range will be used.
*
* @param startPosition Position of the first character to change formatting
* of. By default `0`, corresponding to the very first character.
* @param maxLength Max length of the segment to change formatting of.
* By default `0`, - that and all negative values are replaces by `MaxInt`,
* effectively extracting as much of a string as possible.
* @return Reference to the caller `MutableText` to allow for method chaining.
*/
public final function MutableText ChangeFormatting(
int startIndex,
int maxLength,
Formatting newFormatting)
{
local int endIndex;
if (maxLength <= 0) return self;
if (startIndex >= GetLength()) return self;
endIndex = Min(startIndex + maxLength, GetLength()) - 1;
startIndex = Max(startIndex, 0);
if (startIndex > endIndex) {
return self;
}
if (startIndex == 0 && endIndex == GetLength() - 1) {
ReformatWhole(newFormatting);
}
else {
ReformatRange(startIndex, endIndex, newFormatting);
}
return self;
}
defaultproperties defaultproperties
{ {
} }

69
sources/Text/Tests/TEST_Text.uc

@ -40,6 +40,7 @@ protected static function TESTS()
Test_StartsEndsWith(); Test_StartsEndsWith();
Test_IndexOf(); Test_IndexOf();
Test_Replace(); Test_Replace();
Test_ChangeFormatting();
} }
protected static function Test_TextCreation() protected static function Test_TextCreation()
@ -1044,6 +1045,74 @@ protected static function SubTest_ReplacePartFormatting()
$ "{rgb(4,4,4) cccc}{rgb(76,52,160) aB}{rgb(4,4,4) cc}a")); $ "{rgb(4,4,4) cccc}{rgb(76,52,160) aB}{rgb(4,4,4) cc}a"));
} }
protected static function Test_ChangeFormatting()
{
Context("Testing `ChangeFormatting()` method.");
SubTest_ChangeFormattingRegular();
SubTest_ChangeFormattingEdgeCases();
}
protected static function SubTest_ChangeFormattingRegular()
{
local Text template;
local MutableText testText;
local Text.Formatting greenFormatting, defaultFormatting;
greenFormatting = __().text.FormattingFromColor(__().color.Lime);
Issue("Formatting is not changed correctly.");
template = __().text.FromFormattedString(
"Normal part, {#ff0000 red part}, {#00ff00 green part}!!!");
testText = template.MutableCopy().ChangeFormatting(3, 4, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
("Nor{rgb(0,255,0) mal }part, {rgb(255,0,0) red part}, {rgb(0,255,0)"
@ "green part}!!!"));
testText = template.MutableCopy().ChangeFormatting(12, 10, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part,{rgb(0,255,0) red part,} {rgb(0,255,0) green part}!!!");
testText = template.MutableCopy().ChangeFormatting(12, 11, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part,{rgb(0,255,0) red part, green part}!!!");
// This test was added because it produced `none` access errors in the
// old implementation of `ChangeFormatting()`
testText = template.MutableCopy().ChangeFormatting(0, 35, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"{rgb(0,255,0) Normal part, red part, green part!!}!");
testText = template.MutableCopy().ChangeFormatting(3, 4, defaultFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part, {rgb(255,0,0) red part}, {rgb(0,255,0) green part}!!!");
testText = template.MutableCopy()
.ChangeFormatting(16, 13, defaultFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part, {rgb(255,0,0) red} part, green {rgb(0,255,0) part}!!!");
}
protected static function SubTest_ChangeFormattingEdgeCases()
{
local Text template;
local MutableText testText;
local Text.Formatting greenFormatting, defaultFormatting;
greenFormatting = __().text.FormattingFromColor(__().color.Lime);
Issue("Formatting is not changed correctly when indices are out of or"
@ "near index boundaries.");
template = __().text.FromFormattedString(
"Normal part, {#ff0000 red part}, {#00ff00 green part}!!!");
testText = template.MutableCopy().ChangeFormatting(33, 3, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part, {rgb(255,0,0) red part}, {rgb(0,255,0) green part!!!}");
testText = template.MutableCopy().ChangeFormatting(36, 5, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part, {rgb(255,0,0) red part}, {rgb(0,255,0) green part}!!!");
testText = template.MutableCopy()
.ChangeFormatting(-10, 100, defaultFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
"Normal part, red part, green part!!!");
testText = template.MutableCopy()
.ChangeFormatting(-10, 16, greenFormatting);
TEST_ExpectTrue(testText.ToFormattedString() ==
("{rgb(0,255,0) Normal} part, {rgb(255,0,0) red part}, {rgb(0,255,0)"
@ "green part}!!!"));
}
defaultproperties defaultproperties
{ {
caseName = "Text/MutableText" caseName = "Text/MutableText"

104
sources/Text/Text.uc

@ -124,6 +124,90 @@ protected function Finalizer()
formattingChunks.length = 0; formattingChunks.length = 0;
} }
/**
* Auxiliary method that changes formatting of the whole `Text` to
* a specified one (`newFormatting`). This method is faster than calling
* `ReformatRange`.
*
* @param newFormatting Formatting to set to the whole `Text`.
*/
protected final function ReformatWhole(Formatting newFormatting)
{
local FormattingChunk newChunk;
formattingChunks.length = 0;
newChunk.startIndex = 0;
newChunk.formatting = newFormatting;
formattingChunks[0] = newChunk;
}
/**
* Auxiliary method that changes formatting of the characters with indices in
* range `[start; end]` to a specified one (`newFormatting`).
*
* This method assumes, but does not check that:
* 1. `start <= end`;
* 2. `start` and `end` parameters belong to the range of valid indices
* `[0; GetLength() - 1]`
*
* @param start First character to change formatting of.
* @param end Last character to change formatting of.
* @param newFormatting Formatting to set to the specified characters.
*/
protected final function ReformatRange(
int start,
int end,
Formatting newFormatting)
{
local int i;
local Formatting formattingAfterChangedSegment;
local FormattingChunk newChunk;
local array<FormattingChunk> newFormattingChunks;
start = Max(start, 0);
end = Min(GetLength() - 1, end);
// Formatting right after `end`, te end of re-formatted segment
formattingAfterChangedSegment = GetFormatting(end + 1);
// 1. Copy old formatting before `start`
for (i = 0; i < formattingChunks.length; i += 1)
{
if (start <= formattingChunks[i].startIndex) {
break;
}
newFormattingChunks[newFormattingChunks.length] = formattingChunks[i];
}
newChunk.formatting = newFormatting;
newChunk.startIndex = start;
newFormattingChunks[newFormattingChunks.length] = newChunk;
if (end == GetLength() - 1)
{
formattingChunks = newFormattingChunks;
// We have inserted `FormattingChunk` without checking if it actually
// changes formatting. It might be excessive, so do a normalization.
NormalizeFormatting();
return;
}
// 2. Drop old formatting overwritten by `newFormatting`
while (i < formattingChunks.length)
{
if (end < formattingChunks[i].startIndex) {
break;
}
i += 1;
}
// 3. Copy old formatting after `end`
newChunk.formatting = formattingAfterChangedSegment;
newChunk.startIndex = end + 1; // end < GetLength() - 1
newFormattingChunks[newFormattingChunks.length] = newChunk;
while (i < formattingChunks.length)
{
newFormattingChunks[newFormattingChunks.length] = formattingChunks[i];
i += 1;
}
formattingChunks = newFormattingChunks;
// We have inserted `FormattingChunk` without checking if it actually
// changes formatting. It might be excessive, so do a normalization.
NormalizeFormatting();
}
/** /**
* Static method for creating an immutable `Text` object from (plain) `string`. * Static method for creating an immutable `Text` object from (plain) `string`.
* *
@ -967,6 +1051,26 @@ private final function UpdateFormattingCacheFor(int index)
} }
} }
// Removes possible unnecessary chunks from `formattingChunks`:
// if there is a chunk that tells us to have red color after index `3` and
// next one tells us to have red color after index `5` - the second chunk is
// unnecessary.
private final function NormalizeFormatting()
{
local int i;
while (i < formattingChunks.length - 1)
{
if (_.text.IsFormattingEqual( formattingChunks[i].formatting,
formattingChunks[i + 1].formatting))
{
formattingChunks.Remove(i + 1, 1);
}
else {
i += 1;
}
}
}
/** /**
* Converts data from the caller `Text` instance into a plain `string`. * Converts data from the caller `Text` instance into a plain `string`.
* Can be used to extract only substrings. * Can be used to extract only substrings.

Loading…
Cancel
Save