Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ private void LoadLogs(ConsoleLogsSubscription newConsoleLogsSubscription)
// Console logs are filtered in the UI by the timestamp of the log entry.
var timestampFilterDate = GetFilteredDateFromRemove();

var logParser = new LogParser();
var logParser = new LogParser(ConsoleColor.Black);
await foreach (var batch in subscription.ConfigureAwait(true))
{
if (batch.Count is 0)
Expand Down
38 changes: 20 additions & 18 deletions src/Aspire.Dashboard/ConsoleLogs/AnsiParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public static string StripControlSequences(string text)
return outputBuilder?.ToString() ?? text;
}

public static ConversionResult ConvertToHtml(string? text, ParserState? priorResidualState = null)
public static ConversionResult ConvertToHtml(string? text, ParserState? priorResidualState = null, ConsoleColor? defaultBackgroundColor = null)
{
var textStartIndex = -1;
var textLength = 0;
Expand Down Expand Up @@ -147,7 +147,7 @@ public static ConversionResult ConvertToHtml(string? text, ParserState? priorRes
// Ignore everything else and don't write sequence to the output.
if (finalByte == DisplayAttributesFinalByte)
{
ProcessParameters(ref newState, parameters);
ProcessParameters(defaultBackgroundColor, ref newState, parameters);
}

continue;
Expand Down Expand Up @@ -197,7 +197,7 @@ public static ConversionResult ConvertToHtml(string? text, ParserState? priorRes
return new(outputBuilder.ToString(), currentState);
}

private static void ProcessParameters(ref ParserState newState, int[] parameters)
private static void ProcessParameters(ConsoleColor? defaultBackgroundColor, ref ParserState newState, int[] parameters)
{
for (var i = 0; i < parameters.Length; i++)
{
Expand Down Expand Up @@ -228,7 +228,9 @@ private static void ProcessParameters(ref ParserState newState, int[] parameters
}
else if (TryGetBackgroundColor(parameter, out color))
{
newState.BackgroundColor = color;
// Don't set the background color if it matches the default background color.
// Skipping setting it improves appearance when row mouseover slightly changes color.
newState.BackgroundColor = (color != defaultBackgroundColor) ? color : null;
}
else if (parameter == DefaultBackgroundCode)
{
Expand Down Expand Up @@ -516,14 +518,14 @@ private static string ProcessStateChange(ParserState currentState, ParserState n
{
return state.ForegroundColor switch
{
ConsoleColor.Black => state.Bright ? "ansi-fg-brightblack" : "ansi-fg-black",
ConsoleColor.Blue => state.Bright ? "ansi-fg-brightblue" : "ansi-fg-blue",
ConsoleColor.Cyan => state.Bright ? "ansi-fg-brightcyan" : "ansi-fg-cyan",
ConsoleColor.Green => state.Bright ? "ansi-fg-brightgreen" : "ansi-fg-green",
ConsoleColor.Black => state.Bright ? "ansi-fg-brightblack" : "ansi-fg-black",
ConsoleColor.Blue => state.Bright ? "ansi-fg-brightblue" : "ansi-fg-blue",
ConsoleColor.Cyan => state.Bright ? "ansi-fg-brightcyan" : "ansi-fg-cyan",
ConsoleColor.Green => state.Bright ? "ansi-fg-brightgreen" : "ansi-fg-green",
ConsoleColor.Magenta => state.Bright ? "ansi-fg-brightmagenta" : "ansi-fg-magenta",
ConsoleColor.Red => state.Bright ? "ansi-fg-brightred" : "ansi-fg-red",
ConsoleColor.White => state.Bright ? "ansi-fg-brightwhite" : "ansi-fg-white",
ConsoleColor.Yellow => state.Bright ? "ansi-fg-brightyellow" : "ansi-fg-yellow",
ConsoleColor.Red => state.Bright ? "ansi-fg-brightred" : "ansi-fg-red",
ConsoleColor.White => state.Bright ? "ansi-fg-brightwhite" : "ansi-fg-white",
ConsoleColor.Yellow => state.Bright ? "ansi-fg-brightyellow" : "ansi-fg-yellow",
_ => ""
};
}
Expand All @@ -532,14 +534,14 @@ private static string ProcessStateChange(ParserState currentState, ParserState n
{
return state.BackgroundColor switch
{
ConsoleColor.Black => "ansi-bg-black",
ConsoleColor.Blue => "ansi-bg-blue",
ConsoleColor.Cyan => "ansi-bg-cyan",
ConsoleColor.Green => "ansi-bg-green",
ConsoleColor.Black => "ansi-bg-black",
ConsoleColor.Blue => "ansi-bg-blue",
ConsoleColor.Cyan => "ansi-bg-cyan",
ConsoleColor.Green => "ansi-bg-green",
ConsoleColor.Magenta => "ansi-bg-magenta",
ConsoleColor.Red => "ansi-bg-red",
ConsoleColor.White => "ansi-bg-white",
ConsoleColor.Yellow => "ansi-bg-yellow",
ConsoleColor.Red => "ansi-bg-red",
ConsoleColor.White => "ansi-bg-white",
ConsoleColor.Yellow => "ansi-bg-yellow",
_ => ""
};
}
Expand Down
8 changes: 7 additions & 1 deletion src/Aspire.Dashboard/ConsoleLogs/LogParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ namespace Aspire.Dashboard.ConsoleLogs;

internal sealed class LogParser
{
private readonly ConsoleColor _defaultBackgroundColor;
private AnsiParser.ParserState? _residualState;

public LogParser(ConsoleColor defaultBackgroundColor)
{
_defaultBackgroundColor = defaultBackgroundColor;
}

public LogEntry CreateLogEntry(string rawText, bool isErrorOutput)
{
// Several steps to do here:
Expand Down Expand Up @@ -39,7 +45,7 @@ public LogEntry CreateLogEntry(string rawText, bool isErrorOutput)
var updatedText = WebUtility.HtmlEncode(s);

// 3b. Parse the content to look for ANSI Control Sequences and color them if possible
var conversionResult = AnsiParser.ConvertToHtml(updatedText, _residualState);
var conversionResult = AnsiParser.ConvertToHtml(updatedText, _residualState, _defaultBackgroundColor);
updatedText = conversionResult.ConvertedText;
_residualState = conversionResult.ResidualState;

Expand Down
19 changes: 17 additions & 2 deletions tests/Aspire.Dashboard.Tests/ConsoleLogsTests/LogEntriesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private static LogEntries CreateLogEntries(int? maximumEntryCount = null, int? b

private static void AddLogLine(LogEntries logEntries, string content, bool isError)
{
var logParser = new LogParser();
var logParser = new LogParser(ConsoleColor.Black);
var logEntry = logParser.CreateLogEntry(content, isError);
logEntries.InsertSorted(logEntry);
}
Expand Down Expand Up @@ -268,12 +268,27 @@ public void InsertSorted_TrimsToMaximumEntryCount_OutOfOrder()
public void CreateLogEntry_AnsiAndUrl_HasUrlAnchor()
{
// Arrange
var parser = new LogParser();
var parser = new LogParser(ConsoleColor.Black);

// Act
var entry = parser.CreateLogEntry("\x1b[36mhttps://www.example.com\u001b[0m", isErrorOutput: false);

// Assert
Assert.Equal("<span class=\"ansi-fg-cyan\"></span><a target=\"_blank\" href=\"https://www.example.com\" rel=\"noopener noreferrer nofollow\">https://www.example.com</a>", entry.Content);
}

[Theory]
[InlineData(ConsoleColor.Black, @"<span class=""ansi-fg-green"">info</span>: LoggerName")]
[InlineData(ConsoleColor.Blue, @"<span class=""ansi-fg-green ansi-bg-black"">info</span>: LoggerName")]
public void CreateLogEntry_DefaultBackgroundColor_SkipMatchingColor(ConsoleColor defaultBackgroundColor, string output)
{
// Arrange
var parser = new LogParser(defaultBackgroundColor);

// Act
var entry = parser.CreateLogEntry("\u001b[40m\u001b[32minfo\u001b[39m\u001b[22m\u001b[49m: LoggerName", isErrorOutput: false);

// Assert
Assert.Equal(output, entry.Content);
}
}