< Summary

Information
Class: SwitchBlade.ViewModels.MainViewModel
Assembly: SwitchBlade
File(s): D:\a\switchblade\switchblade\ViewModels\MainViewModel.cs
Tag: 203_23722840422
Line coverage
100%
Covered lines: 156
Uncovered lines: 0
Coverable lines: 156
Total lines: 271
Line coverage: 100%
Branch coverage
100%
Covered branches: 90
Total branches: 90
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\a\switchblade\switchblade\ViewModels\MainViewModel.cs

#LineLine coverage
 1using System;
 2
 3using System.Collections.Generic;
 4using System.Collections.ObjectModel;
 5using System.ComponentModel;
 6using System.Linq;
 7using System.Runtime.CompilerServices;
 8using System.Threading.Tasks;
 9using System.Windows.Input;
 10using SwitchBlade.Core;
 11using SwitchBlade.Contracts;
 12using SwitchBlade.Services;
 13
 14namespace SwitchBlade.ViewModels
 15{
 16    public class MainViewModel : INotifyPropertyChanged, IWindowListViewModel
 17    {
 18        private readonly IWindowOrchestrationService _orchestrationService;
 19        private readonly IWindowSearchService _searchService;
 20        private readonly INavigationService _navigationService;
 21        private readonly ISettingsService? _settingsService;
 22        private readonly IDispatcherService _dispatcherService;
 7123        private ObservableCollection<WindowItem> _filteredWindows = [];
 24        private WindowItem? _selectedWindow;
 7125        private string _searchText = "";
 7126        private bool _enablePreviews = true;
 7127        private bool _isUpdating = false;
 7128        private HashSet<string> _disabledPlugins = [];
 7129        private readonly System.Threading.Lock _settingsLock = new();
 7130        private readonly System.Threading.Lock _updateLock = new();
 31
 32        /// <summary>Event fired when filtered results are updated.</summary>
 33        public event EventHandler? ResultsUpdated;
 34
 35        /// <summary>Event fired when search text changes (user typing).</summary>
 36        public event EventHandler? SearchTextChanged;
 37
 38        /// <summary>Event fired when settings opening is requested.</summary>
 39        public event EventHandler? OpenSettingsRequested;
 40
 41        /// <summary>Command to open the Settings window.</summary>
 342        public ICommand OpenSettingsCommand { get; }
 43
 44        /// <summary>Gets the window providers from the orchestration service.</summary>
 45        public IReadOnlyList<IWindowProvider> WindowProviders =>
 346            [.. _orchestrationService.AllWindows
 547                .Where(w => w.Source != null)
 548                .Select(w => w.Source!)
 349                .Distinct()];
 50
 51        // Primary constructor with all dependencies
 7152        public MainViewModel(
 7153            IWindowOrchestrationService orchestrationService,
 7154            IWindowSearchService searchService,
 7155            INavigationService navigationService,
 7156            ISettingsService? settingsService = null,
 7157            IDispatcherService? dispatcherService = null)
 7158        {
 7159            _orchestrationService = orchestrationService ?? throw new ArgumentNullException(nameof(orchestrationService)
 7060            _searchService = searchService ?? throw new ArgumentNullException(nameof(searchService));
 6961            _navigationService = navigationService ?? throw new ArgumentNullException(nameof(navigationService));
 6862            _settingsService = settingsService;
 6863            _dispatcherService = dispatcherService ?? new WpfDispatcherService();
 64
 65            // Subscribe to orchestration updates
 6866            _orchestrationService.WindowListUpdated += OnWindowListUpdated;
 67
 6868            if (_settingsService != null)
 2369            {
 70                lock (_settingsLock)
 2371                {
 2372                    _disabledPlugins = [.._settingsService.Settings.DisabledPlugins];
 2373                }
 2374                EnablePreviews = _settingsService.Settings.EnablePreviews;
 75
 2376                _settingsService.SettingsChanged += () =>
 177                {
 2378                    lock (_settingsLock)
 179                    {
 180                        _disabledPlugins = [.. _settingsService.Settings.DisabledPlugins];
 181                    }
 182                    EnablePreviews = _settingsService.Settings.EnablePreviews;
 183                    OnPropertyChanged(nameof(ShowInTaskbar));
 184                    OnPropertyChanged(nameof(ShowIcons));
 185                    OnPropertyChanged(nameof(EnableNumberShortcuts));
 186                    OnPropertyChanged(nameof(ShortcutModifierText));
 187                    OnPropertyChanged(nameof(ItemHeight));
 188                    OnPropertyChanged(nameof(EnableSearchHighlighting));
 189                    OnPropertyChanged(nameof(EnableFuzzySearch));
 2490                };
 2391            }
 92
 7093            OpenSettingsCommand = new RelayCommand(_ => OpenSettingsRequested?.Invoke(this, EventArgs.Empty));
 6894        }
 95
 96        private void OnWindowListUpdated(object? sender, WindowListUpdatedEventArgs e)
 1997        {
 3898            _dispatcherService.Invoke(() => UpdateSearch());
 1899        }
 100
 2101        public double ItemHeight => _settingsService?.Settings.ItemHeight ?? 64.0;
 102
 103        public bool EnablePreviews
 104        {
 3105            get => _enablePreviews;
 104106            set { _enablePreviews = value; OnPropertyChanged(); }
 107        }
 108
 2109        public bool EnableNumberShortcuts => _settingsService?.Settings.EnableNumberShortcuts ?? true;
 110
 2111        public bool EnableSearchHighlighting => _settingsService?.Settings.EnableSearchHighlighting ?? true;
 2112        public string SearchHighlightColor => _settingsService?.Settings.SearchHighlightColor ?? "#FF0078D4";
 113
 2114        public bool EnableFuzzySearch => _settingsService?.Settings.EnableFuzzySearch ?? true;
 115
 116        public string ShortcutModifierText
 117        {
 118            get
 2119            {
 2120                var modifier = _settingsService?.Settings.NumberShortcutModifier ?? ModifierKeyFlags.Alt;
 2121                return ModifierKeyFlags.ToString(modifier);
 2122            }
 123        }
 124
 2125        public bool ShowInTaskbar => !_settingsService?.Settings.HideTaskbarIcon ?? true;
 126
 2127        public bool ShowIcons => _settingsService?.Settings.ShowIcons ?? true;
 128
 129        public ObservableCollection<WindowItem> FilteredWindows
 130        {
 306131            get => _filteredWindows;
 132            set
 10133            {
 10134                if (value != null)
 9135                {
 9136                    _filteredWindows = value;
 9137                    OnPropertyChanged();
 9138                }
 10139            }
 140        }
 141
 142        public WindowItem? SelectedWindow
 143        {
 196144            get => _selectedWindow;
 145            set
 53146            {
 53147                if (_selectedWindow != value)
 25148                {
 25149                    _selectedWindow = value;
 25150                    if (!_isUpdating)
 11151                        OnPropertyChanged();
 25152                }
 53153            }
 154        }
 155
 156        public string SearchText
 157        {
 37158            get => _searchText;
 159            set
 16160            {
 16161                if (_searchText != value)
 16162                {
 16163                    _searchText = value;
 16164                    OnPropertyChanged();
 16165                    SearchTextChanged?.Invoke(this, EventArgs.Empty);
 16166                    UpdateSearch(resetSelection: true);
 16167                }
 16168            }
 169        }
 170
 171        public async Task RefreshWindows()
 20172        {
 173            HashSet<string> disabled;
 174            lock (_settingsLock)
 20175            {
 20176                disabled = [.. _disabledPlugins];
 20177            }
 20178            await _orchestrationService.RefreshAsync(disabled);
 20179        }
 180
 181        private void UpdateSearch(bool resetSelection = false)
 35182        {
 183            lock (_updateLock)
 35184            {
 35185                _isUpdating = true;
 186                try
 35187                {
 188                    // Capture current state
 35189                    IntPtr? selectedHwnd = SelectedWindow?.Hwnd;
 35190                    string? selectedTitle = SelectedWindow?.Title;
 35191                    int selectedIndex = SelectedWindow != null ? FilteredWindows.IndexOf(SelectedWindow) : -1;
 35192                    WindowItem? previousSelection = SelectedWindow;
 193
 194                    // Delegate search to service
 35195                    bool useFuzzy = _settingsService?.Settings.EnableFuzzySearch ?? true;
 35196                    var allWindows = _orchestrationService.AllWindows;
 35197                    var sortedResults = _searchService.Search(allWindows, SearchText, useFuzzy);
 198
 199                    // Sync collection in-place
 35200                    SyncCollection(FilteredWindows, sortedResults);
 201
 202                    // Update shortcut indices
 174203                    for (int i = 0; i < FilteredWindows.Count; i++)
 53204                        FilteredWindows[i].ShortcutIndex = (i < 10) ? i : -1;
 205
 206                    // Delegate selection resolution to navigation service
 34207                    var behavior = _settingsService?.Settings.RefreshBehavior ?? RefreshBehavior.PreserveScroll;
 34208                    var newSelection = _navigationService.ResolveSelection(
 34209                        FilteredWindows, selectedHwnd, selectedTitle, selectedIndex, behavior, resetSelection);
 210
 34211                    SelectedWindow = newSelection;
 212
 213                    // Fire notification if selection changed meaningfully
 34214                    if (resetSelection || (SelectedWindow != previousSelection && behavior != RefreshBehavior.PreserveSc
 19215                    {
 19216                        _isUpdating = false;
 19217                        OnPropertyChanged(nameof(SelectedWindow));
 19218                    }
 34219                }
 220                finally
 35221                {
 35222                    _isUpdating = false;
 35223                }
 34224            }
 225
 226            // Fire event outside the lock to prevent deadlocks in listeners
 34227            ResultsUpdated?.Invoke(this, EventArgs.Empty);
 34228        }
 229
 230        private static void SyncCollection(ObservableCollection<WindowItem> collection, IList<WindowItem> source)
 35231        {
 35232            ObservableCollectionSync.Sync(collection, source);
 34233        }
 234
 235        public void MoveSelection(int direction)
 6236        {
 8237            if (FilteredWindows.Count == 0) return;
 4238            int currentIndex = SelectedWindow != null ? FilteredWindows.IndexOf(SelectedWindow) : -1;
 4239            int newIndex = _navigationService.CalculateMoveIndex(currentIndex, direction, FilteredWindows.Count);
 4240            if (newIndex >= 0 && newIndex < FilteredWindows.Count)
 3241                SelectedWindow = FilteredWindows[newIndex];
 6242        }
 243
 244        public void MoveSelectionToFirst()
 2245        {
 2246            if (FilteredWindows.Count > 0)
 1247                SelectedWindow = FilteredWindows[0];
 2248        }
 249
 250        public void MoveSelectionToLast()
 2251        {
 2252            if (FilteredWindows.Count > 0)
 1253                SelectedWindow = FilteredWindows[^1];
 2254        }
 255
 256        public void MoveSelectionByPage(int direction, int pageSize)
 5257        {
 8258            if (FilteredWindows.Count == 0 || pageSize <= 0) return;
 2259            int currentIndex = SelectedWindow != null ? FilteredWindows.IndexOf(SelectedWindow) : 0;
 2260            int newIndex = _navigationService.CalculatePageMoveIndex(currentIndex, direction, pageSize, FilteredWindows.
 2261            if (newIndex >= 0)
 2262                SelectedWindow = FilteredWindows[newIndex];
 5263        }
 264
 265        public event PropertyChangedEventHandler? PropertyChanged;
 266        protected void OnPropertyChanged([CallerMemberName] string? name = null)
 88267        {
 88268            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
 88269        }
 270    }
 271}