File: ValueTracking\TreeItemViewModel.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.ValueTracking;
 
internal abstract class TreeItemViewModel : TreeViewItemBase
{
    private readonly SourceText _sourceText;
    private readonly Glyph _glyph;
    private readonly IGlyphService _glyphService;
 
    protected ValueTrackingTreeViewModel TreeViewModel { get; }
    protected TextSpan TextSpan { get; }
    protected LineSpan LineSpan { get; }
    protected IThreadingContext ThreadingContext { get; }
    protected DocumentId DocumentId { get; }
    protected Workspace Workspace { get; }
 
    public int LineNumber => LineSpan.Start + 1; // LineSpan is 0 indexed, editors are not
 
    public string FileName { get; }
 
    public ImageSource GlyphImage => _glyph.GetImageSource(_glyphService);
    public bool ShowGlyph => !IsLoading;
 
    public ImmutableArray<ClassifiedSpan> ClassifiedSpans { get; }
 
    public ImmutableArray<Inline> Inlines => CalculateInlines();
    public override string AutomationName => _sourceText.ToString(TextSpan);
 
    protected TreeItemViewModel(
        TextSpan textSpan,
        SourceText sourceText,
        DocumentId documentId,
        string fileName,
        Glyph glyph,
        ImmutableArray<ClassifiedSpan> classifiedSpans,
        ValueTrackingTreeViewModel treeViewModel,
        IGlyphService glyphService,
        IThreadingContext threadingContext,
        Workspace workspace,
        ImmutableArray<TreeItemViewModel> children)
    {
        FileName = fileName;
        TextSpan = textSpan;
        _sourceText = sourceText;
        ClassifiedSpans = classifiedSpans;
        TreeViewModel = treeViewModel;
        ThreadingContext = threadingContext;
 
        _glyph = glyph;
        _glyphService = glyphService;
        Workspace = workspace;
        DocumentId = documentId;
 
        if (!children.IsDefaultOrEmpty)
        {
            foreach (var child in children)
            {
                ChildItems.Add(child);
            }
        }
 
        sourceText.GetLineAndOffset(textSpan.Start, out var lineStart, out var _);
        sourceText.GetLineAndOffset(textSpan.End, out var lineEnd, out var _);
        LineSpan = LineSpan.FromBounds(lineStart, lineEnd);
 
        PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(IsLoading))
            {
                NotifyPropertyChanged(nameof(ShowGlyph));
            }
        };
 
        TreeViewModel.PropertyChanged += (s, e) =>
        {
            if (e.PropertyName == nameof(TreeViewModel.HighlightBrush))
            {
                // If the highlight changes we need to recalculate the inlines so the 
                // highlighting is correct
                NotifyPropertyChanged(nameof(Inlines));
            }
        };
    }
 
    public abstract void NavigateTo();
 
    private ImmutableArray<Inline> CalculateInlines()
    {
        if (ClassifiedSpans.IsDefaultOrEmpty)
        {
            return ImmutableArray<Inline>.Empty;
        }
 
        var classifiedTexts = ClassifiedSpans.SelectAsArray(
           cs =>
           {
               return new ClassifiedText(cs.ClassificationType, _sourceText.ToString(cs.TextSpan));
           });
 
        var spanStartPosition = TextSpan.Start - ClassifiedSpans[0].TextSpan.Start;
        var highlightSpan = new TextSpan(spanStartPosition, TextSpan.Length);
 
        return classifiedTexts.ToInlines(
            TreeViewModel.ClassificationFormatMap,
            TreeViewModel.ClassificationTypeMap,
            (run, classifiedText, position) =>
            {
                if (TreeViewModel.HighlightBrush is not null)
                {
                    // Check the span start first because we always want to highlight a run that 
                    // is at the start, even if the TextSpan length is 0. If it's not the start,
                    // highlighting should still happen if the run position is contained within
                    // the span.
                    if (position == highlightSpan.Start || highlightSpan.Contains(position))
                    {
                        run.SetValue(
                            TextElement.BackgroundProperty,
                            TreeViewModel.HighlightBrush);
                    }
                }
            }).ToImmutableArray();
    }
}