Skip to content

Implement sort logic for winget list output #6191

Open
Madhusudhan-MSFT wants to merge 13 commits intomicrosoft:masterfrom
Madhusudhan-MSFT:user/masudars/list-sort-logic
Open

Implement sort logic for winget list output #6191
Madhusudhan-MSFT wants to merge 13 commits intomicrosoft:masterfrom
Madhusudhan-MSFT:user/masudars/list-sort-logic

Conversation

@Madhusudhan-MSFT
Copy link
Copy Markdown
Contributor

@Madhusudhan-MSFT Madhusudhan-MSFT commented Apr 29, 2026

Summary

Implements the sort logic for winget list output, completing the feature requested in #4238. Builds on PR #6177 (settings parsing + CLI handling).

Changes

Sort orchestration (WorkflowBase.cpp):

  • SortInstalledPackagesTableLines — resolves sort fields from CLI --sort > settings output.sortOrder > default (sort by name), determines direction, sorts via std::stable_sort, applies permutation back.
  • When the free-text query (-q) is used and no sort preference is configured, relevance ordering is preserved. If output.sortOrder is configured, it is respected even with queries.
  • FAIL_FAST_MSG for unrecognized sort field values — ensures invalid enum values crash loudly in debug builds.

Sort helper (PackageTableSortHelper.h/.cpp):

  • SortablePackageEntry — holds sortable fields, computed only for fields needed via ComputeSortFieldMask bitmask optimization.
  • CompareByField — case-insensitive ordinal comparison via FoldCase.
  • SortBy — template sort executor used by the workflow; SortEntries — standalone wrapper for unit tests.
  • SortField enum uses flag-bit values (uint32_t) with DEFINE_ENUM_FLAG_OPERATORS for efficient bitmask field tracking.

CLI integration (ListCommand.cpp):

  • --sort (repeatable, limit 6), --ascending/--asc, --descending/--desc arguments.

Argument validation (Command.cpp):

  • Validates --sort values in ValidateArguments, following the --scope/--authentication-mode pattern.

Documentation (list.md):

  • --sort, --ascending, --descending in options table. New "Sorting output" section.

Design decisions

Decision Rationale
Default sort by name When no settings and no query, sort alphabetically by name — most natural default for browsing installed packages.
Relevance preservation with queries Free-text query (-q) without configured sort preserves source relevance ranking. Settings override this if configured — explicit user preference takes priority.
stable_sort Preserves relative order for multi-field sorting (e.g., --sort source --sort name groups by source, alphabetical within).
Flag-bit SortField enum Enables ComputeSortFieldMask to skip unused field extraction — measurable win for large package lists.
FAIL_FAST_MSG on invalid sort Catches enum drift at development time rather than silent misbehavior.

How validated

Unit tests — 20 test cases:

  • 18 sort tests in PackageTableSortHelper.cpp: single/multi-field sorting, both directions, edge cases, case-insensitive comparison, relevance no-op
  • 1 drift detection test in Command.cpp: reads ListCommand::GetArguments() limit and validates against known ConvertToSortField strings — breaks if enum changes without updating limit
  • 1 enforcement test in Command.cpp: verifies SetCountLimit(6) rejects 7 --sort values with TooManyArgError

Manual testing — 21 scenarios on wingetdev (Debug x64): defaults, single/multi-field sorts, direction flags, query + sort interaction, settings precedence, edge cases, invalid input. All passing.

Fixes #4238

Madhusudhan-MSFT and others added 5 commits April 28, 2026 16:18
…pp file

## Summary

Every other Workflow header in the project follows the declarations-only convention with
a matching .cpp file. ListSortHelper.h was the only outlier using inline implementations.
This refactor aligns it with the codebase convention and eliminates the duplicated sort
loop between ListSortHelper.h and WorkflowBase.cpp.

## Changes

- **ListSortHelper.h → declarations only**: Remove inline implementations of CompareByField
  and SortEntries, keeping only the struct definition and function signatures
- **ListSortHelper.cpp**: New compilation unit with the extracted implementations of
  CompareByField and SortEntries
- **WorkflowBase.cpp sort integration**: Replace the duplicated inline sort loop with an
  index-based permutation sort that projects InstalledPackagesTableLine into
  SortablePackageEntry and delegates field comparison to the shared CompareByField
