|
// 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();
}
}
|