static bool NextCharacterIsFullWidthAndWillWrap(int width, int currentLineLength, ReadOnlySpan <char> chunk, int i) => chunk.Length > i + 1 && UnicodeWidth.GetWidth(chunk[i + 1]) > 1 && currentLineLength + 1 == width;
/// <summary> /// Wraps editable input, contained in the string builder, to the supplied width. /// The caret index (as the input is a 1 dimensional string of text) is converted /// to a 2 dimensional coordinate in the wrapped text. /// </summary> public static WordWrappedText WrapEditableCharacters(ReadOnlyStringBuilder input, int caret, int width) { Debug.Assert(caret >= 0 && caret <= input.Length); if (input.Length == 0) { return(new WordWrappedText( new[] { WrappedLine.Empty(startIndex: 0) }, new ConsoleCoordinate(0, caret))); } var lines = new List <WrappedLine>(); int currentLineLength = 0; var line = new StringBuilder(width); int textIndex = 0; int cursorColumn = 0; int cursorRow = 0; foreach (ReadOnlyMemory <char> chunkMemory in input.GetChunks()) { var chunk = chunkMemory.Span; for (var i = 0; i < chunk.Length; i++) { char character = chunk[i]; line.Append(character); bool isCursorPastCharacter = caret > textIndex; Debug.Assert(character != '\t', "tabs should be replaced by spaces"); int unicodeWidth = UnicodeWidth.GetWidth(character); if (unicodeWidth < 1) { Debug.Fail("such character should not be present"); continue; } currentLineLength += unicodeWidth; textIndex++; if (isCursorPastCharacter && !char.IsControl(character)) { cursorColumn++; } if (character == '\n' || currentLineLength == width || NextCharacterIsFullWidthAndWillWrap(width, currentLineLength, chunk, i)) { if (isCursorPastCharacter) { cursorRow++; cursorColumn = 0; } lines.Add(new WrappedLine(textIndex - line.Length, line.ToString())); line = new StringBuilder(); currentLineLength = 0; } } } if (currentLineLength > 0 || input[^ 1] == '\n') { lines.Add(new WrappedLine(textIndex - line.Length, line.ToString())); } Debug.Assert(textIndex == input.Length); if (cursorRow >= lines.Count) { Debug.Assert(cursorRow == lines.Count); lines.Add(WrappedLine.Empty(startIndex: textIndex)); } return(new WordWrappedText(lines, new ConsoleCoordinate(cursorRow, cursorColumn)));