Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Long strings get line breaks inserted when written using AnsiConsole.WriteLine #1312

Open
Gnbrkm41 opened this issue Sep 20, 2023 · 5 comments

Comments

@Gnbrkm41
Copy link

Gnbrkm41 commented Sep 20, 2023

Title says all; It's kind of annoying when you print long single-line strings and copy & paste it somewhere else, only to find that the string is not single line anymore.

Note that this is essentially a duplicate of #845, but since the original issue is marked as closed I am reopening another issue to hopefully get any suggestions to get around this. Since I'm on the latest preview and am experiencing this... I guess the issue still remains as is?


Please upvote 👍 this issue if you are interested in it.

@sandreas
Copy link

sandreas commented Oct 3, 2024

@Gnbrkm41
The suggested solutions so far were:

  • Use Console.WriteLine instead of AnsiConsole.WriteLine (recommended)
  • Use int.MaxValue as Column width (NOT recommended)
    var width = AnsiConsole.Profile.Width;
    AnsiConsole.Profile.Width = int.MaxValue;
    AnsiConsole.MarkupLine(content);
    AnsiConsole.Profile.Width = width;  

My personal suggestion would be a markup option:

AnsiConsole.Markup("[nobr]a very long string[/]");

or an options object as extra parameter or as default:

var options = new AnsiConsoleOptions() {
NoBreak = true
};
AnsiConsole.DefaultOptions = options;
AnsiConsole.WriteLine("....", options); 

@filzrev
Copy link

filzrev commented Oct 3, 2024

The suggested solutions so far were:

I've used another workaround by wrapping render element with custom IRenderable that override Measure/ Render methods.
Downside of this workaround is when using link/decoration affected to the end of the console.
(I've confirmed that problems when changing Windows Terminal's size dynamically)

Example Code
using Spectre.Console;
using Spectre.Console.Rendering;

namespace SampleConsoleApp;

internal class Program
{
    static void Main()
    {
        var bufferSize = Console.BufferWidth;

        string longText = new string('a', bufferSize + 1);

        // By default long text is automatically breaked based on console buffer size.
        AnsiConsole.Console.WriteLine(longText);

        // When using **custom renderable}} wrapper. long text is not wrapped.
        AnsiConsole.Console.WriteLineNoBreak(longText);
        AnsiConsole.Console.WriteLineNoBreak(longText, new(foreground: ConsoleColor.Red));
        AnsiConsole.Console.WriteLineNoBreak(longText, new(background: ConsoleColor.Blue));
        AnsiConsole.Console.WriteLineNoBreak(longText, new(link: "https://google.com"));
        AnsiConsole.Console.WriteLineNoBreak(longText, new(decoration: Decoration.Strikethrough));
        AnsiConsole.Console.MarkupNoBreak(Markup.FromInterpolated($"[yellow]{longText}{Environment.NewLine}[/]"));
    }
}

public static class IAnsiConsoleExtensions
{
    public static void WriteNoBreak(this IAnsiConsole ansiConsole, string text, Style? style = null)
    {
        ansiConsole.Write(new NonBreakingRenderable(text, style));
    }

    public static void WriteLineNoBreak(this IAnsiConsole ansiConsole, string text, Style? style = null)
    {
        ansiConsole.Write(new NonBreakingRenderable($"{text}{Environment.NewLine}", style));
    }

    public static void MarkupNoBreak(this IAnsiConsole ansiConsole, Markup markup)
    {
        ansiConsole.Write(new NonBreakingRenderable(markup));
    }

    private sealed class NonBreakingRenderable : IRenderable
    {
        private readonly IRenderable _renderable;

        public NonBreakingRenderable(string text, Style? style)
        {
            _renderable = new Paragraph(text, style);
        }

        public NonBreakingRenderable(IRenderable renderable)
        {
            _renderable = renderable;
        }

        public Measurement Measure(RenderOptions options, int maxWidth) => _renderable.Measure(options, int.MaxValue);
        public IEnumerable<Segment> Render(RenderOptions options, int maxWidth) => _renderable.Render(options, int.MaxValue);
    }
}

@PavelHrachou
Copy link

PavelHrachou commented Nov 26, 2024

@filzrev I like your idea. Thanks for the code.

I'm using spectre.console for logs, so this is how your code added to what I already have looks like.

using Spectre.Console;
using Spectre.Console.Rendering;

namespace Omada.Ingestion.Logging.Helpers;

/// <summary>
/// A helper class for building ANSI strings using Spectre.Console.
/// </summary>
public sealed class AnsiStringBuilder : IDisposable
{
  /// <summary>
  /// Gets the ANSI console writer.
  /// </summary>
  private IAnsiConsole AnsiWriter { get; }

  /// <summary>
  /// Gets the string writer.
  /// </summary>
  private StringWriter Writer { get; }

  /// <summary>
  /// Initializes a new instance of the <see cref="AnsiStringBuilder"/> class.
  /// </summary>
  public AnsiStringBuilder()
  {
    Writer = new StringWriter();
    AnsiWriter = AnsiConsole.Create(
      new AnsiConsoleSettings
      {
        Ansi = AnsiSupport.No,
        ColorSystem = ColorSystemSupport.NoColors,
        Out = new AnsiConsoleOutput(Writer)
      });
  }

