/// <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)));
public static Row[] ApplyColorToCharacters(IReadOnlyCollection <FormatSpan> highlights, IReadOnlyList <WrappedLine> lines, SelectionSpan?selection, AnsiColor?selectedTextBackground) { var selectionStart = new ConsoleCoordinate(int.MaxValue, int.MaxValue); //invalid var selectionEnd = new ConsoleCoordinate(int.MaxValue, int.MaxValue); //invalid if (selection.TryGet(out var selectionValue)) { selectionStart = selectionValue.Start; selectionEnd = selectionValue.End; } bool selectionHighlight = false; var highlightsLookup = highlights .ToLookup(h => h.Start) .ToDictionary(h => h.Key, conflictingHighlights => conflictingHighlights.OrderByDescending(h => h.Length).First()); var highlightedRows = new Row[lines.Count]; FormatSpan?currentHighlight = null; for (int lineIndex = 0; lineIndex < lines.Count; lineIndex++) { WrappedLine line = lines[lineIndex]; int lineFullWidthCharacterOffset = 0; var cells = Cell.FromText(line.Content); for (int cellIndex = 0; cellIndex < cells.Count; cellIndex++) { var cell = cells[cellIndex]; if (cell.IsContinuationOfPreviousCharacter) { lineFullWidthCharacterOffset++; } // syntax highlight wrapped lines if (currentHighlight.TryGet(out var previousLineHighlight) && cellIndex == 0) { currentHighlight = HighlightSpan(previousLineHighlight, cells, cellIndex, previousLineHighlight.Start - line.StartIndex); } // get current syntaxt highlight start int characterPosition = line.StartIndex + cellIndex - lineFullWidthCharacterOffset; currentHighlight ??= highlightsLookup.TryGetValue(characterPosition, out var lookupHighlight) ? lookupHighlight : null; // syntax highlight based on start if (currentHighlight.TryGet(out var highlight) && highlight.Contains(characterPosition)) { currentHighlight = HighlightSpan(highlight, cells, cellIndex, cellIndex); } // if there's text selected, invert colors to represent the highlight of the selected text. if (selectionStart.Equals(lineIndex, cellIndex - lineFullWidthCharacterOffset)) //start is inclusive { selectionHighlight = true; } if (selectionEnd.Equals(lineIndex, cellIndex - lineFullWidthCharacterOffset)) //end is exclusive { selectionHighlight = false; } if (selectionHighlight) { if (selectedTextBackground.TryGet(out var background)) { cell.Formatting = cell.Formatting with { Background = background }; } else { cell.Formatting = new ConsoleFormat { Inverted = true }; } } } highlightedRows[lineIndex] = new Row(cells); } return(highlightedRows); }