Implement sort logic for winget list output #6191
Implement sort logic for winget list output
#6191Madhusudhan-MSFT wants to merge 16 commits intomicrosoft:masterfrom
Conversation
This comment has been minimized.
This comment has been minimized.
|
Is this only applicable to |
This seems like it would be counter to what a user would want. If I did |
The current implementation is scoped to |
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 @denelon — I believe this aligns with what we discussed during the design phase, would you mind confirming? |
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 |
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 |
Thanks for the clarification, John. Updated in
Removed |
d92410d to
5aae88f
Compare
… add comprehensive tests
…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.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…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)
- 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.
- `WI_IsAnyFlagSet` was used for single-flag checks where `WI_IsFlagSet` is the correct API - `ComputeSortFieldMask` was defined inline in the header instead of the .cpp file - `SortInstalledPackagesTableLines` was called at three separate call sites instead of being encapsulated inside `OutputInstalledPackages` - Default-sort bypass only checked `Query` argument, missing the `MultiQuery` path - Test assertions validated only a single field (name or ID), providing no cross-field verification of sort correctness - Replace all five `WI_IsAnyFlagSet` calls with `WI_IsFlagSet` for single-flag conditionals - Move `ComputeSortFieldMask` body to PackageTableSortHelper.cpp, leaving only the declaration in the header - Consolidate sort invocation inside `OutputInstalledPackages`, reducing three call sites to one - Add `MultiQuery` argument check alongside `Query` for relevance-preserving default - Replace `GetNames`/`GetIds` test helpers with `ValidateSortResult` that asserts all six precomputed fields (FoldedName, FoldedId, FoldedSource, HasAvailableVersion, ParsedInstalledVersion, ParsedAvailableVersion) per entry - Add release notes entry for sortable list output - No behavioral change to end users — fixes are code quality improvements and correctness for the `--query` multi-value path - Test suite now validates complete sort state rather than a single projection, catching regressions in any field computation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e8ba2d5 to
c19ce84
Compare
## Problem - Sort argument count limit was hardcoded to a magic number (6), creating drift risk when new SortField values are added - SortField::Relevance occupied 0x0, conflicting with bitmask zero-initialization semantics - Sort resolution logic was inlined in WorkflowBase.cpp, making it untestable in isolation - Available version comparison used a separate bool flag instead of leveraging std::optional semantics - Missing WI_ASSERT guards on default switch cases in CompareByField ## Solution - Replace hardcoded SetCountLimit(6) with dynamic computation via GetAllExponentialEnumValues - Add SortField::None = 0x0 for bitmask initialization, move Relevance to 0x20, add Max = 0x40 sentinel - Extract ResolveSortParameters() into PackageTableSortHelper as a self-contained testable function - Replace HasAvailableVersion bool with std::optional<Version> throughout sort pipeline - Add WI_ASSERT(false) to default cases in both GetSortKey and CompareByField - Simplify EnsureListSortFieldCountMatchesLimit test to assert (1u << limit) == Max - Remove redundant ValidateArguments_StandardArgExceedsCountLimit test (covered by framework) - Add using-declaration for Settings namespace to reduce qualification noise ## How Validated - Full solution build (AppInstallerCLICore + AppInstallerCLITests): clean, zero warnings - All 19 sort-related test cases pass (199 assertions) - Spell check: no CI violations - End-to-end wingetdev deployment verified across 12 scenarios including settings, query, multi-sort, order direction, and invalid input edge cases
…arameters constructor ## Summary Addresses remaining PR review feedback. The primary change moves all CLI arg and settings extraction out of WorkflowBase.cpp and the free function ResolveSortParameters into a SortParameters constructor that reads directly from the execution context, reducing the caller to a single line. ## Changes - **SortParameters constructor**: Replace the free function `ResolveSortParameters(args, hasQuery, hasAscending, hasDescending)` with `SortParameters(const Execution::Context&)` that reads CLI args, settings, and query presence directly from the context. Default constructor preserved for unit test use. - **SortEntries/SortBy signatures**: Change from `(sortFields, direction)` to `(const SortParameters& sortParams)`, consolidating three separate no-op guards (empty fields, relevance-only, size <= 1) into `!sortParams.ShouldSort`. - **Direction resolution**: Collapse 12-line if/else/else chain into a ternary expression. - **Available field comparison**: Extract `aHas`/`bHas` locals for readability in the optional presence check. - **Test helper**: Add `MakeSortParams` that mirrors the production constructor's ShouldSort logic, replacing manual struct construction across all 11 SortEntries and 2 SortBy call sites. - **Test validation**: Simplify `ValidateSortResult` by using `optional::operator==` instead of manual `has_value()` + `value()` comparison. - **Compile-time count**: Add `GetExponentialEnumValuesCount` constexpr template and use `static constexpr` local in `GetArguments()` for the `--sort` argument count limit, replacing a runtime `GetAllExponentialEnumValues().size()` call. - **WorkflowBase caller**: Reduce from 15 lines of arg extraction to `const SortParameters params(context)` with an early-exit comment explaining the optimization. ## Impact - No behavioral changes — all sort semantics preserved. - WorkflowBase.cpp caller reduced from ~20 lines to ~5 lines. - Forward declaration of `Execution::Context` in the header avoids circular includes.
Summary
Implements the sort logic for
winget listoutput, 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> settingsoutput.sortOrder> query-aware default, determines direction, sorts viastd::stable_sort, applies permutation back.-q) is used and no sort preference is configured, relevance ordering is preserved. Ifoutput.sortOrderis configured, it is respected even with queries.FAIL_FAST_MSGfor unrecognized sort field values — ensures invalid enum values crash loudly in debug builds.Sort helper (
PackageTableSortHelper.h/.cpp):SortablePackageEntry— holds pre-computed case-folded sortable fields, computed only for fields needed viaComputeSortFieldMaskbitmask optimization.CompareByField— case-insensitive ordinal comparison using pre-computedFoldCasevalues.SortBy— template sort executor used by the workflow; standaloneSortEntrieswrapper available for unit tests.SortFieldenum uses flag-bit values (uint32_t) withDEFINE_ENUM_FLAG_OPERATORSfor efficient bitmask field tracking.CLI integration (
ListCommand.cpp):--sort(repeatable, limit 6),--ascending/--asc,--descending/--descarguments.Argument validation (
Command.cpp):--sortvalues inValidateArguments, following the--scope/--authentication-modepattern.Documentation (
list.md):--sort,--ascending,--descendingin options table. New "Sorting output" section.Previously,
winget listoutput preserved the source's natural ordering (source-determined relevance ranking). This PR changes the default to sort alphabetically by name (ascending) when no--sortargument, no-qquery, and nooutput.sortOrdersetting is configured.--sort-qquery with no sort preference--sort relevanceor"sortOrder": ["relevance"]"sortOrder": [](empty array)output.sortOrderconfigured +-qqueryTo restore previous (source-determined) ordering:
winget list --sort relevance{ "output": { "sortOrder": ["relevance"] } }Design decisions
-q) without configured sort preserves source relevance ranking. Settings override this if configured — explicit user preference takes priority.stable_sort--sort source --sort namegroups by source, alphabetical within).SortFieldenumComputeSortFieldMaskto skip unused field extraction — measurable win for large package lists.FoldCaseFAIL_FAST_MSGon invalid sortHow validated
Unit tests — 20 test cases:
PackageTableSortHelper.cpp: single/multi-field sorting, both directions, edge cases, case-insensitive comparison, relevance no-opCommand.cpp: readsListCommand::GetArguments()limit and validates against knownConvertToSortFieldstrings — breaks if enum changes without updating limitCommand.cpp: verifiesSetCountLimit(6)rejects 7--sortvalues withTooManyArgErrorManual 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