/// <summary> /// Transform the given string into a new one, preserving color. /// </summary> /// <param name="s">The input string.</param> /// <param name="func">The function to apply.</param> /// <returns>The generated string.</returns> /// <exception cref="ArgumentNullException">Thrown when <paramref name="func"/> /// is null.</exception> public static ColoredString Transform(this ColoredString s, Func <string, string> func) { if (func == null) { throw new ArgumentNullException(nameof(func)); } return(new ColoredString(func(s.Content), s.ForegroundColor, s.BackgroundColor)); }
/// <summary> /// Take the color of the given string to produce a new one, but with the given /// content. /// </summary> /// <param name="s">The input string.</param> /// <param name="newContent">The new content to use.</param> /// <returns>The generated string.</returns> /// <exception cref="ArgumentNullException">Thrown when <paramref name="newContent"/> /// is null.</exception> public static ColoredString WithContent(this ColoredString s, string newContent) { if (newContent == null) { throw new ArgumentNullException(nameof(newContent)); } return(s.Transform(_ => newContent)); }
/// <summary> /// Returns a new string containing a substring of the given string. /// </summary> /// <param name="s">The string.</param> /// <param name="startIndex">The 0-based index to start from.</param> /// <param name="length">The length of the substring, expressed as /// a count of characters.</param> /// <returns>The new string.</returns> public static ColoredString Substring(this ColoredString s, int startIndex, int length) => s.Transform(content => content.Substring(startIndex, length));
/// <summary> /// Wrap the provided text at the given width, indenting it with the /// given indentation width. /// </summary> /// <param name="text">Text to wrap.</param> /// <param name="width">Maximum width of the text, in number of /// characters.</param> /// <param name="blockIndent">The number of characters to block-indent /// all lines. Use 0 to indicate no block indentation should occur.</param> /// <param name="hangingIndent">The number of characters to hanging-indent /// the text; all lines after the first line are affected, and the first /// line is left unmodified. Use 0 to indicate no hanging indentation /// should occur.</param> /// <returns>The wrapped text.</returns> public static ColoredString Wrap(this ColoredString text, int width, int blockIndent = 0, int hangingIndent = 0) => text.Transform(content => Wrap(content, width, blockIndent, hangingIndent));
/// <summary> /// Simple constructor. /// </summary> /// <param name="value">String content.</param> public ColoredMultistring(ColoredString value) { Content = new[] { value }; }
/// <summary> /// Append a colored string followed by a newline. /// </summary> /// <param name="value">The colored string to append.</param> public void AppendLine(ColoredString value) { Append(value); Append(value.Transform(content => Environment.NewLine)); }
/// <summary> /// Append a colored string. /// </summary> /// <param name="value">The colored string to append.</param> public void Append(ColoredString value) => Insert(Length, value);
/// <summary> /// Inserts the given string at the specified index. /// </summary> /// <param name="index">0-based index.</param> /// <param name="s">The string to insert.</param> public void Insert(int index, ColoredString s) { if (index < 0 || index > Length) { throw new ArgumentOutOfRangeException(nameof(index)); } // Optimization: don't bother if string is empty. if (s.IsEmpty()) { return; } // // At this point, we're guaranteed it's either before or in // the middle of the existing contents. // var pieceIndex = 0; var offset = 0; ColoredString?lastPiece = null; while (pieceIndex < _pieces.Count) { var piece = _pieces[pieceIndex]; Debug.Assert(!piece.IsEmpty()); // Case 1: insertion point is just before this piece. if (index == offset) { if (s.IsSameColorAs(piece)) { _pieces.RemoveAt(pieceIndex); _pieces.Insert(pieceIndex, piece.Transform(content => s.Content + content)); } else { _pieces.Insert(pieceIndex, s); } _totalLength += s.Length; return; } // Case 2: insertion point is in middle of this piece. else if (index < offset + piece.Length) { _pieces.RemoveAt(pieceIndex); if (s.IsSameColorAs(piece)) { _pieces.Insert(pieceIndex, piece.Transform(content => content.Substring(0, index - offset) + s.Content + content.Substring(index - offset))); } else { _pieces.Insert(pieceIndex, piece.Substring(0, index - offset)); _pieces.Insert(pieceIndex + 1, s); _pieces.Insert(pieceIndex + 2, piece.Substring(index - offset)); } _totalLength += s.Length; return; } // Case 3: insertion point is just after this piece. // Only insert during this loop iteration if new piece // can be merged with this one. We'll otherwise get it // the next time around. else if (index == offset + piece.Length) { if (s.IsSameColorAs(piece)) { _pieces.RemoveAt(pieceIndex); _pieces.Insert(pieceIndex, piece.Transform(content => content + s.Content)); _totalLength += s.Length; return; } } offset += piece.Length; lastPiece = piece; ++pieceIndex; } // If we're still here, then it goes at the end. Debug.Assert(index == Length); // Append. _pieces.Add(s); _totalLength += s.Length; }