- Remove the local CompareByField adapter that was creating temporary SortablePackageEntry
  copies per comparison call
- Align include path and grouping with repo conventions (bare sibling name, local before system)
- Simplify GetArgs access to match established MultiQueryFlow pattern

## Impact

- Sort logic now lives in a single shared location, reusable by future commands (search,
  upgrade, font list) without duplication
- Follows the established Workflows/ header convention (24/24 other headers use .h + .cpp)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add --sort, --ascending, and --descending options to the options table.
Add new 'Sorting output' section documenting:
- CLI sort arguments with multi-field examples
- Settings-based sortOrder configuration
- Resolution order (CLI > settings > default)
- Relevance protection behavior for queries
Validate --sort argument values in Command::ValidateArguments following the
same pattern as --scope and --authentication-mode validation. Invalid values
produce a clear error message listing valid options (name, id, version,
source, available, relevance). Centralized in Command.cpp so any future
command adding --sort gets validation automatically.
@github-actions

This comment has been minimized.

@Trenly
Copy link
Copy Markdown
Contributor

Trenly commented Apr 29, 2026

Is this only applicable to winget list or to any command that presents the user with a table of packages (upgrade, search, pin, etc.)

@Trenly
Copy link
Copy Markdown
Contributor

Trenly commented Apr 29, 2026

Key behavior to note: Settings sortOrder is intentionally NOT applied when query/filter arguments are present. Only explicit --sort CLI arguments can override relevance ordering. The --descending flag applies to whichever sort fields are active (CLI or settings).

This seems like it would be counter to what a user would want. If I did winget list Microsoft and my default sort order in settings is Name I'd expect both the filter and the sorting to work

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Madhusudhan-MSFT
Copy link
Copy Markdown
Contributor Author

Madhusudhan-MSFT commented Apr 29, 2026

Is this only applicable to winget list or to any command that presents the user with a table of packages (upgrade, search, pin, etc.)

The current implementation is scoped to winget list per the feature request in #4238. The sort infrastructure (schema, settings, argument definitions) was designed to be reusable, so extending to other table-outputting commands like upgrade or search would be straightforward if there's demand for it.

@Madhusudhan-MSFT
Copy link
Copy Markdown
Contributor Author

Madhusudhan-MSFT commented Apr 29, 2026

This seems like it would be counter to what a user would want. If I did winget list Microsoft and my default sort order in settings is Name I'd expect both the filter and the sorting to work

This was discussed and agreed upon during the initial design. The intent is to preserve source relevance ordering for queries since a settings-based sort could push the best match down without the user realizing why. Explicit --sort always works with queries (e.g., winget list Microsoft --sort name).

@denelon — I believe this aligns with what we discussed during the design phase, would you mind confirming?

@Trenly
Copy link
Copy Markdown
Contributor

Trenly commented Apr 29, 2026

if there's demand for it.

I think there is, but agree with the approach of limiting the scope for now and letting Demitrius figure out when to bring the rest in

@JohnMcPMS
Copy link
Copy Markdown
Member

This seems like it would be counter to what a user would want. If I did winget list Microsoft and my default sort order in settings is Name I'd expect both the filter and the sorting to work

This was discussed and agreed upon during the initial design. The intent is to preserve source relevance ordering for queries since a settings-based sort could push the best match down without the user realizing why. Explicit --sort always works with queries (e.g., winget list Microsoft --sort name).

@denelon — I believe this aligns with what we discussed during the design phase, would you mind confirming?

From the design portion, my feedback was that the default behavior should be to remain unchanged with a query but could have a new ordering without a query. If the user does supply a setting value, my expectation would be that it would be used with or without a query unless overridden with a command line argument.

What constitutes a query? Probably only the actual query argument, not id or other fields. Relevance would largely by the same for those already.

…edback

- Remove HasRelevanceAffectingArgs function (overly broad filter)
- Narrow relevance preservation to only the free-text query argument
- Settings output.sortOrder now applies even when query is present
  (user explicitly configured sort preference takes priority)
- Filter args (--id, --name, --tag etc.) no longer block settings sort
  since they use exact/substring matching with no meaningful relevance
