/// <summary> /// Splits the provided segments into lines with a maximum width. /// </summary> /// <param name="context">The render context.</param> /// <param name="segments">The segments to split into lines.</param> /// <param name="maxWidth">The maximum width.</param> /// <returns>A list of lines.</returns> public static List <SegmentLine> SplitLines(RenderContext context, IEnumerable <Segment> segments, int maxWidth) { if (context is null) { throw new ArgumentNullException(nameof(context)); } if (segments is null) { throw new ArgumentNullException(nameof(segments)); } var lines = new List <SegmentLine>(); var line = new SegmentLine(); var stack = new Stack <Segment>(segments.Reverse()); while (stack.Count > 0) { var segment = stack.Pop(); // Does this segment make the line exceed the max width? if (line.CellCount(context) + segment.CellCount(context) > maxWidth) { var diff = -(maxWidth - (line.Length + segment.Text.Length)); var offset = segment.Text.Length - diff; var(first, second) = segment.Split(offset); line.Add(first); lines.Add(line); line = new SegmentLine(); if (second != null) { stack.Push(second); } continue; } // Does the segment contain a newline? if (segment.Text.ContainsExact("\n")) { // Is it a new line? if (segment.Text == "\n") { if (line.Length != 0 || segment.IsLineBreak) { lines.Add(line); line = new SegmentLine(); } continue; } var text = segment.Text; while (text != null) { var parts = text.SplitLines(); if (parts.Length > 0) { if (parts[0].Length > 0) { line.Add(new Segment(parts[0], segment.Style)); } } if (parts.Length > 1) { if (line.Length > 0) { lines.Add(line); line = new SegmentLine(); } text = string.Concat(parts.Skip(1).Take(parts.Length - 1)); } else { text = null; } } } else { line.Add(segment); } } if (line.Count > 0) { lines.Add(line); } return(lines); }
protected override IEnumerable <Segment> Render(RenderContext context, int maxWidth) { lock (_lock) { DidOverflow = false; if (_renderable != null) { var segments = _renderable.Render(context, maxWidth); var lines = Segment.SplitLines(segments); var shape = SegmentShape.Calculate(context, lines); if (shape.Height > _console.Profile.Height) { if (Overflow == VerticalOverflow.Crop) { if (OverflowCropping == VerticalOverflowCropping.Bottom) { // Remove bottom lines var index = Math.Min(_console.Profile.Height, lines.Count); var count = lines.Count - index; lines.RemoveRange(index, count); } else { // Remove top lines var start = lines.Count - _console.Profile.Height; lines.RemoveRange(0, start); } shape = SegmentShape.Calculate(context, lines); } else if (Overflow == VerticalOverflow.Ellipsis) { var ellipsisText = _console.Profile.Capabilities.Unicode ? "…" : "..."; var ellipsis = new SegmentLine(((IRenderable) new Markup($"[yellow]{ellipsisText}[/]")).Render(context, maxWidth)); if (OverflowCropping == VerticalOverflowCropping.Bottom) { // Remove bottom lines var index = Math.Min(_console.Profile.Height - 1, lines.Count); var count = lines.Count - index; lines.RemoveRange(index, count); lines.Add(ellipsis); } else { // Remove top lines var start = lines.Count - _console.Profile.Height; lines.RemoveRange(0, start + 1); lines.Insert(0, ellipsis); } shape = SegmentShape.Calculate(context, lines); } DidOverflow = true; } _shape = _shape == null ? shape : _shape.Value.Inflate(shape); _shape.Value.Apply(context, ref lines); foreach (var(_, _, last, line) in lines.Enumerate()) { foreach (var item in line) { yield return(item); } if (!last) { yield return(Segment.LineBreak); } } yield break; } _shape = null; } }