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 [36;1mTargetFramework[m [120G[13DTarget (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 [36;1mTargetFramework[m [120G[7D (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 [36;1mTargetFramework[m [120G[7D (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[1F
- project [36;1mtfName[m Build (0.0s)
+ project [36;1mtfName[m [120G[12DBuild (0.0s)
[?25h[?25l[2F
- project [36;1mtf2[m Build (0.0s)[K
+[K project [36;1mtf2[m [120G[12DBuild (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[1F
- project [36;1mtfName[m Build (0.0s)
+ project [36;1mtfName[m [120G[12DBuild (0.0s)
[?25h[?25l[2F
- project [36;1mtf2[m Build (0.0s)[K
+[K project [36;1mtf2[m [120G[12DBuild (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[1F
- project [36;1mtfName[m Build (0.0s)
+ project [36;1mtfName[m [120G[12DBuild (0.0s)
[?25h[?25l[2F
- project [36;1mtf2[m Build (0.0s)[K
+[K project [36;1mtf2[m [120G[12DBuild (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[1F
- project Build (0.0s)
+ project [120G[12DBuild (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[1F
- project Build (0.0s)
+ project [120G[12DBuild (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[1F
- project Build (0.0s)
+ project [120G[12DBuild (0.0s)
[?25h
\ No newline at end of file
diff --git a/src/MSBuild/Resources/Strings.resx b/src/MSBuild/Resources/Strings.resx
index a753a09c5b6..98c41c4823c 100644
--- a/src/MSBuild/Resources/Strings.resx
+++ b/src/MSBuild/Resources/Strings.resx
@@ -1536,26 +1536,11 @@
{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)
+
+ {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..55929f8714c 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.de.xlf b/src/MSBuild/Resources/xlf/Strings.de.xlf
index 0a93c2b4b6d..226e0960175 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.es.xlf b/src/MSBuild/Resources/xlf/Strings.es.xlf
index cfa87dc7547..1846d8646a1 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.fr.xlf b/src/MSBuild/Resources/xlf/Strings.fr.xlf
index b4b2ae8bfde..f4d88741a76 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.it.xlf b/src/MSBuild/Resources/xlf/Strings.it.xlf
index d5b83a7548a..12e7aa2ac94 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.ja.xlf b/src/MSBuild/Resources/xlf/Strings.ja.xlf
index b10dcf784c2..cd05368111a 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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} 秒)
-
- 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} 秒)
diff --git a/src/MSBuild/Resources/xlf/Strings.ko.xlf b/src/MSBuild/Resources/xlf/Strings.ko.xlf
index d6d76449a65..dfa81b71e90 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}초)
-
- 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}초)
diff --git a/src/MSBuild/Resources/xlf/Strings.pl.xlf b/src/MSBuild/Resources/xlf/Strings.pl.xlf
index 2a48d1d5450..133760011df 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{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 15c9b624a84..247e14490ef 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}{1} {2} ({3}s)
diff --git a/src/MSBuild/Resources/xlf/Strings.ru.xlf b/src/MSBuild/Resources/xlf/Strings.ru.xlf
index 6dca92a8e84..1d89ce20f45 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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} с)
-
- 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} с)
diff --git a/src/MSBuild/Resources/xlf/Strings.tr.xlf b/src/MSBuild/Resources/xlf/Strings.tr.xlf
index ff6f95f2eae..d3fc5c7e9ed 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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}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}sn)
diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf
index db252831300..12f6f98c04d 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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})
-
- 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})
diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf
index f5907cf51e5..b603ad790a6 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}: duration in seconds with 1 decimal point
+
+
-
-
- {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} 秒)
-
- 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} 秒)
diff --git a/src/MSBuild/TerminalLogger/NodeStatus.cs b/src/MSBuild/TerminalLogger/NodeStatus.cs
new file mode 100644
index 00000000000..c82a3d106d7
--- /dev/null
+++ b/src/MSBuild/TerminalLogger/NodeStatus.cs
@@ -0,0 +1,58 @@
+// 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 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");
+
+ return string.IsNullOrEmpty(TargetFramework)
+ ? string.Format("{0}{1} {2} ({3}s)",
+ TerminalLogger.Indentation,
+ Project,
+ Target,
+ duration)
+ : string.Format("{0}{1} {2} {3} ({4}s)",
+ TerminalLogger.Indentation,
+ Project,
+ AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor),
+ Target,
+ duration);
+ }
+
+ public override int GetHashCode()
+ {
+ throw new System.NotImplementedException();
+ }
+}
diff --git a/src/MSBuild/TerminalLogger/NodesFrame.cs b/src/MSBuild/TerminalLogger/NodesFrame.cs
new file mode 100644
index 00000000000..144288950fa
--- /dev/null
+++ b/src/MSBuild/TerminalLogger/NodesFrame.cs
@@ -0,0 +1,135 @@
+// 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 const int MaxColumn = 120;
+
+ private readonly NodeStatus[] _nodes;
+
+ 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 = Math.Min(width, MaxColumn);
+ Height = height;
+
+ _nodes = new NodeStatus[nodes.Length];
+
+ foreach (NodeStatus? status in nodes)
+ {
+ if (status is not null)
+ {
+ _nodes[NodesCount++] = status;
+ }
+ }
+ }
+
+ internal ReadOnlySpan RenderNodeStatus(NodeStatus status)
+ {
+ string durationString = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword(
+ "DurationDisplay",
+ status.Stopwatch.Elapsed.TotalSeconds);
+
+ string project = status.Project;
+ string? targetFramework = status.TargetFramework;
+ string target = status.Target;
+
+ int renderedWidth = Length(durationString, project, targetFramework, target);
+
+ if (renderedWidth > Width)
+ {
+ 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 $"{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;
+ }
+
+ ///
+ /// 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++)
+ {
+ ReadOnlySpan needed = RenderNodeStatus(_nodes[i]);
+
+ // Do we have previous node string to compare with?
+ if (previousFrame.NodesCount > i)
+ {
+ 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
+ {
+ // TODO: check components to figure out skips and optimize this
+ sb.Append($"{AnsiCodes.CSI}{AnsiCodes.EraseInLine}");
+ sb.Append(needed);
+ }
+ }
+ 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 ba0e8abf043..221d6e4e4fa 100644
--- a/src/MSBuild/TerminalLogger/TerminalLogger.cs
+++ b/src/MSBuild/TerminalLogger/TerminalLogger.cs
@@ -1,11 +1,9 @@
-// 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.Text;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
@@ -52,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.
@@ -221,7 +195,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);
@@ -745,137 +719,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