  /// <summary>
  /// Writes a <see cref="IRenderable"/> to the console.
  /// </summary>
  /// <param name="renderable">The <see cref="IRenderable"/> to write.</param>
  public void Write(IRenderable renderable)
  {
    AnsiWriter.Write(renderable);
  }

  /// <summary>
  /// Writes line to the builder without a line break.
  /// </summary>
  /// <param name="text">The text to write.</param>
  /// <param name="style">The style to apply to the text.</param>
  public void WriteNoLineBreak(string text, Style? style = null) => AnsiWriter.Write(new NonBreakingRenderable(text, style));

  /// <summary>
  /// Writes line to the builder without a line break.
  /// </summary>
  /// <param name="text">The text to write.</param>
  /// <param name="style">The style to apply to the text.</param>
  public void WriteLineNoLineBreak(string text, Style? style = null) => AnsiWriter.Write(new NonBreakingRenderable($"{text}{Environment.NewLine}", style));

  /// <summary>
  /// Writes markup to the console without a line break.
  /// </summary>
  /// <param name="markup">The markup to write.</param>
  public void MarkupNoLineBreak(Markup markup) => AnsiWriter.Write(new NonBreakingRenderable(markup));

  /// <summary>
  /// Returns the string generated using ANSI string builder.
  /// </summary>
  public override string ToString()
  {
    return Writer.ToString();
  }

  /// <summary>
  /// Disposes the resources used by the <see cref="AnsiStringBuilder"/> class.
  /// </summary>
  public void Dispose()
  {
    Writer.Dispose();
  }

  /// <summary>
  /// A renderable class that prevents line breaks.
  /// </summary>
  private sealed class NonBreakingRenderable : IRenderable
  {
    private readonly IRenderable _renderable;

    /// <summary>
    /// Initializes a new instance of the <see cref="NonBreakingRenderable"/> class with text and style.
    /// </summary>
    /// <param name="text">The text to render.</param>
    /// <param name="style">The style to apply to the text.</param>
    public NonBreakingRenderable(string text, Style? style)
    {
      _renderable = new Paragraph(text, style);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="NonBreakingRenderable"/> class with a renderable.
    /// </summary>
    /// <remarks>
    /// Added to allow logging without a line break.
    /// Based on issue: Long strings get line breaks inserted when written using AnsiConsole.WriteLine #1312
    /// Comment: https://github.com/spectreconsole/spectre.console/issues/1312#issuecomment-2391514446
    /// </remarks>
    /// <param name="renderable">The renderable to wrap.</param>
    public NonBreakingRenderable(IRenderable renderable)
    {
      _renderable = renderable;
    }

    /// <summary>
    /// Measures the size of the renderable.
    /// </summary>
    /// <param name="options">The render options.</param>
    /// <param name="maxWidth">The maximum width.</param>
    /// <returns>The measurement of the renderable.</returns>
    public Measurement Measure(RenderOptions options, int maxWidth) =>
      _renderable.Measure(
        options,
        int.MaxValue);

    /// <summary>
    /// Renders the renderable.
    /// </summary>
    /// <param name="options">The render options.</param>
    /// <param name="maxWidth">The maximum width.</param>
    /// <returns>The segments of the renderable.</returns>
    public IEnumerable<Segment> Render(RenderOptions options, int maxWidth) =>
      _renderable.Render(
        options,
        int.MaxValue);
  }
}

@ricardoboss
Copy link

ricardoboss commented Jan 2, 2025

I'm not sure why, but I don't run into this when running my program in debug mode in Rider:

Code
using Spectre.Console;

const string moreThan80Chars =
    "asldjkhashjafhjkadfjhkashjksdakhjasjkhlasdhjklashjlkasjhkajhkdlasdhjkadjasdkjhs|asdkjlshdflkjshdfkjhsdfjhsdfkjhsdfkjhsdf";

Console.WriteLine(moreThan80Chars);
AnsiConsole.WriteLine(moreThan80Chars);

Console.WriteLine("Window Width:" + Console.WindowWidth);
Console.WriteLine("Buffer Width:" + Console.BufferWidth);

image

@ricardoboss
Copy link

Using Console.LargestWindowWidth might be the easiest workaround:

using Spectre.Console;

const string moreThan80Chars =
    "asldjkhashjafhjkadfjhkashjksdakhjasjkhlasdhjklashjlkasjhkajhkdlasdhjkadjasdkjhs|asdkjlshdflkjshdfkjhsdfjhsdfkjhsdfkjhsdf";

AnsiConsole.Console.Profile.Width = Console.LargestWindowWidth;

Console.WriteLine(moreThan80Chars);
AnsiConsole.WriteLine(moreThan80Chars);

Console.WriteLine("Window Width: " + Console.WindowWidth);
Console.WriteLine("Buffer Width: " + Console.BufferWidth);
Console.WriteLine("LargestWindowWidth: " + Console.LargestWindowWidth);

For some reason, this also changes the values for Console.WindowWidth and Console.BufferWidth.
In any case, this is better than setting Profile.Width to int.MaxValue or screwing with the measurements of widgets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Todo 🕑
Development

No branches or pull requests

5 participants