diff --git a/src/VisualStudio/Core/Def/TableDataSource/AbstractRoslynTableDataSource.cs b/src/VisualStudio/Core/Def/TableDataSource/AbstractRoslynTableDataSource.cs deleted file mode 100644 index 514fcbf434930..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/AbstractRoslynTableDataSource.cs +++ /dev/null @@ -1,86 +0,0 @@ -// 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 Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.SolutionCrawler; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -/// -/// A version of ITableDataSource who knows how to connect them to Roslyn solution crawler for live information. -/// -internal abstract class AbstractRoslynTableDataSource : AbstractTableDataSource - where TItem : TableItem - where TData : notnull -{ - public AbstractRoslynTableDataSource(Workspace workspace, IThreadingContext threadingContext) - : base(workspace, threadingContext) - => ConnectToSolutionCrawlerService(workspace); - - protected ImmutableArray GetDocumentsWithSameFilePath(Solution solution, DocumentId documentId) - { - var document = solution.GetTextDocument(documentId); - if (document == null) - { - return ImmutableArray.Empty; - } - - return solution.GetDocumentIdsWithFilePath(document.FilePath); - } - - /// - /// Flag indicating if a solution crawler is running incremental analyzers in background. - /// We get build progress updates from . - /// Solution crawler progress events are guaranteed to be invoked in a serial fashion. - /// - protected bool IsSolutionCrawlerRunning { get; private set; } - - private void ConnectToSolutionCrawlerService(Workspace workspace) - { - var crawlerService = workspace.Services.GetService(); - if (crawlerService == null) - { - // can happen depends on host such as testing host. - return; - } - - var reporter = crawlerService.GetProgressReporter(workspace); - reporter.ProgressChanged += OnSolutionCrawlerProgressChanged; - - // set initial value - SolutionCrawlerProgressChanged(reporter.InProgress); - } - - private void OnSolutionCrawlerProgressChanged(object sender, ProgressData progressData) - { - switch (progressData.Status) - { - case ProgressStatus.Started: - SolutionCrawlerProgressChanged(running: true); - break; - case ProgressStatus.Stopped: - SolutionCrawlerProgressChanged(running: false); - break; - } - } - - private void SolutionCrawlerProgressChanged(bool running) - { - IsSolutionCrawlerRunning = running; - ChangeStableStateIfRequired(newIsStable: !IsSolutionCrawlerRunning); - } - - protected void ChangeStableStateIfRequired(bool newIsStable) - { - var oldIsStable = IsStable; - if (oldIsStable != newIsStable) - { - IsStable = newIsStable; - ChangeStableState(newIsStable); - } - } -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/AbstractTable.cs b/src/VisualStudio/Core/Def/TableDataSource/AbstractTable.cs deleted file mode 100644 index 065338078995c..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/AbstractTable.cs +++ /dev/null @@ -1,93 +0,0 @@ -// 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 Microsoft.CodeAnalysis; -using Microsoft.VisualStudio.Shell.TableManager; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -/// -/// Base implementation of new platform table. this knows how to create various ITableDataSource and connect -/// them to ITableManagerProvider -/// -internal abstract class AbstractTable -{ - protected AbstractTable(Workspace workspace, ITableManagerProvider provider, string tableIdentifier) - { - Workspace = workspace; - this.TableManager = provider.GetTableManager(tableIdentifier); - } - - protected Workspace Workspace { get; } - internal ITableManager TableManager { get; } - internal abstract ImmutableArray Columns { get; } - - protected abstract void AddTableSourceIfNecessary(Solution solution); - protected abstract void RemoveTableSourceIfNecessary(Solution solution); - protected abstract void ShutdownSource(); - - protected void ConnectWorkspaceEvents() - => Workspace.WorkspaceChanged += OnWorkspaceChanged; - - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) - { - switch (e.Kind) - { - case WorkspaceChangeKind.SolutionAdded: - case WorkspaceChangeKind.ProjectAdded: - AddTableSourceIfNecessary(e.NewSolution); - break; - case WorkspaceChangeKind.SolutionRemoved: - case WorkspaceChangeKind.ProjectRemoved: - ShutdownSourceIfNecessary(e.NewSolution); - RemoveTableSourceIfNecessary(e.NewSolution); - break; - case WorkspaceChangeKind.SolutionChanged: - case WorkspaceChangeKind.SolutionCleared: - case WorkspaceChangeKind.SolutionReloaded: - case WorkspaceChangeKind.ProjectChanged: - case WorkspaceChangeKind.ProjectReloaded: - case WorkspaceChangeKind.DocumentAdded: - case WorkspaceChangeKind.DocumentRemoved: - case WorkspaceChangeKind.DocumentReloaded: - case WorkspaceChangeKind.DocumentChanged: - case WorkspaceChangeKind.AdditionalDocumentAdded: - case WorkspaceChangeKind.AdditionalDocumentRemoved: - case WorkspaceChangeKind.AdditionalDocumentReloaded: - case WorkspaceChangeKind.AdditionalDocumentChanged: - case WorkspaceChangeKind.AnalyzerConfigDocumentAdded: - case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved: - case WorkspaceChangeKind.AnalyzerConfigDocumentChanged: - case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded: - break; - default: - throw ExceptionUtilities.UnexpectedValue(e.Kind); - } - } - - private void ShutdownSourceIfNecessary(Solution solution) - { - if (solution.ProjectIds.Count > 0) - { - return; - } - - ShutdownSource(); - } - - protected void AddInitialTableSource(Solution solution, ITableDataSource source) - { - if (solution.ProjectIds.Count == 0) - { - return; - } - - AddTableSource(source); - } - - protected void AddTableSource(ITableDataSource source) - => this.TableManager.AddSource(source, Columns); -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableControlEventProcessorProvider.cs b/src/VisualStudio/Core/Def/TableDataSource/AbstractTableControlEventProcessorProvider.cs deleted file mode 100644 index c497e6527825e..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableControlEventProcessorProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -// 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.Threading; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.VisualStudio.Shell.TableControl; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -internal abstract class AbstractTableControlEventProcessorProvider : ITableControlEventProcessorProvider - where TItem : TableItem -{ - public ITableControlEventProcessor GetAssociatedEventProcessor(IWpfTableControl tableControl) - => CreateEventProcessor(); - - protected virtual EventProcessor CreateEventProcessor() - => new(); - - protected class EventProcessor : TableControlEventProcessorBase - { - protected static AbstractTableEntriesSnapshot? GetEntriesSnapshot(ITableEntryHandle entryHandle) - => GetEntriesSnapshot(entryHandle, out _); - - protected static AbstractTableEntriesSnapshot? GetEntriesSnapshot(ITableEntryHandle entryHandle, out int index) - { - if (!entryHandle.TryGetSnapshot(out var snapshot, out index)) - { - return null; - } - - return snapshot as AbstractTableEntriesSnapshot; - } - - public override void PreprocessNavigate(ITableEntryHandle entryHandle, TableEntryNavigateEventArgs e) - { - var roslynSnapshot = GetEntriesSnapshot(entryHandle, out var index); - if (roslynSnapshot == null) - { - return; - } - - // don't be too strict on navigation on our item. if we can't handle the item, - // let default handler to handle it at least. - // we might fail to navigate if we don't see the document in our solution anymore. - // that can happen if error is staled build error or user used #line pragma in C# - // to point to some random file in error or more. - - // TODO: Use a threaded-wait-dialog here so we can cancel navigation. - var options = new NavigationOptions(PreferProvisionalTab: e.IsPreview, ActivateTab: e.ShouldActivate); - e.Handled = roslynSnapshot.TryNavigateTo(index, options, CancellationToken.None); - } - } -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableDataSource.cs b/src/VisualStudio/Core/Def/TableDataSource/AbstractTableDataSource.cs deleted file mode 100644 index 726a65187a762..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableDataSource.cs +++ /dev/null @@ -1,422 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.VisualStudio.Shell.TableManager; -using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -/// -/// Base implementation of ITableDataSource -/// -internal abstract class AbstractTableDataSource : ITableDataSource - where TItem : TableItem - where TData : notnull -{ - private readonly object _gate = new(); - - // This map holds aggregation key to factory - // Any data that shares same aggregation key will de-duplicated to same factory - private readonly Dictionary> _map = []; - - // This map holds each data source key to its aggregation key - private readonly Dictionary _aggregateKeyMap = []; - - private ImmutableArray _subscriptions; - protected bool IsStable; - - public AbstractTableDataSource(Workspace workspace, IThreadingContext threadingContext) - { - _subscriptions = ImmutableArray.Empty; - - Workspace = workspace; - ThreadingContext = threadingContext; - IsStable = true; - } - - public Workspace Workspace { get; } - - public abstract string DisplayName { get; } - - public abstract string SourceTypeIdentifier { get; } - - public abstract string Identifier { get; } - - protected IThreadingContext ThreadingContext { get; } - - public void RefreshAllFactories() - { - ImmutableArray snapshot; - List> factories; - lock (_gate) - { - snapshot = _subscriptions; - factories = _map.Values.ToList(); - } - - // let table manager know that we want to refresh factories. - for (var i = 0; i < snapshot.Length; i++) - { - foreach (var factory in factories) - { - factory.OnRefreshed(); - - snapshot[i].AddOrUpdate(factory, newFactory: false); - } - } - } - - public void Refresh(TableEntriesFactory factory) - { - var snapshot = _subscriptions; - - for (var i = 0; i < snapshot.Length; i++) - { - snapshot[i].AddOrUpdate(factory, newFactory: false); - } - } - - public void Shutdown() - { - // editor team wants us to update snapshot versions before - // removing factories on shutdown. - RefreshAllFactories(); - - // and then remove all factories. - ImmutableArray snapshot; - - lock (_gate) - { - snapshot = _subscriptions; - _map.Clear(); - } - - // let table manager know that we want to clear all factories - for (var i = 0; i < snapshot.Length; i++) - { - snapshot[i].RemoveAll(); - } - } - - public ImmutableArray AggregateItems(IEnumerable> groupedItems) - { - using var _0 = ArrayBuilder.GetInstance(out var aggregateItems); - using var _1 = ArrayBuilder.GetInstance(out var projectNames); - using var _2 = ArrayBuilder.GetInstance(out var projectGuids); - - string[]? stringArrayCache = null; - Guid[]? guidArrayCache = null; - - static T[] GetOrCreateArray([NotNull] ref T[]? cache, ArrayBuilder value) - => (cache != null && Enumerable.SequenceEqual(cache, value)) ? cache : (cache = value.ToArray()); - - foreach (var (_, items) in groupedItems) - { - TItem? firstItem = null; - var hasSingle = true; - - foreach (var item in items) - { - if (firstItem == null) - { - firstItem = item; - } - else - { - hasSingle = false; - } - - if (item.ProjectName != null) - { - projectNames.Add(item.ProjectName); - } - - if (item.ProjectGuid != Guid.Empty) - { - projectGuids.Add(item.ProjectGuid); - } - } - - // firstItem could only be null if a group was empty, which is not allowed - RoslynDebug.AssertNotNull(firstItem); - - if (hasSingle) - { - aggregateItems.Add(firstItem); - } - else - { - projectNames.SortAndRemoveDuplicates(StringComparer.Ordinal); - projectGuids.SortAndRemoveDuplicates(Comparer.Default); - - aggregateItems.Add((TItem)firstItem.WithAggregatedData(GetOrCreateArray(ref stringArrayCache, projectNames), GetOrCreateArray(ref guidArrayCache, projectGuids))); - } - - projectNames.Clear(); - projectGuids.Clear(); - } - - return Order(aggregateItems).ToImmutableArray(); - } - - public abstract IEqualityComparer GroupingComparer { get; } - - public abstract IEnumerable Order(IEnumerable groupedItems); - - public abstract AbstractTableEntriesSnapshot CreateSnapshot(AbstractTableEntriesSource source, int version, ImmutableArray items, ImmutableArray trackingPoints); - - /// - /// Get unique ID per given data such as DiagnosticUpdatedArgs or TodoUpdatedArgs. - /// Data contains multiple items belong to one logical chunk. and the Id represents this particular - /// chunk of the data - /// - public abstract object GetItemKey(TData data); - - /// - /// Create TableEntriesSource for the given data. - /// - public abstract AbstractTableEntriesSource CreateTableEntriesSource(object data); - - /// - /// Get unique ID for given data that will be used to find data whose items needed to be merged together. - /// - /// for example, for linked files, data that belong to same physical file will be gathered and items that belong to - /// those data will be de-duplicated. - /// - protected abstract object GetOrUpdateAggregationKey(TData data); - - protected void OnDataAddedOrChanged(TData data) - { - // reuse factory. it is okay to re-use factory since we make sure we remove the factory before - // adding it back - ImmutableArray snapshot; - lock (_gate) - { - snapshot = _subscriptions; - GetOrCreateFactory_NoLock(data, out var factory, out var newFactory); - - factory.OnDataAddedOrChanged(data); - - NotifySubscriptionOnDataAddedOrChanged_NoLock(snapshot, factory, newFactory); - } - } - - protected void OnDataRemoved(TData data) - { - lock (_gate) - { - RemoveStaledData(data); - } - } - - protected void RemoveStaledData(TData data) - { - OnDataRemoved_NoLock(data); - - RemoveAggregateKey_NoLock(data); - } - - private void OnDataRemoved_NoLock(TData data) - { - ImmutableArray snapshot; - var key = TryGetAggregateKey(data); - if (key == null) - { - // never created before. - return; - } - - snapshot = _subscriptions; - if (!_map.TryGetValue(key, out var factory)) - { - // never reported about this before - return; - } - - // remove this particular item from map - if (!factory.OnDataRemoved(data)) - { - // let error list know that factory has changed. - NotifySubscriptionOnDataAddedOrChanged_NoLock(snapshot, factory, newFactory: false); - return; - } - - // everything belong to the factory has removed. remove the factory - _map.Remove(key); - - // let table manager know that we want to clear the entries - NotifySubscriptionOnDataRemoved_NoLock(snapshot, factory); - } - - private static void NotifySubscriptionOnDataAddedOrChanged_NoLock(ImmutableArray snapshot, TableEntriesFactory factory, bool newFactory) - { - for (var i = 0; i < snapshot.Length; i++) - { - snapshot[i].AddOrUpdate(factory, newFactory); - } - } - - private static void NotifySubscriptionOnDataRemoved_NoLock(ImmutableArray snapshot, TableEntriesFactory factory) - { - for (var i = 0; i < snapshot.Length; i++) - { - snapshot[i].Remove(factory); - } - } - - private void GetOrCreateFactory_NoLock(TData data, out TableEntriesFactory factory, out bool newFactory) - { - newFactory = false; - - var key = GetOrUpdateAggregationKey(data); - if (_map.TryGetValue(key, out factory)) - { - return; - } - - var source = CreateTableEntriesSource(data); - factory = new TableEntriesFactory(ThreadingContext, this, source); - - _map.Add(key, factory); - newFactory = true; - } - - protected void ChangeStableState(bool stable) - { - ImmutableArray snapshot; - - lock (_gate) - { - snapshot = _subscriptions; - } - - for (var i = 0; i < snapshot.Length; i++) - { - snapshot[i].IsStable = stable; - } - } - - protected void AddAggregateKey(TData data, object aggregateKey) - => _aggregateKeyMap.Add(GetItemKey(data), aggregateKey); - - protected object? TryGetAggregateKey(TData data) - { - var key = GetItemKey(data); - if (_aggregateKeyMap.TryGetValue(key, out var aggregateKey)) - { - return aggregateKey; - } - - return null; - } - - private void RemoveAggregateKey_NoLock(TData data) - => _aggregateKeyMap.Remove(GetItemKey(data)); - - IDisposable ITableDataSource.Subscribe(ITableDataSink sink) - { - lock (_gate) - { - return new SubscriptionWithoutLock(this, sink); - } - } - - internal int NumberOfSubscription_TestOnly - { - get { return _subscriptions.Length; } - } - - protected class SubscriptionWithoutLock : IDisposable - { - private readonly AbstractTableDataSource _source; - private readonly ITableDataSink _sink; - - public SubscriptionWithoutLock(AbstractTableDataSource source, ITableDataSink sink) - { - _source = source; - _sink = sink; - - Register(); - ReportInitialData(); - } - - public bool IsStable - { - get - { - return _sink.IsStable; - } - - set - { - _sink.IsStable = value; - } - } - - public void AddOrUpdate(ITableEntriesSnapshotFactory provider, bool newFactory) - { - if (newFactory) - { - _sink.AddFactory(provider); - return; - } - - _sink.FactorySnapshotChanged(provider); - } - - public void Remove(ITableEntriesSnapshotFactory factory) - => _sink.RemoveFactory(factory); - - public void RemoveAll() - => _sink.RemoveAllFactories(); - - public void Dispose() - { - // REVIEW: will closing task hub dispose this subscription? - UnRegister(); - } - - private void ReportInitialData() - { - foreach (var provider in _source._map.Values) - { - AddOrUpdate(provider, newFactory: true); - } - - IsStable = _source.IsStable; - } - - private void Register() - => UpdateSubscriptions(s => s.Add(this)); - - private void UnRegister() - => UpdateSubscriptions(s => s.Remove(this)); - - private void UpdateSubscriptions(Func, ImmutableArray> update) - { - while (true) - { - var current = _source._subscriptions; - var @new = update(current); - - // try replace with new list - var registered = ImmutableInterlocked.InterlockedCompareExchange(ref _source._subscriptions, @new, current); - if (registered == current) - { - // succeeded - break; - } - } - } - } -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableEntriesSource.cs b/src/VisualStudio/Core/Def/TableDataSource/AbstractTableEntriesSource.cs deleted file mode 100644 index 72f8517669d0a..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/AbstractTableEntriesSource.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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 Microsoft.VisualStudio.Text; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -/// -/// Provide information to create a ITableEntriesSnapshot -/// -/// This works on data that belong to logically same source of items such as one particular analyzer or todo list analyzer. -/// -internal abstract class AbstractTableEntriesSource where TItem : TableItem -{ - public abstract object Key { get; } - public abstract ImmutableArray GetItems(); - public abstract ImmutableArray GetTrackingPoints(ImmutableArray items); -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/Extensions.cs b/src/VisualStudio/Core/Def/TableDataSource/Extensions.cs deleted file mode 100644 index dfd7affaacfdb..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/Extensions.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -internal static class Extensions -{ - public static ImmutableArray CreateTrackingPoints(this Workspace workspace, DocumentId? documentId, ImmutableArray items) - where TItem : TableItem - { - if (documentId == null) - { - return ImmutableArray.Empty; - } - - var solution = workspace.CurrentSolution; - var document = solution.GetDocument(documentId); - if (document == null || !document.IsOpen()) - { - return ImmutableArray.Empty; - } - - if (!document.TryGetText(out var text)) - { - return ImmutableArray.Empty; - } - - var snapshot = text.FindCorrespondingEditorTextSnapshot(); - if (snapshot != null) - { - return items.SelectAsArray(CreateTrackingPoint, snapshot); - } - - var textBuffer = text.Container.TryGetTextBuffer(); - if (textBuffer == null) - { - return ImmutableArray.Empty; - } - - return items.SelectAsArray(CreateTrackingPoint, textBuffer.CurrentSnapshot); - } - - private static ITrackingPoint CreateTrackingPoint(TableItem item, ITextSnapshot snapshot) - { - if (snapshot.Length == 0) - { - return snapshot.CreateTrackingPoint(0, PointTrackingMode.Negative); - } - - var position = item.GetOriginalPosition(); - if (position.Line >= snapshot.LineCount) - { - return snapshot.CreateTrackingPoint(snapshot.Length, PointTrackingMode.Positive); - } - - var adjustedLine = Math.Max(position.Line, 0); - var textLine = snapshot.GetLineFromLineNumber(adjustedLine); - if (position.Character >= textLine.Length) - { - return snapshot.CreateTrackingPoint(textLine.End, PointTrackingMode.Positive); - } - - var adjustedColumn = Math.Max(position.Character, 0); - return snapshot.CreateTrackingPoint(textLine.Start + adjustedColumn, PointTrackingMode.Positive); - } -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/OpenDocumentTracker.cs b/src/VisualStudio/Core/Def/TableDataSource/OpenDocumentTracker.cs deleted file mode 100644 index 84ac990426783..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/OpenDocumentTracker.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using Microsoft.CodeAnalysis; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -internal class OpenDocumentTracker - where TItem : TableItem -{ - private readonly object _gate = new(); - private readonly Dictionary>>> _map = []; - - private readonly Workspace _workspace; - - public OpenDocumentTracker(Workspace workspace) - { - _workspace = workspace; - - _workspace.DocumentClosed += OnDocumentClosed; - _workspace.WorkspaceChanged += OnWorkspaceChanged; - } - - public void TrackOpenDocument(DocumentId documentId, object id, AbstractTableEntriesSnapshot snapshot) - { - lock (_gate) - { - if (!_map.TryGetValue(documentId, out var secondMap)) - { - secondMap = []; - _map.Add(documentId, secondMap); - } - - if (secondMap.TryGetValue(id, out var oldWeakSnapshot) && oldWeakSnapshot.TryGetTarget(out var oldSnapshot)) - { - oldSnapshot.StopTracking(); - } - - secondMap[id] = new WeakReference>(snapshot); - } - } - - private void StopTracking(DocumentId documentId) - { - lock (_gate) - { - StopTracking_NoLock(documentId); - } - } - - private void StopTracking(Solution solution, ProjectId? projectId = null) - { - lock (_gate) - { - foreach (var documentId in _map.Keys.Where(d => projectId == null ? true : d.ProjectId == projectId).ToList()) - { - if (solution.GetDocument(documentId) != null) - { - // document still exist. - continue; - } - - StopTracking_NoLock(documentId); - } - } - } - - private void StopTracking_NoLock(DocumentId documentId) - { - if (!_map.TryGetValue(documentId, out var secondMap)) - { - return; - } - - _map.Remove(documentId); - foreach (var weakSnapshot in secondMap.Values) - { - if (!weakSnapshot.TryGetTarget(out var snapshot)) - { - continue; - } - - snapshot.StopTracking(); - } - } - - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) - { - switch (e.Kind) - { - case WorkspaceChangeKind.SolutionRemoved: - case WorkspaceChangeKind.SolutionCleared: - StopTracking(e.NewSolution); - break; - - case WorkspaceChangeKind.ProjectRemoved: - StopTracking(e.NewSolution, e.ProjectId); - break; - - case WorkspaceChangeKind.DocumentRemoved: - StopTracking(e.DocumentId!); - break; - - default: - // do nothing - break; - } - } - - private void OnDocumentClosed(object sender, DocumentEventArgs e) - => StopTracking(e.Document.Id); -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/TableEntriesFactory.cs b/src/VisualStudio/Core/Def/TableDataSource/TableEntriesFactory.cs deleted file mode 100644 index 3753ea1bbce7c..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/TableEntriesFactory.cs +++ /dev/null @@ -1,306 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Navigation; -using Microsoft.VisualStudio.Shell.TableManager; -using Microsoft.VisualStudio.Text; -using Roslyn.Utilities; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -internal class TableEntriesFactory : ITableEntriesSnapshotFactory - where TItem : TableItem - where TData : notnull -{ - private readonly object _gate = new(); - - private readonly AbstractTableDataSource _tableSource; - private readonly AggregatedEntriesSource _entriesSources; - private readonly WeakReference _lastSnapshotWeakReference = new(null!); - - private int _lastVersion = 0; - - public TableEntriesFactory(IThreadingContext threadingContext, AbstractTableDataSource tableSource, AbstractTableEntriesSource entriesSource) - { - _tableSource = tableSource; - _entriesSources = new AggregatedEntriesSource(threadingContext, _tableSource, entriesSource); - } - - public int CurrentVersionNumber - { - get - { - lock (_gate) - { - return _lastVersion; - } - } - } - - public ITableEntriesSnapshot GetCurrentSnapshot() - { - lock (_gate) - { - var version = _lastVersion; - if (TryGetLastSnapshot(version, out var lastSnapshot)) - { - return lastSnapshot; - } - - var items = _entriesSources.GetItems(); - return CreateSnapshot(version, items); - } - } - - public ITableEntriesSnapshot? GetSnapshot(int versionNumber) - { - lock (_gate) - { - if (TryGetLastSnapshot(versionNumber, out var lastSnapshot)) - { - return lastSnapshot; - } - - var version = _lastVersion; - if (version != versionNumber) - { - _tableSource.Refresh(this); - return null; - } - - var items = _entriesSources.GetItems(); - return CreateSnapshot(version, items); - } - } - - public void OnDataAddedOrChanged(TData data) - { - lock (_gate) - { - UpdateVersion_NoLock(); - - _entriesSources.OnDataAddedOrChanged(data); - } - } - - public bool OnDataRemoved(TData data) - { - lock (_gate) - { - UpdateVersion_NoLock(); - return _entriesSources.OnDataRemoved(data); - } - } - - public void OnRefreshed() - { - lock (_gate) - { - UpdateVersion_NoLock(); - } - } - - protected void UpdateVersion_NoLock() - => _lastVersion++; - - public void Dispose() - { - } - - private bool TryGetLastSnapshot(int version, out ITableEntriesSnapshot lastSnapshot) - { - return _lastSnapshotWeakReference.TryGetTarget(out lastSnapshot) && - lastSnapshot.VersionNumber == version; - } - - private ITableEntriesSnapshot CreateSnapshot(int version, ImmutableArray items) - { - var snapshot = _entriesSources.CreateSnapshot(version, items, _entriesSources.GetTrackingPoints(items)); - _lastSnapshotWeakReference.SetTarget(snapshot); - - return snapshot; - } - - private class AggregatedEntriesSource - { - private readonly IThreadingContext _threadingContext; - private readonly EntriesSourceCollections _sources; - private readonly AbstractTableDataSource _tableSource; - - public AggregatedEntriesSource(IThreadingContext threadingContext, AbstractTableDataSource tableSource, AbstractTableEntriesSource primary) - { - _threadingContext = threadingContext; - _tableSource = tableSource; - _sources = new EntriesSourceCollections(primary); - } - - public void OnDataAddedOrChanged(TData data) - => _sources.OnDataAddedOrChanged(data, _tableSource); - - public bool OnDataRemoved(TData data) - => _sources.OnDataRemoved(data, _tableSource); - - public ImmutableArray GetItems() - { - if (_sources.Primary != null) - { - return _sources.Primary.GetItems(); - } - - // flatten items from multiple sources and group them by deduplication identity - // merge duplicated items into de-duplicated item list - return _tableSource.AggregateItems( - _sources.GetSources() - .SelectMany(s => s.GetItems()) - .GroupBy(d => d, _tableSource.GroupingComparer)); - } - - public ImmutableArray GetTrackingPoints(ImmutableArray items) - { - if (items.Length == 0) - { - return ImmutableArray.Empty; - } - - if (_sources.Primary != null) - { - return _sources.Primary.GetTrackingPoints(items); - } - - return _tableSource.Workspace.CreateTrackingPoints(items[0].DocumentId, items); - } - - public AbstractTableEntriesSnapshot CreateSnapshot(int version, ImmutableArray items, ImmutableArray trackingPoints) - { - if (_sources.Primary != null) - { - return _tableSource.CreateSnapshot(_sources.Primary, version, items, trackingPoints); - } - - // we can be called back from error list while all sources are removed but before error list know about it yet - // since notification is pending in the queue. - var source = _sources.GetSources().FirstOrDefault(); - if (source == null) - { - return new EmptySnapshot(_threadingContext, version); - } - - return _tableSource.CreateSnapshot(source, version, items, trackingPoints); - } - - private class EmptySnapshot : AbstractTableEntriesSnapshot - { - public EmptySnapshot(IThreadingContext threadingContext, int version) - : base(threadingContext, version, ImmutableArray.Empty, ImmutableArray.Empty) - { - } - - public override bool TryNavigateTo(int index, NavigationOptions options, CancellationToken cancellationToken) => false; - - public override bool TryGetValue(int index, string columnName, [NotNullWhen(true)] out object? content) - { - content = null; - return false; - } - } - - private class EntriesSourceCollections - { - private AbstractTableEntriesSource? _primary; - private Dictionary>? _sources; - - public EntriesSourceCollections(AbstractTableEntriesSource primary) - { - Contract.ThrowIfNull(primary); - _primary = primary; - } - - public AbstractTableEntriesSource? Primary - { - get - { - if (_primary != null) - { - return _primary; - } - - RoslynDebug.AssertNotNull(_sources); - if (_sources.Count == 1) - { - return _sources.Values.First(); - } - - return null; - } - } - - public IEnumerable> GetSources() - { - EnsureSources(); - return _sources.Values; - } - - [MemberNotNull(nameof(_sources))] - private void EnsureSources() - { - if (_sources == null) - { - RoslynDebug.AssertNotNull(_primary); - _sources = new Dictionary> - { - { _primary.Key, _primary } - }; - _primary = null; - } - } - - public void OnDataAddedOrChanged(TData data, AbstractTableDataSource tableSource) - { - var key = tableSource.GetItemKey(data); - if (_primary != null && _primary.Key.Equals(key)) - { - return; - } - - if (_sources != null) - { - if (_sources.ContainsKey(key)) - { - return; - } - } - - EnsureSources(); - - var source = tableSource.CreateTableEntriesSource(data); - _sources.Add(source.Key, source); - } - - public bool OnDataRemoved(TData data, AbstractTableDataSource tableSource) - { - var key = tableSource.GetItemKey(data); - if (_primary != null && _primary.Key.Equals(key)) - { - return true; - } - - if (_sources != null) - { - _sources.Remove(key); - return _sources.Count == 0; - } - - // they never reported to us before - return false; - } - } - } -} diff --git a/src/VisualStudio/Core/Def/TableDataSource/ValueTypeCache.cs b/src/VisualStudio/Core/Def/TableDataSource/ValueTypeCache.cs deleted file mode 100644 index 1ace63588f3d0..0000000000000 --- a/src/VisualStudio/Core/Def/TableDataSource/ValueTypeCache.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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; -using System.Collections.Concurrent; - -namespace Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource; - -internal static class ValueTypeCache -{ - /// - /// Re-use already boxed object for value type. - /// this cache never release cached object. must be used only with fixed set of value types. or - /// something that grows very slowly like Guid for projects. - /// - public static object GetOrCreate(T value) where T : struct - { - // let compiler creates a cache for each value type. - return Cache.Instance.GetOrCreate(value); - } - - private class Cache where T : struct - { - public static readonly Cache Instance = new(); - - private static readonly Func s_boxer = v => v; - - // this will be never released, must be used only for fixed size set - private readonly ConcurrentDictionary _map = - new(concurrencyLevel: 2, capacity: 5); - - public object GetOrCreate(T value) - => _map.GetOrAdd(value, s_boxer); - } -}