- Update list.md documentation to reflect new behavior
- Resolution: CLI --sort > settings (always) > default relevance (query only)
@Madhusudhan-MSFT Madhusudhan-MSFT changed the title Implement sort logic for winget list output Implement sort logic for winget list output Apr 29, 2026
@Madhusudhan-MSFT
Copy link
Copy Markdown
Contributor Author

From the design portion, my feedback was that the default behavior should be to remain unchanged with a query but could have a new ordering without a query. If the user does supply a setting value, my expectation would be that it would be used with or without a query unless overridden with a command line argument.

What constitutes a query? Probably only the actual query argument, not id or other fields. Relevance would largely by the same for those already.

Thanks for the clarification, John. Updated in 70bd157e:

  • Default unchanged — without output.sortOrder configured, positional query preserves relevance as before
  • Settings always honored — if user configures output.sortOrder, it applies regardless of arguments (user explicitly opted in)
  • "Query" narrowed — only the positional query argument (not --id/--name/--tag) is considered relevance-affecting in the no-settings case

Removed HasRelevanceAffectingArgs in favor of sortFields.empty() && Args.Contains(Query).

@Madhusudhan-MSFT Madhusudhan-MSFT marked this pull request as ready for review April 29, 2026 23:42
@Madhusudhan-MSFT Madhusudhan-MSFT requested a review from a team as a code owner April 29, 2026 23:42
Comment thread doc/windows/package-manager/winget/list.md Outdated
Comment thread src/AppInstallerCLICore/Commands/ListCommand.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/ListSortHelper.h Outdated
Comment thread src/AppInstallerCLICore/Workflows/ListSortHelper.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp Outdated
Comment thread src/AppInstallerCLICore/Workflows/WorkflowBase.cpp
- Fix comment: 'enable' -> 'ease' unit testing (not impossible, just decoupled)
- Remove stale comment from deleted HasRelevanceAffectingArgs function
- Add THROW_HR(E_UNEXPECTED) assertion for invalid sort values that bypass validation
- Change --sort count limit from 7 to 6 (matches actual SortField enum count)
When no sort preferences are configured (no CLI --sort, no settings) and no
positional query is present, default to sorting by name ascending for
deterministic, user-friendly output. With a positional query, relevance
ordering is preserved unless the user explicitly configures sort preferences.

Update list.md to document this conditional default behavior.
Restructure ListSortHelper to own the production sort path:

- SortablePackageEntry now precomputes case-folded strings and parsed
  versions at construction time, avoiding repeated FoldCase/Version
  parsing during comparisons.
- Add OriginalIndex field so entries track their source position.
- Add SortBy<T> template that converts arbitrary item vectors into
  SortablePackageEntry projections, sorts, then reorders the source
  vector to match. This is the production code path.
- WorkflowBase calls SortBy with a conversion lambda, replacing the
  inline permutation sort.
- SortEntries is no longer test-only — it is the same code path that
  production uses via SortBy.
- Add SortBy template tests to validate the end-to-end pipeline with
  custom source types.
Rename to reflect broader scope — the helper sorts package table rows
for any table-based command (list, search, upgrade), not just list.
Change SortField to flag-bit enum (uint32_t) and add DEFINE_ENUM_FLAG_OPERATORS
so sort fields compose into a bitmask. SortBy computes the mask once and passes
it through the converter to SortablePackageEntry's constructor, which now only
precomputes fields that are actually needed for the requested sort.
- Add static_assert on SortField::Available at enum definition site to
  catch new values that bypass constructor handling.
- Replace invalid-sort assertion with FAIL_FAST_MSG for clearer diagnostics.
- Collapse two consecutive sortFields.empty() checks into a single branch.
- Format SortablePackageEntry constructor arguments one-per-line.
- Add --query example to list.md Example queries section.
@Madhusudhan-MSFT Madhusudhan-MSFT force-pushed the user/masudars/list-sort-logic branch from d92410d to 5aae88f Compare May 1, 2026 21:01
@Madhusudhan-MSFT Madhusudhan-MSFT requested a review from JohnMcPMS May 1, 2026 21:27
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.

Enable sorting the output of WinGet List

3 participants