From 93b60a937cf1ae044fbfcd8fd3e0124413fa337a Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 14 Jul 2023 14:00:58 -0500 Subject: [PATCH 1/5] Pull NodesFrame to own file --- src/MSBuild/TerminalLogger/NodesFrame.cs | 141 +++++++++++++++++++ src/MSBuild/TerminalLogger/TerminalLogger.cs | 134 +----------------- 2 files changed, 142 insertions(+), 133 deletions(-) create mode 100644 src/MSBuild/TerminalLogger/NodesFrame.cs diff --git a/src/MSBuild/TerminalLogger/NodesFrame.cs b/src/MSBuild/TerminalLogger/NodesFrame.cs new file mode 100644 index 00000000000..f15ac177a36 --- /dev/null +++ b/src/MSBuild/TerminalLogger/NodesFrame.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +using System.Text; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Logging.TerminalLogger; + +/// +/// Capture states on nodes to be rendered on display. +/// +internal sealed class NodesFrame +{ + private readonly List _nodeStrings = new(); + private readonly StringBuilder _renderBuilder = new(); + + public int Width { get; } + public int Height { get; } + public int NodesCount { get; private set; } + + public NodesFrame(NodeStatus?[] nodes, int width, int height) + { + Width = width; + Height = height; + Init(nodes); + } + + public string NodeString(int index) + { + if (index >= NodesCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _nodeStrings[index]; + } + + private void Init(NodeStatus?[] nodes) + { + int i = 0; + foreach (NodeStatus? n in nodes) + { + if (n is null) + { + continue; + } + string str = n.ToString(); + + if (i < _nodeStrings.Count) + { + _nodeStrings[i] = str; + } + else + { + _nodeStrings.Add(str); + } + i++; + + // We cant output more than what fits on screen + // -2 because cursor command F cant reach, in Windows Terminal, very 1st line, and last line is empty caused by very last WriteLine + if (i >= Height - 2) + { + break; + } + } + + NodesCount = i; + } + + private ReadOnlySpan FitToWidth(ReadOnlySpan input) + { + return input.Slice(0, Math.Min(input.Length, Width - 1)); + } + + /// + /// Render VT100 string to update from current to next frame. + /// + public string Render(NodesFrame previousFrame) + { + StringBuilder sb = _renderBuilder; + sb.Clear(); + + int i = 0; + for (; i < NodesCount; i++) + { + var needed = FitToWidth(NodeString(i).AsSpan()); + + // Do we have previous node string to compare with? + if (previousFrame.NodesCount > i) + { + var previous = FitToWidth(previousFrame.NodeString(i).AsSpan()); + + if (!previous.SequenceEqual(needed)) + { + int commonPrefixLen = previous.CommonPrefixLength(needed); + + if (commonPrefixLen != 0 && needed.Slice(0, commonPrefixLen).IndexOf('\x1b') == -1) + { + // no escape codes, so can trivially skip substrings + sb.Append($"{AnsiCodes.CSI}{commonPrefixLen}{AnsiCodes.MoveForward}"); + sb.Append(needed.Slice(commonPrefixLen)); + } + else + { + sb.Append(needed); + } + + // Shall we clear rest of line + if (needed.Length < previous.Length) + { + sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}"); + } + } + } + else + { + // From now on we have to simply WriteLine + sb.Append(needed); + } + + // Next line + sb.AppendLine(); + } + + // clear no longer used lines + if (i < previousFrame.NodesCount) + { + sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInDisplay}"); + } + + return sb.ToString(); + } + + public void Clear() + { + NodesCount = 0; + } +} diff --git a/src/MSBuild/TerminalLogger/TerminalLogger.cs b/src/MSBuild/TerminalLogger/TerminalLogger.cs index 9743b489917..3a8b6d7aaa1 100644 --- a/src/MSBuild/TerminalLogger/TerminalLogger.cs +++ b/src/MSBuild/TerminalLogger/TerminalLogger.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -216,7 +215,7 @@ public string Parameters /// public void Initialize(IEventSource eventSource, int nodeCount) { - // When MSBUILDNOINPROCNODE enabled, NodeId's reported by build start with 2. We need to reserve an extra spot for this case. + // When MSBUILDNOINPROCNODE enabled, NodeId's reported by build start with 2. We need to reserve an extra spot for this case. _nodes = new NodeStatus[nodeCount + 1]; Initialize(eventSource); @@ -724,137 +723,6 @@ private void EraseNodes() _currentFrame.Clear(); } - /// - /// Capture states on nodes to be rendered on display. - /// - private sealed class NodesFrame - { - private readonly List _nodeStrings = new(); - private readonly StringBuilder _renderBuilder = new(); - - public int Width { get; } - public int Height { get; } - public int NodesCount { get; private set; } - - public NodesFrame(NodeStatus?[] nodes, int width, int height) - { - Width = width; - Height = height; - Init(nodes); - } - - public string NodeString(int index) - { - if (index >= NodesCount) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - return _nodeStrings[index]; - } - - private void Init(NodeStatus?[] nodes) - { - int i = 0; - foreach (NodeStatus? n in nodes) - { - if (n is null) - { - continue; - } - string str = n.ToString(); - - if (i < _nodeStrings.Count) - { - _nodeStrings[i] = str; - } - else - { - _nodeStrings.Add(str); - } - i++; - - // We cant output more than what fits on screen - // -2 because cursor command F cant reach, in Windows Terminal, very 1st line, and last line is empty caused by very last WriteLine - if (i >= Height - 2) - { - break; - } - } - - NodesCount = i; - } - - private ReadOnlySpan FitToWidth(ReadOnlySpan input) - { - return input.Slice(0, Math.Min(input.Length, Width - 1)); - } - - /// - /// Render VT100 string to update from current to next frame. - /// - public string Render(NodesFrame previousFrame) - { - StringBuilder sb = _renderBuilder; - sb.Clear(); - - int i = 0; - for (; i < NodesCount; i++) - { - var needed = FitToWidth(NodeString(i).AsSpan()); - - // Do we have previous node string to compare with? - if (previousFrame.NodesCount > i) - { - var previous = FitToWidth(previousFrame.NodeString(i).AsSpan()); - - if (!previous.SequenceEqual(needed)) - { - int commonPrefixLen = previous.CommonPrefixLength(needed); - - if (commonPrefixLen != 0 && needed.Slice(0, commonPrefixLen).IndexOf('\x1b') == -1) - { - // no escape codes, so can trivially skip substrings - sb.Append($"{AnsiCodes.CSI}{commonPrefixLen}{AnsiCodes.MoveForward}"); - sb.Append(needed.Slice(commonPrefixLen)); - } - else - { - sb.Append(needed); - } - - // Shall we clear rest of line - if (needed.Length < previous.Length) - { - sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}"); - } - } - } - else - { - // From now on we have to simply WriteLine - sb.Append(needed); - } - - // Next line - sb.AppendLine(); - } - - // clear no longer used lines - if (i < previousFrame.NodesCount) - { - sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInDisplay}"); - } - - return sb.ToString(); - } - - public void Clear() - { - NodesCount = 0; - } - } - #endregion #region Helpers From b78c640272d4e479b0ee54ee6192c4243c5bf69e Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 14 Jul 2023 14:13:41 -0500 Subject: [PATCH 2/5] File for NodeStatus --- src/MSBuild/TerminalLogger/NodeStatus.cs | 31 ++++++++++++++++++++ src/MSBuild/TerminalLogger/TerminalLogger.cs | 31 ++------------------ 2 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 src/MSBuild/TerminalLogger/NodeStatus.cs diff --git a/src/MSBuild/TerminalLogger/NodeStatus.cs b/src/MSBuild/TerminalLogger/NodeStatus.cs new file mode 100644 index 00000000000..f238cb0aa8d --- /dev/null +++ b/src/MSBuild/TerminalLogger/NodeStatus.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Logging.TerminalLogger; + +/// +/// Encapsulates the per-node data shown in live node output. +/// +internal record NodeStatus(string Project, string? TargetFramework, string Target, Stopwatch Stopwatch) +{ + public override string ToString() + { + string duration = Stopwatch.Elapsed.TotalSeconds.ToString("F1"); + + return string.IsNullOrEmpty(TargetFramework) + ? ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_NoTF", + TerminalLogger.Indentation, + Project, + Target, + duration) + : ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_WithTF", + TerminalLogger.Indentation, + Project, + AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor), + Target, + duration); + } +} diff --git a/src/MSBuild/TerminalLogger/TerminalLogger.cs b/src/MSBuild/TerminalLogger/TerminalLogger.cs index 3a8b6d7aaa1..4cf1dd04b7d 100644 --- a/src/MSBuild/TerminalLogger/TerminalLogger.cs +++ b/src/MSBuild/TerminalLogger/TerminalLogger.cs @@ -1,9 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.Build.Framework; @@ -51,36 +50,12 @@ public ProjectContext(BuildEventContext context) { } } - /// - /// Encapsulates the per-node data shown in live node output. - /// - internal record NodeStatus(string Project, string? TargetFramework, string Target, Stopwatch Stopwatch) - { - public override string ToString() - { - string duration = Stopwatch.Elapsed.TotalSeconds.ToString("F1"); - - return string.IsNullOrEmpty(TargetFramework) - ? ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_NoTF", - Indentation, - Project, - Target, - duration) - : ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_WithTF", - Indentation, - Project, - AnsiCodes.Colorize(TargetFramework, TargetFrameworkColor), - Target, - duration); - } - } - /// /// The indentation to use for all build output. /// - private const string Indentation = " "; + internal const string Indentation = " "; - private const TerminalColor TargetFrameworkColor = TerminalColor.Cyan; + internal const TerminalColor TargetFrameworkColor = TerminalColor.Cyan; /// /// Protects access to state shared between the logger callbacks and the rendering thread. From 566d823495404e2ede10ffaa1c2e6554ad38195f Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 14 Jul 2023 15:16:20 -0500 Subject: [PATCH 3/5] Preserve NodeStatus in frame Instead of the rendered string, with its invisible control characters. --- src/MSBuild/Resources/Strings.resx | 6 ++ src/MSBuild/Resources/xlf/Strings.cs.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.de.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.es.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.fr.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.it.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.ja.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.ko.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.pl.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.pt-BR.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.ru.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.tr.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf | 7 ++ src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf | 7 ++ src/MSBuild/TerminalLogger/NodesFrame.cs | 66 +++++++------------ 15 files changed, 121 insertions(+), 42 deletions(-) diff --git a/src/MSBuild/Resources/Strings.resx b/src/MSBuild/Resources/Strings.resx index a753a09c5b6..f275607e720 100644 --- a/src/MSBuild/Resources/Strings.resx +++ b/src/MSBuild/Resources/Strings.resx @@ -1557,6 +1557,12 @@ {4}: duration in seconds with 1 decimal point + + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + Build failed. Properties, Items, and Target results cannot be obtained. See details in stderr above. diff --git a/src/MSBuild/Resources/xlf/Strings.cs.xlf b/src/MSBuild/Resources/xlf/Strings.cs.xlf index 9a4dee64212..d948c5af693 100644 --- a/src/MSBuild/Resources/xlf/Strings.cs.xlf +++ b/src/MSBuild/Resources/xlf/Strings.cs.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: Přepínač -noAutoResponse nelze zadat v souboru automatických odpovědí MSBuild.rsp ani v žádném jiném souboru odpovědí, na který se v souboru automatických odpovědí odkazuje. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.de.xlf b/src/MSBuild/Resources/xlf/Strings.de.xlf index 0a93c2b4b6d..b6aca3913f7 100644 --- a/src/MSBuild/Resources/xlf/Strings.de.xlf +++ b/src/MSBuild/Resources/xlf/Strings.de.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: Der Schalter "-noAutoResponse" kann weder in der automatischen Antwortdatei "MSBuild.rsp" noch in einer anderen Antwortdatei verwendet werden, auf die die automatische Antwortdatei verweist. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.es.xlf b/src/MSBuild/Resources/xlf/Strings.es.xlf index cfa87dc7547..477d6d234ae 100644 --- a/src/MSBuild/Resources/xlf/Strings.es.xlf +++ b/src/MSBuild/Resources/xlf/Strings.es.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: El modificador -noAutoResponse no puede especificarse en el archivo de respuesta automática MSBuild.rsp ni en ningún archivo de respuesta al que el archivo de respuesta automática haga referencia. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.fr.xlf b/src/MSBuild/Resources/xlf/Strings.fr.xlf index b4b2ae8bfde..420d4cba27b 100644 --- a/src/MSBuild/Resources/xlf/Strings.fr.xlf +++ b/src/MSBuild/Resources/xlf/Strings.fr.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: Impossible de spécifier le commutateur -noAutoResponse dans le fichier réponse automatique MSBuild.rsp, ni dans aucun autre fichier réponse référencé par le fichier réponse automatique. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.it.xlf b/src/MSBuild/Resources/xlf/Strings.it.xlf index d5b83a7548a..65f304a9d00 100644 --- a/src/MSBuild/Resources/xlf/Strings.it.xlf +++ b/src/MSBuild/Resources/xlf/Strings.it.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: non è possibile specificare l'opzione -noAutoResponse nel file di risposta automatica MSBuild.rsp o in file di risposta a cui il file di risposta automatica fa riferimento. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.ja.xlf b/src/MSBuild/Resources/xlf/Strings.ja.xlf index b10dcf784c2..0963e9babdf 100644 --- a/src/MSBuild/Resources/xlf/Strings.ja.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ja.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: MSBuild.rsp 自動応答ファイルや、自動応答ファイルによって参照される応答ファイルに -noAutoResponse スイッチを指定することはできません。 {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.ko.xlf b/src/MSBuild/Resources/xlf/Strings.ko.xlf index d6d76449a65..6de25567c37 100644 --- a/src/MSBuild/Resources/xlf/Strings.ko.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ko.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: MSBuild.rsp 자동 지시 파일과 자동 지시 파일에서 참조하는 모든 지시 파일에는 -noAutoResponse 스위치를 지정할 수 없습니다. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.pl.xlf b/src/MSBuild/Resources/xlf/Strings.pl.xlf index 2a48d1d5450..9dd5d5ed8c1 100644 --- a/src/MSBuild/Resources/xlf/Strings.pl.xlf +++ b/src/MSBuild/Resources/xlf/Strings.pl.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: przełącznika -noAutoResponse nie można określić w pliku autoodpowiedzi MSBuild.rsp ani w żadnym pliku odpowiedzi, do którego odwołuje się plik autoodpowiedzi. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf index 15c9b624a84..633e44eed9d 100644 --- a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf +++ b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: A opção /noAutoResponse não pode ser especificada no arquivo de resposta automática MSBuild.rsp nem em qualquer arquivo de resposta usado como referência para o arquivo de resposta automática. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.ru.xlf b/src/MSBuild/Resources/xlf/Strings.ru.xlf index 6dca92a8e84..c11f88d0caf 100644 --- a/src/MSBuild/Resources/xlf/Strings.ru.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ru.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: ключ noAutoResponse не может быть указан в файле автоответа MSBuild.rsp или в любом другом файле ответа, на который файл автоответа ссылается. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.tr.xlf b/src/MSBuild/Resources/xlf/Strings.tr.xlf index ff6f95f2eae..a7443f21dc1 100644 --- a/src/MSBuild/Resources/xlf/Strings.tr.xlf +++ b/src/MSBuild/Resources/xlf/Strings.tr.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: -noAutoResponse anahtarı, MSBuild.rsp otomatik yanıt dosyasında ve bu dosyanın başvuruda bulunduğu herhangi bir yanıt dosyasında belirtilemez. {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf index db252831300..76bbbb211d9 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: 不能在 MSBuild.rsp 自动响应文件中或由该自动响应文件引用的任何响应文件中指定 -noAutoResponse 开关。 {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf index f5907cf51e5..e1dc55e759d 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf @@ -77,6 +77,13 @@ MSBUILD : error MSB1027: -noAutoResponse 參數不能在 MSBuild.rsp 自動回應檔中指定,也不能在自動回應檔所參考的任何回應檔中指定。 {StrBegin="MSBUILD : error MSB1027: "}LOCALIZATION: The prefix "MSBUILD : error MSBxxxx:", "-noAutoResponse" and "MSBuild.rsp" should not be localized. + + ({0:F1}s) + ({0:F1}s) + + {0}: duration in seconds with 1 decimal point + + -question (Experimental) Question whether there is any build work. diff --git a/src/MSBuild/TerminalLogger/NodesFrame.cs b/src/MSBuild/TerminalLogger/NodesFrame.cs index f15ac177a36..d2613a41618 100644 --- a/src/MSBuild/TerminalLogger/NodesFrame.cs +++ b/src/MSBuild/TerminalLogger/NodesFrame.cs @@ -14,7 +14,7 @@ namespace Microsoft.Build.Logging.TerminalLogger; /// internal sealed class NodesFrame { - private readonly List _nodeStrings = new(); + private readonly NodeStatus[] _nodes; private readonly StringBuilder _renderBuilder = new(); public int Width { get; } @@ -25,54 +25,36 @@ public NodesFrame(NodeStatus?[] nodes, int width, int height) { Width = width; Height = height; - Init(nodes); - } - public string NodeString(int index) - { - if (index >= NodesCount) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } + _nodes = new NodeStatus[nodes.Length]; - return _nodeStrings[index]; + foreach (NodeStatus? status in nodes) + { + if (status is not null) + { + _nodes[NodesCount++] = status; + } + } } - private void Init(NodeStatus?[] nodes) + private ReadOnlySpan RenderNodeStatus(NodeStatus status) { - int i = 0; - foreach (NodeStatus? n in nodes) - { - if (n is null) - { - continue; - } - string str = n.ToString(); + string durationString = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( + "DurationDisplay", + status.Stopwatch.Elapsed.TotalSeconds); - if (i < _nodeStrings.Count) - { - _nodeStrings[i] = str; - } - else - { - _nodeStrings.Add(str); - } - i++; + int totalWidth = TerminalLogger.Indentation.Length + + status.Project.Length + 1 + + (status.TargetFramework?.Length ?? -1) + 1 + + status.Target.Length + 1 + + durationString.Length; - // We cant output more than what fits on screen - // -2 because cursor command F cant reach, in Windows Terminal, very 1st line, and last line is empty caused by very last WriteLine - if (i >= Height - 2) - { - break; - } + if (Width > totalWidth) + { + return $"{TerminalLogger.Indentation}{status.Project} {status.TargetFramework} {status.Target} {durationString}".AsSpan(); } - NodesCount = i; - } - - private ReadOnlySpan FitToWidth(ReadOnlySpan input) - { - return input.Slice(0, Math.Min(input.Length, Width - 1)); + return string.Empty.AsSpan(); } /// @@ -86,12 +68,12 @@ public string Render(NodesFrame previousFrame) int i = 0; for (; i < NodesCount; i++) { - var needed = FitToWidth(NodeString(i).AsSpan()); + var needed = RenderNodeStatus(_nodes[i]); // Do we have previous node string to compare with? if (previousFrame.NodesCount > i) { - var previous = FitToWidth(previousFrame.NodeString(i).AsSpan()); + var previous = RenderNodeStatus(previousFrame._nodes[i]); if (!previous.SequenceEqual(needed)) { From e50041a57b698ea069d226a2caab0d4e8b89f7ca Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Mon, 17 Jul 2023 15:31:34 -0500 Subject: [PATCH 4/5] De-record NodeStatus I wanted control over equality, and we don't need the auto-generated stuff. --- src/MSBuild/TerminalLogger/NodeStatus.cs | 29 +++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/MSBuild/TerminalLogger/NodeStatus.cs b/src/MSBuild/TerminalLogger/NodeStatus.cs index f238cb0aa8d..3b5d91ccb3e 100644 --- a/src/MSBuild/TerminalLogger/NodeStatus.cs +++ b/src/MSBuild/TerminalLogger/NodeStatus.cs @@ -9,8 +9,30 @@ namespace Microsoft.Build.Logging.TerminalLogger; /// /// Encapsulates the per-node data shown in live node output. /// -internal record NodeStatus(string Project, string? TargetFramework, string Target, Stopwatch Stopwatch) +internal class NodeStatus { + public string Project { get; } + public string? TargetFramework { get; } + public string Target { get; } + public Stopwatch Stopwatch { get; } + + public NodeStatus(string project, string? targetFramework, string target, Stopwatch stopwatch) + { + Project = project; + TargetFramework = targetFramework; + Target = target; + Stopwatch = stopwatch; + } + + /// + /// Equality is based on the project, target framework, and target, but NOT the elapsed time. + /// + public override bool Equals(object? obj) => + obj is NodeStatus status && + Project == status.Project && + TargetFramework == status.TargetFramework && + Target == status.Target; + public override string ToString() { string duration = Stopwatch.Elapsed.TotalSeconds.ToString("F1"); @@ -28,4 +50,9 @@ public override string ToString() Target, duration); } + + public override int GetHashCode() + { + throw new System.NotImplementedException(); + } } From 8f8dd687e999b35f6c5dfe366fbbcf455bae56d2 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Fri, 14 Jul 2023 16:46:15 -0500 Subject: [PATCH 5/5] Right justify output Move cursor right (it stops at end of line), then back to justify the output. This makes the localization less flexible since the formatting + justification is handled in code, but makes it a lot easier to understand. In the future reordering in translations would need to be handled fairly carefully. Avoid a bunch of overwrite problems by simplifiying the should-we-do-a-fancy-overwrite decision to "is just time tick" or "just overwrite it all". --- src/Framework/Logging/AnsiCodes.cs | 9 ++ src/MSBuild.UnitTests/NodeStatus_Tests.cs | 62 +++++++++++++ ...deStatus_Tests.EverythingFits.verified.txt | 1 + ...odeStatus_Tests.GoesToProject.verified.txt | 1 + ...ests.NamespaceIsTruncatedNext.verified.txt | 1 + ..._Tests.TargetIsTruncatedFirst.verified.txt | 1 + ...sWithNewTargetFramework.Linux.verified.txt | 4 +- ...tesWithNewTargetFramework.OSX.verified.txt | 4 +- ...ithNewTargetFramework.Windows.verified.txt | 4 +- ...isplayNodesShowsCurrent.Linux.verified.txt | 2 +- ....DisplayNodesShowsCurrent.OSX.verified.txt | 2 +- ...playNodesShowsCurrent.Windows.verified.txt | 2 +- src/MSBuild/Resources/Strings.resx | 21 ----- src/MSBuild/Resources/xlf/Strings.cs.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.de.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.es.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.fr.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.it.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.ja.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.ko.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.pl.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.pt-BR.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.ru.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.tr.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf | 23 ----- src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf | 23 ----- src/MSBuild/TerminalLogger/NodeStatus.cs | 4 +- src/MSBuild/TerminalLogger/NodesFrame.cs | 86 +++++++++++-------- 28 files changed, 135 insertions(+), 368 deletions(-) create mode 100644 src/MSBuild.UnitTests/NodeStatus_Tests.cs create mode 100644 src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.EverythingFits.verified.txt create mode 100644 src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.GoesToProject.verified.txt create mode 100644 src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.NamespaceIsTruncatedNext.verified.txt create mode 100644 src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.TargetIsTruncatedFirst.verified.txt diff --git a/src/Framework/Logging/AnsiCodes.cs b/src/Framework/Logging/AnsiCodes.cs index 016260d55f8..8466220026b 100644 --- a/src/Framework/Logging/AnsiCodes.cs +++ b/src/Framework/Logging/AnsiCodes.cs @@ -141,4 +141,13 @@ public static string MakeBold(string? s) return $"{CSI}{SetBold}{s}{SetDefaultColor}"; } + + public static string MoveCursorBackward(int count) => $"{CSI}{count}{MoveBackward}"; + + /// + /// Moves cursor to the specified column, or the rightmost column if is greater than the width of the terminal. + /// + /// Column index. + /// Control codes to set the desired position. + public static string SetCursorHorizontal(int column) => $"{CSI}{column}G"; } diff --git a/src/MSBuild.UnitTests/NodeStatus_Tests.cs b/src/MSBuild.UnitTests/NodeStatus_Tests.cs new file mode 100644 index 00000000000..50ae7b38a51 --- /dev/null +++ b/src/MSBuild.UnitTests/NodeStatus_Tests.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Microsoft.Build.Logging.TerminalLogger; + +using VerifyTests; +using VerifyXunit; +using Xunit; + +using static VerifyXunit.Verifier; + + +namespace Microsoft.Build.CommandLine.UnitTests; + +[UsesVerify] +public class NodeStatus_Tests +{ + private readonly NodeStatus _status = new("Namespace.Project", "TargetFramework", "Target", new()); + + public NodeStatus_Tests() + { + UseProjectRelativeDirectory("Snapshots"); + } + + [Fact] + public async Task EverythingFits() + { + NodesFrame frame = new(new[] { _status }, width: 80, height: 5); + + await Verify(frame.RenderNodeStatus(_status).ToString()); + } + + [Fact] + public async Task TargetIsTruncatedFirst() + { + NodesFrame frame = new(new[] { _status }, width: 45, height: 5); + + await Verify(frame.RenderNodeStatus(_status).ToString()); + } + + [Fact] + public async Task NamespaceIsTruncatedNext() + { + NodesFrame frame = new(new[] { _status }, width: 40, height: 5); + + await Verify(frame.RenderNodeStatus(_status).ToString()); + } + + [Fact] + public async Task GoesToProject() + { + NodesFrame frame = new(new[] { _status }, width: 10, height: 5); + + await Verify(frame.RenderNodeStatus(_status).ToString()); + } +} diff --git a/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.EverythingFits.verified.txt b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.EverythingFits.verified.txt new file mode 100644 index 00000000000..a889f734e14 --- /dev/null +++ b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.EverythingFits.verified.txt @@ -0,0 +1 @@ + Namespace.Project TargetFramework Target (0.0s) \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.GoesToProject.verified.txt b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.GoesToProject.verified.txt new file mode 100644 index 00000000000..74eb4993b40 --- /dev/null +++ b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.GoesToProject.verified.txt @@ -0,0 +1 @@ +Project \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.NamespaceIsTruncatedNext.verified.txt b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.NamespaceIsTruncatedNext.verified.txt new file mode 100644 index 00000000000..a06cd82177c --- /dev/null +++ b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.NamespaceIsTruncatedNext.verified.txt @@ -0,0 +1 @@ + Project TargetFramework  (0.0s) \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.TargetIsTruncatedFirst.verified.txt b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.TargetIsTruncatedFirst.verified.txt new file mode 100644 index 00000000000..014bb0cb3be --- /dev/null +++ b/src/MSBuild.UnitTests/Snapshots/NodeStatus_Tests.TargetIsTruncatedFirst.verified.txt @@ -0,0 +1 @@ + Namespace.Project TargetFramework  (0.0s) \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Linux.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Linux.verified.txt index 0777ef5cc91..fe12f4e9dec 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Linux.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Linux.verified.txt @@ -1,5 +1,5 @@ ]9;4;3;\[?25l - project tfName Build (0.0s) + project tfName Build (0.0s) [?25h[?25l - project tf2 Build (0.0s) + project tf2 Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.OSX.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.OSX.verified.txt index d860724e3ab..8d078e61f5c 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.OSX.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.OSX.verified.txt @@ -1,5 +1,5 @@ [?25l - project tfName Build (0.0s) + project tfName Build (0.0s) [?25h[?25l - project tf2 Build (0.0s) + project tf2 Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Windows.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Windows.verified.txt index 0777ef5cc91..fe12f4e9dec 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Windows.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesOverwritesWithNewTargetFramework.Windows.verified.txt @@ -1,5 +1,5 @@ ]9;4;3;\[?25l - project tfName Build (0.0s) + project tfName Build (0.0s) [?25h[?25l - project tf2 Build (0.0s) + project tf2 Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Linux.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Linux.verified.txt index d0cb5b914e0..1f7b782f2ef 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Linux.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Linux.verified.txt @@ -1,3 +1,3 @@ ]9;4;3;\[?25l - project Build (0.0s) + project Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.OSX.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.OSX.verified.txt index edce93c06c4..143745dea40 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.OSX.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.OSX.verified.txt @@ -1,3 +1,3 @@ [?25l - project Build (0.0s) + project Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Windows.verified.txt b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Windows.verified.txt index d0cb5b914e0..1f7b782f2ef 100644 --- a/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Windows.verified.txt +++ b/src/MSBuild.UnitTests/Snapshots/TerminalLogger_Tests.DisplayNodesShowsCurrent.Windows.verified.txt @@ -1,3 +1,3 @@ ]9;4;3;\[?25l - project Build (0.0s) + project Build (0.0s) [?25h \ No newline at end of file diff --git a/src/MSBuild/Resources/Strings.resx b/src/MSBuild/Resources/Strings.resx index f275607e720..98c41c4823c 100644 --- a/src/MSBuild/Resources/Strings.resx +++ b/src/MSBuild/Resources/Strings.resx @@ -1536,27 +1536,6 @@ {0}: VT100 coded hyperlink to project output directory - - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - ({0:F1}s) diff --git a/src/MSBuild/Resources/xlf/Strings.cs.xlf b/src/MSBuild/Resources/xlf/Strings.cs.xlf index d948c5af693..55929f8714c 100644 --- a/src/MSBuild/Resources/xlf/Strings.cs.xlf +++ b/src/MSBuild/Resources/xlf/Strings.cs.xlf @@ -1495,29 +1495,6 @@ Když se nastaví na MessageUponIsolationViolation (nebo jeho krátký Proces = {0} - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.de.xlf b/src/MSBuild/Resources/xlf/Strings.de.xlf index b6aca3913f7..226e0960175 100644 --- a/src/MSBuild/Resources/xlf/Strings.de.xlf +++ b/src/MSBuild/Resources/xlf/Strings.de.xlf @@ -1483,29 +1483,6 @@ Dieses Protokollierungsformat ist standardmäßig aktiviert. Prozess = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.es.xlf b/src/MSBuild/Resources/xlf/Strings.es.xlf index 477d6d234ae..1846d8646a1 100644 --- a/src/MSBuild/Resources/xlf/Strings.es.xlf +++ b/src/MSBuild/Resources/xlf/Strings.es.xlf @@ -1489,29 +1489,6 @@ Esta marca es experimental y puede que no funcione según lo previsto. Proceso: "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.fr.xlf b/src/MSBuild/Resources/xlf/Strings.fr.xlf index 420d4cba27b..f4d88741a76 100644 --- a/src/MSBuild/Resources/xlf/Strings.fr.xlf +++ b/src/MSBuild/Resources/xlf/Strings.fr.xlf @@ -1482,29 +1482,6 @@ Remarque : verbosité des enregistreurs d’événements de fichiers Processus = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.it.xlf b/src/MSBuild/Resources/xlf/Strings.it.xlf index 65f304a9d00..12e7aa2ac94 100644 --- a/src/MSBuild/Resources/xlf/Strings.it.xlf +++ b/src/MSBuild/Resources/xlf/Strings.it.xlf @@ -1493,29 +1493,6 @@ Nota: livello di dettaglio dei logger di file Processo = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.ja.xlf b/src/MSBuild/Resources/xlf/Strings.ja.xlf index 0963e9babdf..cd05368111a 100644 --- a/src/MSBuild/Resources/xlf/Strings.ja.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ja.xlf @@ -1482,29 +1482,6 @@ プロセス = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3} 秒) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4} 秒) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3} 秒) diff --git a/src/MSBuild/Resources/xlf/Strings.ko.xlf b/src/MSBuild/Resources/xlf/Strings.ko.xlf index 6de25567c37..dfa81b71e90 100644 --- a/src/MSBuild/Resources/xlf/Strings.ko.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ko.xlf @@ -1482,29 +1482,6 @@ 프로세스 = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}초) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}초) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}초) diff --git a/src/MSBuild/Resources/xlf/Strings.pl.xlf b/src/MSBuild/Resources/xlf/Strings.pl.xlf index 9dd5d5ed8c1..133760011df 100644 --- a/src/MSBuild/Resources/xlf/Strings.pl.xlf +++ b/src/MSBuild/Resources/xlf/Strings.pl.xlf @@ -1493,29 +1493,6 @@ Ta flaga jest eksperymentalna i może nie działać zgodnie z oczekiwaniami. Proces = „{0}” - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf index 633e44eed9d..247e14490ef 100644 --- a/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf +++ b/src/MSBuild/Resources/xlf/Strings.pt-BR.xlf @@ -1483,29 +1483,6 @@ arquivo de resposta. Processo = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}s) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}s) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}s) diff --git a/src/MSBuild/Resources/xlf/Strings.ru.xlf b/src/MSBuild/Resources/xlf/Strings.ru.xlf index c11f88d0caf..1d89ce20f45 100644 --- a/src/MSBuild/Resources/xlf/Strings.ru.xlf +++ b/src/MSBuild/Resources/xlf/Strings.ru.xlf @@ -1481,29 +1481,6 @@ Процесс = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3} с) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4} с) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3} с) diff --git a/src/MSBuild/Resources/xlf/Strings.tr.xlf b/src/MSBuild/Resources/xlf/Strings.tr.xlf index a7443f21dc1..d3fc5c7e9ed 100644 --- a/src/MSBuild/Resources/xlf/Strings.tr.xlf +++ b/src/MSBuild/Resources/xlf/Strings.tr.xlf @@ -1486,29 +1486,6 @@ İşlem = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}sn) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3}({4}sn) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}sn) diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf index 76bbbb211d9..12f6f98c04d 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf @@ -1482,29 +1482,6 @@ 进程 = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3}) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4}) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3}) diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf index e1dc55e759d..b603ad790a6 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf @@ -1482,29 +1482,6 @@ 流程 = "{0}" - - {0}{1} {2} ({3}s) - {0}{1} {2} ({3} 秒) - - Project building. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target - {3}: duration in seconds with 1 decimal point - - - - {0}{1} {2} {3} ({4}s) - {0}{1} {2} {3} ({4} 秒) - - Project building including target framework information. - {0}: indentation - few spaces to visually indent row - {1}: project name - {2}: target framework - {3}: target - {4}: duration in seconds with 1 decimal point - - {0}{1} {2} ({3}s) {0}{1} {2} ({3} 秒) diff --git a/src/MSBuild/TerminalLogger/NodeStatus.cs b/src/MSBuild/TerminalLogger/NodeStatus.cs index 3b5d91ccb3e..c82a3d106d7 100644 --- a/src/MSBuild/TerminalLogger/NodeStatus.cs +++ b/src/MSBuild/TerminalLogger/NodeStatus.cs @@ -38,12 +38,12 @@ public override string ToString() string duration = Stopwatch.Elapsed.TotalSeconds.ToString("F1"); return string.IsNullOrEmpty(TargetFramework) - ? ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_NoTF", + ? string.Format("{0}{1} {2} ({3}s)", TerminalLogger.Indentation, Project, Target, duration) - : ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectBuilding_WithTF", + : string.Format("{0}{1} {2} {3} ({4}s)", TerminalLogger.Indentation, Project, AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor), diff --git a/src/MSBuild/TerminalLogger/NodesFrame.cs b/src/MSBuild/TerminalLogger/NodesFrame.cs index d2613a41618..144288950fa 100644 --- a/src/MSBuild/TerminalLogger/NodesFrame.cs +++ b/src/MSBuild/TerminalLogger/NodesFrame.cs @@ -14,7 +14,10 @@ namespace Microsoft.Build.Logging.TerminalLogger; /// internal sealed class NodesFrame { + private const int MaxColumn = 120; + private readonly NodeStatus[] _nodes; + private readonly StringBuilder _renderBuilder = new(); public int Width { get; } @@ -23,38 +26,58 @@ internal sealed class NodesFrame public NodesFrame(NodeStatus?[] nodes, int width, int height) { - Width = width; + Width = Math.Min(width, MaxColumn); Height = height; _nodes = new NodeStatus[nodes.Length]; - foreach (NodeStatus? status in nodes) + foreach (NodeStatus? status in nodes) + { + if (status is not null) { - if (status is not null) - { - _nodes[NodesCount++] = status; - } + _nodes[NodesCount++] = status; } + } } - private ReadOnlySpan RenderNodeStatus(NodeStatus status) + internal ReadOnlySpan RenderNodeStatus(NodeStatus status) { string durationString = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "DurationDisplay", status.Stopwatch.Elapsed.TotalSeconds); - int totalWidth = TerminalLogger.Indentation.Length + - status.Project.Length + 1 + - (status.TargetFramework?.Length ?? -1) + 1 + - status.Target.Length + 1 + - durationString.Length; + string project = status.Project; + string? targetFramework = status.TargetFramework; + string target = status.Target; + + int renderedWidth = Length(durationString, project, targetFramework, target); - if (Width > totalWidth) + if (renderedWidth > Width) { - return $"{TerminalLogger.Indentation}{status.Project} {status.TargetFramework} {status.Target} {durationString}".AsSpan(); + renderedWidth -= target.Length; + target = string.Empty; + + if (renderedWidth > Width) + { + int lastDotInProject = project.LastIndexOf('.'); + renderedWidth -= lastDotInProject; + project = project.Substring(lastDotInProject + 1); + + if (renderedWidth > Width) + { + return project.AsSpan(); + } + } } - return string.Empty.AsSpan(); + return $"{TerminalLogger.Indentation}{project}{(targetFramework is null ? string.Empty : " ")}{AnsiCodes.Colorize(targetFramework, TerminalLogger.TargetFrameworkColor)} {AnsiCodes.SetCursorHorizontal(MaxColumn)}{AnsiCodes.MoveCursorBackward(target.Length + durationString.Length + 1)}{target} {durationString}".AsSpan(); + + static int Length(string durationString, string project, string? targetFramework, string target) => + TerminalLogger.Indentation.Length + + project.Length + 1 + + (targetFramework?.Length ?? -1) + 1 + + target.Length + 1 + + durationString.Length; } /// @@ -68,33 +91,22 @@ public string Render(NodesFrame previousFrame) int i = 0; for (; i < NodesCount; i++) { - var needed = RenderNodeStatus(_nodes[i]); + ReadOnlySpan needed = RenderNodeStatus(_nodes[i]); // Do we have previous node string to compare with? if (previousFrame.NodesCount > i) { - var previous = RenderNodeStatus(previousFrame._nodes[i]); - - if (!previous.SequenceEqual(needed)) + if (previousFrame._nodes[i] == _nodes[i]) + { + // Same everything except time + string durationString = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("DurationDisplay", _nodes[i].Stopwatch.Elapsed.TotalSeconds); + sb.Append($"{AnsiCodes.SetCursorHorizontal(MaxColumn)}{AnsiCodes.MoveCursorBackward(durationString.Length)}{durationString}"); + } + else { - int commonPrefixLen = previous.CommonPrefixLength(needed); - - if (commonPrefixLen != 0 && needed.Slice(0, commonPrefixLen).IndexOf('\x1b') == -1) - { - // no escape codes, so can trivially skip substrings - sb.Append($"{AnsiCodes.CSI}{commonPrefixLen}{AnsiCodes.MoveForward}"); - sb.Append(needed.Slice(commonPrefixLen)); - } - else - { - sb.Append(needed); - } - - // Shall we clear rest of line - if (needed.Length < previous.Length) - { - sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}"); - } + // TODO: check components to figure out skips and optimize this + sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}"); + sb.Append(needed); } } else