< Summary

Information
Class: SwitchBlade.Core.SearchHighlightBehavior
Assembly: SwitchBlade
File(s): D:\a\switchblade\switchblade\Core\SearchHighlightBehavior.cs
Tag: 203_23722840422
Line coverage
100%
Covered lines: 91
Uncovered lines: 0
Coverable lines: 91
Total lines: 171
Line coverage: 100%
Branch coverage
100%
Covered branches: 26
Total branches: 26
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
GetTitle(...)100%11100%
SetTitle(...)100%11100%
GetSearchText(...)100%11100%
SetSearchText(...)100%11100%
GetIsEnabled(...)100%11100%
SetIsEnabled(...)100%11100%
GetUseFuzzy(...)100%11100%
SetUseFuzzy(...)100%11100%
GetHighlightColor(...)100%11100%
SetHighlightColor(...)100%11100%
OnPropertyChanged(...)100%1010100%
BuildSegments(...)100%1616100%

File(s)

D:\a\switchblade\switchblade\Core\SearchHighlightBehavior.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Windows;
 4using System.Windows.Controls;
 5using System.Windows.Documents;
 6
 7namespace SwitchBlade.Core
 8{
 9    /// <summary>
 10    /// Represents a text segment with an indication of whether it should be bold.
 11    /// This is a pure data type with no WPF dependency, enabling unit testing.
 12    /// </summary>
 13    public readonly struct HighlightSegment
 14    {
 15        public string Text { get; }
 16        public bool IsBold { get; }
 17
 18        public HighlightSegment(string text, bool isBold)
 19        {
 20            Text = text;
 21            IsBold = isBold;
 22        }
 23    }
 24
 25    /// <summary>
 26    /// Attached behavior that highlights matching search characters in a TextBlock
 27    /// by rendering them with bold font weight.
 28    /// </summary>
 29    /// <remarks>
 30    /// Uses four attached DPs (Title, SearchText, IsEnabled, UseFuzzy).
 31    /// When any of them changes, the TextBlock.Inlines collection is rebuilt
 32    /// with bold <see cref="Run"/> segments for matched characters.
 33    /// </remarks>
 34    public static class SearchHighlightBehavior
 35    {
 36        // --- Title ---
 137        public static readonly DependencyProperty TitleProperty =
 138            DependencyProperty.RegisterAttached(
 139                "Title", typeof(string), typeof(SearchHighlightBehavior),
 140                new PropertyMetadata(string.Empty, OnPropertyChanged));
 41
 1742        public static string GetTitle(DependencyObject obj) => (string)obj.GetValue(TitleProperty);
 543        public static void SetTitle(DependencyObject obj, string value) => obj.SetValue(TitleProperty, value);
 44
 45        // --- SearchText ---
 146        public static readonly DependencyProperty SearchTextProperty =
 147            DependencyProperty.RegisterAttached(
 148                "SearchText", typeof(string), typeof(SearchHighlightBehavior),
 149                new PropertyMetadata(string.Empty, OnPropertyChanged));
 50
 1651        public static string GetSearchText(DependencyObject obj) => (string)obj.GetValue(SearchTextProperty);
 452        public static void SetSearchText(DependencyObject obj, string value) => obj.SetValue(SearchTextProperty, value);
 53
 54        // --- IsEnabled ---
 155        public static readonly DependencyProperty IsEnabledProperty =
 156            DependencyProperty.RegisterAttached(
 157                "IsEnabled", typeof(bool), typeof(SearchHighlightBehavior),
 158                new PropertyMetadata(true, OnPropertyChanged));
 59
 1660        public static bool GetIsEnabled(DependencyObject obj) => (bool)obj.GetValue(IsEnabledProperty);
 461        public static void SetIsEnabled(DependencyObject obj, bool value) => obj.SetValue(IsEnabledProperty, value);
 62
 63        // --- UseFuzzy ---
 164        public static readonly DependencyProperty UseFuzzyProperty =
 165            DependencyProperty.RegisterAttached(
 166                "UseFuzzy", typeof(bool), typeof(SearchHighlightBehavior),
 167                new PropertyMetadata(true, OnPropertyChanged));
 68
 1769        public static bool GetUseFuzzy(DependencyObject obj) => (bool)obj.GetValue(UseFuzzyProperty);
 270        public static void SetUseFuzzy(DependencyObject obj, bool value) => obj.SetValue(UseFuzzyProperty, value);
 71
 72        // --- HighlightColor ---
 173        public static readonly DependencyProperty HighlightColorProperty =
 174            DependencyProperty.RegisterAttached(
 175                "HighlightColor", typeof(string), typeof(SearchHighlightBehavior),
 176                new PropertyMetadata("#FF0078D4", OnPropertyChanged));
 77
 1678        public static string GetHighlightColor(DependencyObject obj) => (string)obj.GetValue(HighlightColorProperty);
 479        public static void SetHighlightColor(DependencyObject obj, string value) => obj.SetValue(HighlightColorProperty,
 80
 81        // --- Core logic ---
 82        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 1683        {
 1784            if (d is not TextBlock textBlock) return;
 85
 1586            string title = GetTitle(textBlock);
 1587            string searchText = GetSearchText(textBlock);
 1588            bool isEnabled = GetIsEnabled(textBlock);
 1589            bool useFuzzy = GetUseFuzzy(textBlock);
 1590            string highlightColor = GetHighlightColor(textBlock);
 91
 1592            var segments = BuildSegments(title, searchText, isEnabled, useFuzzy);
 93
 1594            System.Windows.Media.Brush? highlightBrush = null;
 95            try
 1596            {
 1597                if (!string.IsNullOrEmpty(highlightColor))
 1498                {
 1499                    var color = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(highli
 13100                    highlightBrush = new System.Windows.Media.SolidColorBrush(color);
 13101                }
 14102            }
 3103            catch { /* Fallback to default/none */ }
 104
 15105            textBlock.Inlines.Clear();
 99106            foreach (var seg in segments)
 27107            {
 27108                var run = new Run(seg.Text);
 27109                if (seg.IsBold)
 6110                {
 6111                    run.FontWeight = FontWeights.Bold;
 6112                    if (highlightBrush != null)
 4113                        run.Foreground = highlightBrush;
 6114                }
 27115                textBlock.Inlines.Add(run);
 27116            }
 16117        }
 118
 119        /// <summary>
 120        /// Builds a list of <see cref="HighlightSegment"/> for the given title,
 121        /// marking matched characters as bold when highlighting is enabled.
 122        /// This method is pure (no WPF dependency) and safe to call from any thread.
 123        /// </summary>
 124        internal static List<HighlightSegment> BuildSegments(string title, string searchText, bool isEnabled, bool useFu
 25125        {
 25126            var segments = new List<HighlightSegment>();
 127
 25128            if (string.IsNullOrEmpty(title))
 2129            {
 2130                segments.Add(new HighlightSegment(string.Empty, false));
 2131                return segments;
 132            }
 133
 23134            if (!isEnabled || string.IsNullOrEmpty(searchText))
 10135            {
 10136                segments.Add(new HighlightSegment(title, false));
 10137                return segments;
 138            }
 139
 13140            var matchedIndices = FuzzyMatcher.GetMatchedIndices(title, searchText, useFuzzy);
 141
 13142            if (matchedIndices.Length == 0)
 2143            {
 2144                segments.Add(new HighlightSegment(title, false));
 2145                return segments;
 146            }
 147
 148            // Build a set for O(1) lookup
 11149            var matchSet = new HashSet<int>(matchedIndices);
 150
 151            // Group consecutive characters with the same bold/normal state into segments
 11152            int segmentStart = 0;
 11153            bool currentBold = matchSet.Contains(0);
 154
 162155            for (int i = 1; i <= title.Length; i++)
 70156            {
 70157                bool isBold = i < title.Length && matchSet.Contains(i);
 70158                if (i == title.Length || isBold != currentBold)
 30159                {
 30160                    var text = title.Substring(segmentStart, i - segmentStart);
 30161                    segments.Add(new HighlightSegment(text, currentBold));
 162
 30163                    segmentStart = i;
 30164                    currentBold = isBold;
 30165                }
 70166            }
 167
 11168            return segments;
 25169        }
 170    }
 171}