/** * Mutable version of Acedia's `BaseText`. * Copyright 2020 - 2022 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. * * Acedia is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License, or * (at your option) any later version. * * Acedia is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ class MutableText extends BaseText; var private int CODEPOINT_NEWLINE; public function Text IntoText() { local Text immutableVersion; immutableVersion = Copy(); FreeSelf(); return immutableVersion; } public function MutableText IntoMutableText() { local MutableText mutableVersion; mutableVersion = MutableCopy(); FreeSelf(); return mutableVersion; } /** * Clears all current data from the caller `MutableText` instance. * * @return Returns caller `MutableText` to allow for method chaining. */ public final function MutableText Clear() { DropCodePoints(); return self; } /** * Appends a new character to the caller `MutableText`, while discarding its * own formatting. * * @param newCharacter Character to add to the caller `MutableText`. * Only valid characters will be added. Its formatting will be discarded. * @param characterFormatting You can use this parameter to specify formatting * `newCharacter` should have in the caller `MutableText` instead of * its own. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendRawCharacter( BaseText.Character newCharacter, optional Formatting characterFormatting) { if (!_.text.IsValidCharacter(newCharacter)) { return self; } SetFormatting(characterFormatting); return MutableText(AppendCodePoint(newCharacter.codePoint)); } /** * Appends all characters from the given array, in order, but discarding their * own formatting. * * This method should be faster than `AppendManyCharacters()` or several calls * of `AppendRawCharacter()`, since it does not need to check whether * formatting is changed from character to character. * * @param newCharacters Characters to be added to the caller `MutableText`. * Only valid characters will be added. Their formatting will be discarded. * @param characterFormatting You can use this parameter to specify formatting * `newCharacters` should have in the caller `MutableText` instead of * their own. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendManyRawCharacters( array newCharacters, optional Formatting charactersFormatting) { local int i; SetFormatting(charactersFormatting); for (i = 0; i < newCharacters.length; i += 1) { AppendCodePoint(newCharacters[i].codePoint); } return self; } /** * Appends a new character to the caller `MutableText`. * * @param newCharacter Character to add to the caller `MutableText`. * Only valid characters will be added. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendCharacter( BaseText.Character newCharacter) { if (!_.text.IsValidCharacter(newCharacter)) { return self; } SetFormatting(newCharacter.formatting); return MutableText(AppendCodePoint(newCharacter.codePoint)); } /** * Appends all characters from the given array, in order. * * @param newCharacters Characters to be added to the caller `MutableText`. * Only valid characters will be added. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendManyCharacters( array newCharacters) { local int i; for (i = 0; i < newCharacters.length; i += 1) { AppendCharacter(newCharacters[i]); } return self; } /** * Converts caller `MutableText` instance into lower case. */ public final function ToLower() { ConvertCase(true); } /** * Converts caller `MutableText` instance into upper case. */ public final function ToUpper() { ConvertCase(false); } /** * Appends new line character to the caller `MutableText`. * * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendLineBreak() { AppendCodePoint(CODEPOINT_NEWLINE); return self; } /** * Appends contents of another `BaseText` to the caller `MutableText`. * * @param other Instance of `BaseText`, which content method * will append. Appends nothing if passed value is `none`. * @param defaultFormatting Formatting to apply to `other`'s character that * do not have it specified. For example, `defaultFormatting.isColored`, * but some of `other`'s characters do not have a color defined - * they will be appended with a specified color. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText Append( BaseText other, optional Formatting defaultFormatting) { local int i; local int otherLength; local Character nextCharacter; local Formatting newFormatting; if (other == none) { return self; } SetFormatting(defaultFormatting); otherLength = other.GetLength(); for (i = 0; i < otherLength; i += 1) { nextCharacter = other.GetRawCharacter(i); if (other.IsFormattingChangedAt(i)) { newFormatting = other.GetFormatting(i); // If default formatting is specified, but `other`'s formatting // (at least for some characters) is not, - apply default one if (defaultFormatting.isColored && !newFormatting.isColored) { newFormatting.isColored = true; newFormatting.color = defaultFormatting.color; } SetFormatting(newFormatting); } AppendCodePoint(nextCharacter.codePoint); } return self; } /** * Prepends contents of another `BaseText` to the caller `MutableText`. * * @param other Instance of `BaseText`, which content method * will prepend. Prepends nothing if passed value is `none`. * @param defaultFormatting Formatting to apply to `other`'s character that * do not have it specified. For example, `defaultFormatting.isColored`, * but some of `other`'s characters do not have a color defined - * they will be prepended with a specified color. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText Prepend( BaseText other, optional Formatting defaultFormatting) { local MutableText selfCopy; if (other == none) { return self; } selfCopy = MutableCopy(); Clear().Append(other, defaultFormatting).Append(selfCopy); _.memory.Free(selfCopy); return self; } /** * Appends contents of the plain `string` to the caller `MutableText`. * * @param source Plain `string` to be appended to * the caller `MutableText`. * @param defaultFormatting Formatting to be used for `source`'s characters. * By default defines 'null' formatting (no color set). * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendString( string source, optional Formatting defaultFormatting) { local int i; local int sourceLength; sourceLength = Len(source); SetFormatting(defaultFormatting); // Decompose `source` into integer codes for (i = 0; i < sourceLength; i += 1) { AppendCodePoint(Asc(Mid(source, i, 1))); } return self; } /** * Prepends contents of the plain `string` to the caller `MutableText`. * * @param source Plain `string` to be prepended to * the caller `MutableText`. * @param defaultFormatting Formatting to be used for `source`'s characters. * By default defines 'null' formatting (no color set). * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText PrependString( string source, optional Formatting defaultFormatting) { local MutableText selfCopy; selfCopy = MutableCopy(); Clear().AppendString(source, defaultFormatting).Append(selfCopy); _.memory.Free(selfCopy); return self; } /** * Appends contents of the colored `string` to the caller `MutableText`. * * @param source Colored `string` to be appended to * the caller `MutableText`. * @param defaultFormatting Formatting to be used for `source`'s characters * that have no color information defined. * By default defines 'null' formatting (no color set). * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendColoredString( string source, optional Formatting defaultFormatting) { local int i; local int sourceLength; local array sourceAsIntegers; local Formatting newFormatting; // Decompose `source` into integer codes sourceLength = Len(source); for (i = 0; i < sourceLength; i += 1) { sourceAsIntegers[sourceAsIntegers.length] = Asc(Mid(source, i, 1)); } // With colored strings we only need to care about color for formatting i = 0; newFormatting = defaultFormatting; SetFormatting(newFormatting); while (i < sourceLength) { if (sourceAsIntegers[i] == CODEPOINT_ESCAPE) { if (i + 3 >= sourceLength) break; newFormatting.isColored = true; newFormatting.color = _.color.RGB( sourceAsIntegers[i + 1], sourceAsIntegers[i + 2], sourceAsIntegers[i + 3]); i += 4; SetFormatting(newFormatting); } else { AppendCodePoint(sourceAsIntegers[i]); i += 1; } } return self; } /** * Prepends contents of the colored `string` to the caller `MutableText`. * * @param source Colored `string` to be prepended to * the caller `MutableText`. * @param defaultFormatting Formatting to be used for `source`'s characters * that have no color information defined. * By default defines 'null' formatting (no color set). * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText PrependColoredString( string source, optional Formatting defaultFormatting) { local MutableText selfCopy; selfCopy = MutableCopy(); Clear().AppendColoredString(source, defaultFormatting).Append(selfCopy); _.memory.Free(selfCopy); return self; } /** * Appends contents of the formatted `BaseText` to the caller `MutableText`. * * @param other `BaseText` (with formatted string contents) to be * appended to the caller `MutableText`. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendFormatted( BaseText other, optional Formatting defaultFormatting) { local MutableText coloredOther; if (other == none) { return self; } if (defaultFormatting.isColored) { coloredOther = other .MutableCopy() .ChangeDefaultFormatting(defaultFormatting); class'FormattingStringParser'.static .ParseFormatted(coloredOther, self); _.memory.Free(coloredOther); } else { class'FormattingStringParser'.static.ParseFormatted(other, self); } return self; } /** * Prepends contents of the formatted `BaseText` to the caller `MutableText`. * * @param other `BaseText` (with formatted string contents) to be * prepended to the caller `MutableText`. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText PrependFormatted( BaseText other, optional Formatting defaultFormatting) { local MutableText selfCopy; if (other == none) { return self; } selfCopy = MutableCopy(); Clear().AppendFormatted(other, defaultFormatting).Append(selfCopy); _.memory.Free(selfCopy); return self; } /** * Appends contents of the formatted `string` to the caller `MutableText`. * * @param source Formatted `string` to be appended to * the caller `MutableText`. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText AppendFormattedString( string source, optional Formatting defaultFormatting) { local MutableText sourceAsText; sourceAsText = _.text.FromStringM(source); AppendFormatted(sourceAsText); _.memory.Free(sourceAsText); return self; } /** * Prepends contents of the formatted `string` to the caller `MutableText`. * * @param source Formatted `string` to be prepended to * the caller `MutableText`. * @return Caller `MutableText` to allow for method chaining. */ public final function MutableText PrependFormattedString( string source, optional Formatting defaultFormatting) { local MutableText selfCopy; selfCopy = MutableCopy(); Clear().AppendFormattedString(source, defaultFormatting).Append(selfCopy); _.memory.Free(selfCopy); return self; } /** * Unlike `Text`, `MutableText` can change it's content and therefore it's * hash code cannot depend on it. So we restore `AcediaObject`'s behavior and * return random value, generated at the time of allocation. * * @return Hash code for the caller `MutableText`. */ protected function int CalculateHashCode() { return super(AcediaObject).GetHashCode(); } /** * Replaces every occurrence of the `Text` object `before` with * the `Text` object `after`. * * @param before `BaseText` contents to match and then replace. * @param after `BaseText` contents to replace `before` with. * @param caseSensitivity Defines whether `before` should be matched * in a case-sensitive manner. By default it will be. * @param formatSensitivity Defines whether `before` should be matched * in a way sensitive for color information. By default it is will not. * @return Returns caller `MutableText`, to allow for method chaining. */ public final function MutableText Replace( BaseText before, BaseText after, optional CaseSensitivity caseSensitivity, optional FormatSensitivity formatSensitivity) { local int index; local bool needToInsertReplacer; local int nextReplacementIndex; local BaseText selfCopy; if (before == none) return self; if (before.IsEmpty()) return self; selfCopy = Copy(); Clear(); while (index < selfCopy.GetLength()) { nextReplacementIndex = selfCopy.IndexOf(before, index, caseSensitivity, formatSensitivity); if (nextReplacementIndex < 0) { needToInsertReplacer = false; nextReplacementIndex = selfCopy.GetLength(); } else { needToInsertReplacer = true; } // Copy characters between replacements one by one while (index < nextReplacementIndex) { AppendCharacter(selfCopy.GetCharacter(index)); index += 1; } if (needToInsertReplacer) { Append(after); // `before.GetLength() > 0` because of entry conditions index += before.GetLength(); } } selfCopy.FreeSelf(); return self; } /** * Replaces every occurrence of the string `before` with the string `after`. * * @param before `string` contents to match and then replace. * @param after `string` contents to replace `before` with. * @param caseSensitivity Defines whether `before` should be matched * in a case-sensitive manner. By default it will be. * @return Returns caller `MutableText`, to allow for method chaining. */ public final function MutableText ReplaceS( string before, string after, optional CaseSensitivity caseSensitivity) { local MutableText beforeText, afterText; beforeText = _.text.FromStringM(before); afterText = _.text.FromStringM(after); Replace(beforeText, afterText, caseSensitivity); _.memory.Free(beforeText); _.memory.Free(afterText); return self; } /** * Sets `newFormatting` for every non-formatted character in * the caller `MutableText`. * * @param newFormatting Formatting to use for all non-formatted characters * in the caller `MutableText`. If `newFormatting` is not colored itself - * method does nothing. * @return Returns caller `MutableText`, to allow for method chaining. */ public final function MutableText ChangeDefaultFormatting( Formatting newFormatting) { _changeDefaultFormatting(newFormatting); return self; } /** * Colors every non-formatted character in the caller `MutableText` in * the given `defaultColor`. * * @param defaultColor Color to use for all non-formatted characters in * the caller `MutableText`. * @return Returns caller `MutableText`, to allow for method chaining. */ public final function MutableText ChangeDefaultColor(Color defaultColor) { return ChangeDefaultFormatting(_.text.FormattingFromColor(defaultColor)); } /** * 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 newFormatting New formatting to apply. * @param startIndex 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 mean that method should * reformat all characters to the right of `startIndex`. * @return Reference to the caller `MutableText` to allow for method chaining. */ public final function MutableText ChangeFormatting( Formatting newFormatting, optional int startIndex, optional int maxLength) { local int endIndex; if (startIndex >= GetLength()) { return self; } endIndex = startIndex + maxLength - 1; startIndex = Max(startIndex, 0); if (maxLength <= 0) { endIndex = GetLength() - 1; } if (startIndex > endIndex) { return self; } if (startIndex == 0 && endIndex == GetLength() - 1) { ReformatWhole(newFormatting); } else { ReformatRange(startIndex, endIndex, newFormatting); } return self; } /** * Removes all characters in range, specified as * `[startIndex; startIndex + maxLength - 1]`. * * 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 startIndex Position of the first character to get removed. * @param maxLength Max length of the segment to get removed. * By default `0` - that and all negative values mean that method should * remove all characters to the right of `startIndex`. * @return Reference to the caller `MutableText` to allow for method chaining. */ public final function MutableText Remove(int startIndex, optional int maxLength) { local int index; local int endIndex; local Text selfCopy; if (startIndex >= GetLength()) { return self; } endIndex = startIndex + maxLength - 1; startIndex = Max(startIndex, 0); if (maxLength <= 0) { endIndex = GetLength() - 1; } if (startIndex > endIndex) { return self; } if (startIndex == 0 && endIndex == GetLength() - 1) { Clear(); return self; } selfCopy = Copy(); Clear(); while (index < selfCopy.GetLength()) { if (index >= startIndex && index <= endIndex) { index = endIndex + 1; } else { AppendCharacter(selfCopy.GetCharacter(index)); index += 1; } } selfCopy.FreeSelf(); return self; } /** * Removes leading and trailing whitespaces from the caller `MutableText`. * Optionally also reduces all sequences of internal whitespace characters to * a single space (first space character in each sequence). * * @param fixInnerSpacings By default `false` - only removes leading and * trailing whitespace sequences from the caller `MutableText`. * Setting this to `true` also makes method simplify sequences internal * whitespace characters inside caller `MutableText`. * @return Reference to the caller `MutableText` to allow for method chaining. */ public final function MutableText Simplify(optional bool fixInnerSpacings) { local int index; local int leftIndex, rightIndex; local bool isWhitespace, foundNonWhitespace; local Text selfCopy; while (leftIndex < GetLength()) { if (!_.text.IsWhitespace(GetCharacter(leftIndex))) { foundNonWhitespace = true; break; } leftIndex += 1; } if (!foundNonWhitespace) { Clear(); return self; } rightIndex = GetLength() - 1; while (rightIndex >= 0) { if (!_.text.IsWhitespace(GetCharacter(rightIndex))) { break; } rightIndex -= 1; } selfCopy = Copy(leftIndex, rightIndex - leftIndex + 1); Clear(); while (index < selfCopy.GetLength()) { isWhitespace = _.text.IsWhitespace(selfCopy.GetCharacter(index)); if (foundNonWhitespace || !isWhitespace) { AppendCharacter(selfCopy.GetCharacter(index)); } if (fixInnerSpacings) { foundNonWhitespace = !isWhitespace; } index += 1; } selfCopy.FreeSelf(); return self; } defaultproperties { CODEPOINT_NEWLINE = 10 }