mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
## Summary This PR refactors CmdPal settings/state to be immutable end-to-end. ### Core changes - Convert model types to immutable records / init-only properties: - `SettingsModel` - `AppStateModel` - `ProviderSettings` - `DockSettings` - `RecentCommandsManager` - supporting settings types (fallback/hotkey/alias/top-level hotkey/history items, etc.) - Replace mutable collections with immutable equivalents where appropriate: - `ImmutableDictionary<,>` - `ImmutableList<>` - Move mutation flow to atomic service updates: - `ISettingsService.UpdateSettings(Func<SettingsModel, SettingsModel>)` - `IAppStateService.UpdateState(Func<AppStateModel, AppStateModel>)` - Update ViewModels/managers/services to use copy-on-write (`with`) patterns instead of in-place mutation. - Update serialization context + tests for immutable model graph compatibility. ## Why Issue #46437 is caused by mutable shared state being updated from different execution paths/threads, leading to race-prone behavior during persistence/serialization. By making settings/app state immutable and using atomic swap/update patterns, we remove in-place mutation and eliminate this class of concurrency bug. ## Validation - Built successfully: - `Microsoft.CmdPal.UI.ViewModels` - `Microsoft.CmdPal.UI` - `Microsoft.CmdPal.UI.ViewModels.UnitTests` - Updated unit tests for immutable update patterns. Fixes #46437 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
90 lines
3.0 KiB
C#
90 lines
3.0 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
|
using Microsoft.CmdPal.UI.ViewModels.Services;
|
|
|
|
namespace Microsoft.CmdPal.UI.ViewModels;
|
|
|
|
public partial class FallbackSettingsViewModel : ObservableObject
|
|
{
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly ProviderSettingsViewModel _providerSettingsViewModel;
|
|
|
|
private FallbackSettings _fallbackSettings;
|
|
|
|
public string DisplayName { get; private set; } = string.Empty;
|
|
|
|
public IconInfoViewModel Icon { get; private set; } = new(null);
|
|
|
|
public string Id { get; private set; } = string.Empty;
|
|
|
|
public bool IsEnabled
|
|
{
|
|
get => _fallbackSettings.IsEnabled;
|
|
set
|
|
{
|
|
if (value != _fallbackSettings.IsEnabled)
|
|
{
|
|
var newSettings = _fallbackSettings with { IsEnabled = value };
|
|
|
|
if (!newSettings.IsEnabled)
|
|
{
|
|
newSettings = newSettings with { IncludeInGlobalResults = false };
|
|
}
|
|
|
|
_fallbackSettings = newSettings;
|
|
_providerSettingsViewModel.UpdateFallbackSettings(Id, _fallbackSettings);
|
|
|
|
OnPropertyChanged(nameof(IsEnabled));
|
|
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IncludeInGlobalResults
|
|
{
|
|
get => _fallbackSettings.IncludeInGlobalResults;
|
|
set
|
|
{
|
|
if (value != _fallbackSettings.IncludeInGlobalResults)
|
|
{
|
|
var newSettings = _fallbackSettings with { IncludeInGlobalResults = value };
|
|
|
|
if (!newSettings.IsEnabled)
|
|
{
|
|
newSettings = newSettings with { IsEnabled = true };
|
|
}
|
|
|
|
_fallbackSettings = newSettings;
|
|
_providerSettingsViewModel.UpdateFallbackSettings(Id, _fallbackSettings);
|
|
|
|
OnPropertyChanged(nameof(IncludeInGlobalResults));
|
|
WeakReferenceMessenger.Default.Send<ReloadCommandsMessage>(new());
|
|
}
|
|
}
|
|
}
|
|
|
|
public FallbackSettingsViewModel(
|
|
TopLevelViewModel fallback,
|
|
FallbackSettings fallbackSettings,
|
|
ProviderSettingsViewModel providerSettings,
|
|
ISettingsService settingsService)
|
|
{
|
|
_settingsService = settingsService;
|
|
_providerSettingsViewModel = providerSettings;
|
|
_fallbackSettings = fallbackSettings;
|
|
|
|
Id = fallback.Id;
|
|
DisplayName = string.IsNullOrWhiteSpace(fallback.DisplayTitle)
|
|
? (string.IsNullOrWhiteSpace(fallback.Title) ? providerSettings.DisplayName : fallback.Title)
|
|
: fallback.DisplayTitle;
|
|
|
|
Icon = new(fallback.InitialIcon);
|
|
Icon.InitializeProperties();
|
|
}
|
|
}
|