< Summary

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

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Text()100%11100%
get_IsBold()100%11100%
.ctor(...)100%11100%

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    {
 4415        public string Text { get; }
 4416        public bool IsBold { get; }
 17
 18        public HighlightSegment(string text, bool isBold)
 4419        {
 4420            Text = text;
 4421            IsBold = isBold;
 4422        }
 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 ---
 37        public static readonly DependencyProperty TitleProperty =
 38            DependencyProperty.RegisterAttached(
 39                "Title", typeof(string), typeof(SearchHighlightBehavior),
 40                new PropertyMetadata(string.Empty, OnPropertyChanged));
 41
 42        public static string GetTitle(DependencyObject obj) => (string)obj.GetValue(TitleProperty);
 43        public static void SetTitle(DependencyObject obj, string value) => obj.SetValue(TitleProperty, value);
 44
 45        // --- SearchText ---
 46        public static readonly DependencyProperty SearchTextProperty =
 47            DependencyProperty.RegisterAttached(
 48                "SearchText", typeof(string), typeof(SearchHighlightBehavior),
 49                new PropertyMetadata(string.Empty, OnPropertyChanged));
 50
 51        public static string GetSearchText(DependencyObject obj) => (string)obj.GetValue(SearchTextProperty);
 52        public static void SetSearchText(DependencyObject obj, string value) => obj.SetValue(SearchTextProperty, value);
 53
 54        // --- IsEnabled ---
 55        public static readonly DependencyProperty IsEnabledProperty =
 56            DependencyProperty.RegisterAttached(
 57                "IsEnabled", typeof(bool), typeof(SearchHighlightBehavior),
 58                new PropertyMetadata(true, OnPropertyChanged));
 59
 60        public static bool GetIsEnabled(DependencyObject obj) => (bool)obj.GetValue(IsEnabledProperty);
 61        public static void SetIsEnabled(DependencyObject obj, bool value) => obj.SetValue(IsEnabledProperty, value);
 62
 63        // --- UseFuzzy ---
 64        public static readonly DependencyProperty UseFuzzyProperty =
 65            DependencyProperty.RegisterAttached(
 66                "UseFuzzy", typeof(bool), typeof(SearchHighlightBehavior),
 67                new PropertyMetadata(true, OnPropertyChanged));
 68
 69        public static bool GetUseFuzzy(DependencyObject obj) => (bool)obj.GetValue(UseFuzzyProperty);
 70        public static void SetUseFuzzy(DependencyObject obj, bool value) => obj.SetValue(UseFuzzyProperty, value);
 71
 72        // --- HighlightColor ---
 73        public static readonly DependencyProperty HighlightColorProperty =
 74            DependencyProperty.RegisterAttached(
 75                "HighlightColor", typeof(string), typeof(SearchHighlightBehavior),
 76                new PropertyMetadata("#FF0078D4", OnPropertyChanged));
 77
 78        public static string GetHighlightColor(DependencyObject obj) => (string)obj.GetValue(HighlightColorProperty);
 79        public static void SetHighlightColor(DependencyObject obj, string value) => obj.SetValue(HighlightColorProperty,
 80
 81        // --- Core logic ---
 82        private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 83        {
 84            if (d is not TextBlock textBlock) return;
 85
 86            string title = GetTitle(textBlock);
 87            string searchText = GetSearchText(textBlock);
 88            bool isEnabled = GetIsEnabled(textBlock);
 89            bool useFuzzy = GetUseFuzzy(textBlock);
 90            string highlightColor = GetHighlightColor(textBlock);
 91
 92            var segments = BuildSegments(title, searchText, isEnabled, useFuzzy);
 93
 94            System.Windows.Media.Brush? highlightBrush = null;
 95            try
 96            {
 97                if (!string.IsNullOrEmpty(highlightColor))
 98                {
 99                    var color = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(highli
 100                    highlightBrush = new System.Windows.Media.SolidColorBrush(color);
 101                }
 102            }
 103            catch { /* Fallback to default/none */ }
 104
 105            textBlock.Inlines.Clear();
 106            foreach (var seg in segments)
 107            {
 108                var run = new Run(seg.Text);
 109                if (seg.IsBold)
 110                {
 111                    run.FontWeight = FontWeights.Bold;
 112                    if (highlightBrush != null)
 113                        run.Foreground = highlightBrush;
 114                }
 115                textBlock.Inlines.Add(run);
 116            }
 117        }
 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
 125        {
 126            var segments = new List<HighlightSegment>();
 127
 128            if (string.IsNullOrEmpty(title))
 129            {
 130                segments.Add(new HighlightSegment(string.Empty, false));
 131                return segments;
 132            }
 133
 134            if (!isEnabled || string.IsNullOrEmpty(searchText))
 135            {
 136                segments.Add(new HighlightSegment(title, false));
 137                return segments;
 138            }
 139
 140            var matchedIndices = FuzzyMatcher.GetMatchedIndices(title, searchText, useFuzzy);
 141
 142            if (matchedIndices.Length == 0)
 143            {
 144                segments.Add(new HighlightSegment(title, false));
 145                return segments;
 146            }
 147
 148            // Build a set for O(1) lookup
 149            var matchSet = new HashSet<int>(matchedIndices);
 150
 151            // Group consecutive characters with the same bold/normal state into segments
 152            int segmentStart = 0;
 153            bool currentBold = matchSet.Contains(0);
 154
 155            for (int i = 1; i <= title.Length; i++)
 156            {
 157                bool isBold = i < title.Length && matchSet.Contains(i);
 158                if (i == title.Length || isBold != currentBold)
 159                {
 160                    var text = title.Substring(segmentStart, i - segmentStart);
 161                    segments.Add(new HighlightSegment(text, currentBold));
 162
 163                    segmentStart = i;
 164                    currentBold = isBold;
 165                }
 166            }
 167
 168            return segments;
 169        }
 170    }
 171}