Skip to content

Change how Truncation works#6142

Open
Trenly wants to merge 8 commits intomicrosoft:masterfrom
Trenly:Truncate
Open

Change how Truncation works#6142
Trenly wants to merge 8 commits intomicrosoft:masterfrom
Trenly:Truncate

Conversation

@Trenly
Copy link
Copy Markdown
Contributor

@Trenly Trenly commented Apr 14, 2026

  • I have signed the Contributor License Agreement.
  • I have updated the Release Notes.
  • This pull request is related to an issue.
    • Several, will link them after review and before merge
    • 6137
    • 1653
    • 3986
    • 3071
    • 1300
    • 2603
    • Mitigates 2161
    • Maybe 1618

Problem

When winget list output is redirected to a file or variable:

  • Output was truncated at 120 characters (hardcoded fallback width)
  • Rows beyond the first 50 were truncated to widths locked by earlier rows
  • Spinner and progress bar characters appeared in the redirected output

Changes

ChannelStreams.h / ChannelStreams.cpp

  • GetConsoleWidth() now returns std::optional<size_t> — nullopt when no console is attached (stdout redirected), actual width otherwise. Eliminates the hardcoded 120-column fallback.

TableOutput.h

  • All rows are now buffered before any output is written. Column widths are computed from the full dataset in EvaluateAndFlushBuffer(), eliminating truncation and misalignment for both console and
    non-console output.
  • Column shrinking to fit console width is skipped when no console is present.
  • Removed the 50-row "sizing buffer" optimization that was the root cause of truncation even if the console was wide enough for all columns.

Note

I assumed the 50-row sizing buffer was for performance reasons and to ensure the CLI would not take too much memory. A test on my machine showed a terminal width of 220 and roughly 200 packages. Assuming a terminal width of 500 characters on an ultrawide monitor, UTF-16 encoded, with 10k rows, that would be roughly 10 MB of buffered content. Given that 10k rows and 500 characters would be a statistical anomaly, I assumed that it would be safe to remove this sizing buffer even if it comes at a minor performance cost.

ExecutionReporter.cpp

  • Spinner and progress bar are only created when a console is present (GetConsoleWidth().has_value()). When stdout is redirected they remain nullptr, suppressing all progress noise from redirected output.

ExecutionProgress.cpp, WorkflowBase.cpp, ConfigurationFlow.cpp

  • Updated call sites to handle std::optional<size_t> from GetConsoleWidth().

AppInstallerCLITests

  • Added new test cases to ensure console width creates appropriate truncation behavior
  • Added new test to ensure that buffering all lines at once does not cause issues

Microsoft Reviewers: Open in CodeFlow

@Trenly Trenly requested a review from a team as a code owner April 14, 2026 04:20
@github-actions

This comment has been minimized.

Comment on lines +19 to +24
#ifndef AICLI_DISABLE_TEST_HOOKS
// Overrides the value returned by GetConsoleWidth(). Pass nullptr to remove the override.
// Pass a pointer to std::nullopt to simulate no console; pass a pointer to a value to
// simulate a console of that width.
void TestHook_SetConsoleWidth_Override(std::optional<size_t>* value);
#endif
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't put the hooks into the headers here; only the test headers.

else
{
m_out << '\r' << std::string(GetConsoleWidth(), ' ') << '\r';
m_out << '\r' << std::string(GetConsoleWidth().value_or(0), ' ') << '\r';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent of other changes I think that it would be better to just \n. Since we should never hit this if progress is disabled whenever this is std::nullopt, more of a nit.

// Test that all rows are buffered and column widths account for values beyond the first 50 rows.
// In the old sizing-buffer design, a row at position 55 with a longer value than any of the
// first 50 rows would be truncated. The new design buffers every row so no value is clipped.
TEST_CASE("TableOutput_AllRowsBuffered_NoTruncation", "[tableoutput]")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests should set width override to prevent using the console width they are run in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants