diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/AsyncTagging/BackgroundTagging.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/AsyncTagging/BackgroundTagging.cs index 72d583c5d..7ab7f900e 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/AsyncTagging/BackgroundTagging.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/AsyncTagging/BackgroundTagging.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Acuminator.Utilities.Common; +using Acuminator.Vsix.Logger; using Shell = Microsoft.VisualStudio.Shell; @@ -80,8 +81,32 @@ public void Dispose() return; _isDisposed = true; - CancelTagging(); - _cancellationTokenSource.Dispose(); + + try + { + _cancellationTokenSource.Cancel(); + } + catch (OperationCanceledException) + { + } + catch (AggregateException aggregateException) + { + var flattened = aggregateException.Flatten(); + var exceptionsToLog = flattened.InnerExceptions.Where(ex => ex is not OperationCanceledException); + + foreach (var exception in exceptionsToLog) + { + AcuminatorVSPackage.Instance?.AcuminatorLogger?.LogException(exception, logOnlyFromAcuminatorAssemblies: false, LogMode.Warning); + } + } + catch (Exception ex) + { + AcuminatorVSPackage.Instance?.AcuminatorLogger?.LogException(ex, logOnlyFromAcuminatorAssemblies: false, LogMode.Warning); + } + finally + { + _cancellationTokenSource.Dispose(); + } } private static Task AfterTaggingActionAsync(Task taggingTask, PXRoslynColorizerTagger tagger, CancellationToken cancellationToken) diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/Base/PXTaggerBase.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/Base/PXTaggerBase.cs index f874b9201..ad7f57dd4 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/Base/PXTaggerBase.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/Base/PXTaggerBase.cs @@ -14,8 +14,10 @@ namespace Acuminator.Vsix.Coloriser { - public abstract class PXTaggerBase : IDisposable + public abstract class PXTaggerBase { + private readonly TextDocumentDisposedNotification _disposedNotification; + #pragma warning disable CS0067 public event EventHandler? TagsChanged; #pragma warning restore CS0067 @@ -32,7 +34,8 @@ public abstract class PXTaggerBase : IDisposable protected bool CacheCheckingEnabled { get; } - protected PXTaggerBase(ITextBuffer buffer, bool subscribeToSettingsChanges, bool useCacheChecking) + protected PXTaggerBase(ITextBuffer buffer, ITextDocumentFactoryService textDocumentFactory, + bool subscribeToSettingsChanges, bool useCacheChecking) { Buffer = buffer.CheckIfNull(); SubscribedToSettingsChanges = subscribeToSettingsChanges; @@ -47,6 +50,9 @@ protected PXTaggerBase(ITextBuffer buffer, bool subscribeToSettingsChanges, bool genOptionsPage.ColoringSettingChanged += ColoringSettingChangedHandler; } } + + _disposedNotification = new TextDocumentDisposedNotification(textDocumentFactory, Buffer); + _disposedNotification.CurrentTextDocumentDisposed += CleanupOnTextDocumentDisposed; } protected virtual void ColoringSettingChangedHandler(object sender, SettingChangedEventArgs e) @@ -94,8 +100,12 @@ protected internal virtual void ResetCacheAndFlags(ITextSnapshot? newSnapshotToC Snapshot = newSnapshotToCache; } - public virtual void Dispose() + protected virtual void CleanupOnTextDocumentDisposed(object sender, EventArgs e) { + Type taggerType = GetType(); + Buffer.Properties.RemoveProperty(taggerType); + _disposedNotification.CurrentTextDocumentDisposed -= CleanupOnTextDocumentDisposed; + if (!SubscribedToSettingsChanges) return; diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTagger.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTagger.cs index c884d0f1d..215664bdf 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTagger.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTagger.cs @@ -25,8 +25,9 @@ internal class PXOutliningTagger : PXTaggerBase, ITagger [MemberNotNullWhen(returnValue: true, nameof(ColorizerTagger))] public override bool HasReferenceToAcumaticaPlatform => ColorizerTagger?.HasReferenceToAcumaticaPlatform ?? false; - public PXOutliningTagger(ITextBuffer buffer, bool subscribeToSettingsChanges, bool useCacheChecking) : - base(buffer, subscribeToSettingsChanges, useCacheChecking) + public PXOutliningTagger(ITextBuffer buffer, ITextDocumentFactoryService textDocumentFactory, bool subscribeToSettingsChanges, + bool useCacheChecking) : + base(buffer, textDocumentFactory, subscribeToSettingsChanges, useCacheChecking) { } @@ -69,15 +70,15 @@ private void OnColorizingTaggerTagsChanged(object sender, SnapshotSpanEventArgs RaiseTagsChanged(); } - public override void Dispose() + protected override void CleanupOnTextDocumentDisposed(object sender, EventArgs e) { + base.CleanupOnTextDocumentDisposed(sender, e); + if (Interlocked.Exchange(ref _isSubscribed, NOT_SUBSCRIBED) == SUBSCRIBED && ColorizerTagger != null) { ColorizerTagger.TagsChanged -= OnColorizingTaggerTagsChanged; ColorizerTagger = null; } - - base.Dispose(); } } } diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTaggerProvider.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTaggerProvider.cs index 1a525d29b..a6a10b322 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTaggerProvider.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/Outlining/PXOutliningTaggerProvider.cs @@ -1,5 +1,4 @@ #nullable enable - using System; using System.Collections.Generic; using System.ComponentModel.Composition; @@ -7,30 +6,36 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; -namespace Acuminator.Vsix.Coloriser +namespace Acuminator.Vsix.Coloriser; + +[ContentType("CSharp")] +[TagType(typeof(IOutliningRegionTag))] +[TextViewRole(PredefinedTextViewRoles.Document)] +[Export(typeof(ITaggerProvider))] +public class PXOutliningTaggerProvider : ITaggerProvider { - [ContentType("CSharp")] - [TagType(typeof(IOutliningRegionTag))] - [TextViewRole(PredefinedTextViewRoles.Document)] - [Export(typeof(ITaggerProvider))] - public class PXOutliningTaggerProvider : ITaggerProvider + private readonly ITextDocumentFactoryService _textDocumentFactory; + + [ImportingConstructor] + public PXOutliningTaggerProvider(ITextDocumentFactoryService textDocumentFactory) { - public ITagger? CreateTagger(ITextBuffer buffer) where T : ITag - { - if (buffer == null || !ThreadHelper.CheckAccess()) - return null; + _textDocumentFactory = textDocumentFactory; + } + + public ITagger? CreateTagger(ITextBuffer buffer) where T : ITag + { + if (buffer == null || !ThreadHelper.CheckAccess()) + return null; - PXOutliningTagger outliningTagger = buffer.Properties.GetOrCreateSingletonProperty(() => - { - return new PXOutliningTagger(buffer, subscribeToSettingsChanges: true, useCacheChecking: true); - }); + PXOutliningTagger outliningTagger = buffer.Properties.GetOrCreateSingletonProperty(typeof(PXOutliningTagger), () => + { + return new PXOutliningTagger(buffer, _textDocumentFactory, subscribeToSettingsChanges: true, useCacheChecking: true); + }); - return outliningTagger as ITagger; - } + return outliningTagger as ITagger; } } diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/PXColorizerTaggerProvider.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/PXColorizerTaggerProvider.cs index 2e25233b1..d37c49927 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/PXColorizerTaggerProvider.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/PXColorizerTaggerProvider.cs @@ -2,180 +2,115 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using Acuminator.Utilities.Roslyn; using Acuminator.Vsix.Utilities; -using Microsoft.VisualStudio.Language.StandardClassification; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Formatting; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper; -namespace Acuminator.Vsix.Coloriser +namespace Acuminator.Vsix.Coloriser; + +[ContentType(Constants.CSharp.LegacyLanguageName)] +[TagType(typeof(IClassificationTag))] +[TextViewRole(PredefinedTextViewRoles.Document)] +[Export(typeof(IViewTaggerProvider))] +public class PXColorizerTaggerProvider : IViewTaggerProvider { - [ContentType(Constants.CSharp.LegacyLanguageName)] - [TagType(typeof(IClassificationTag))] - [TextViewRole(PredefinedTextViewRoles.Document)] - [Export(typeof(IViewTaggerProvider))] - public class PXColorizerTaggerProvider : IViewTaggerProvider - { - [Import] - internal IClassificationTypeRegistryService _classificationRegistry = null!; // Set via MEF + private readonly IClassificationTypeRegistryService _classificationRegistry; + private readonly ITextDocumentFactoryService _textDocumentFactory; - [Import] - internal IClassificationFormatMapService _classificationFormatMapService = null!; //Set via MEF + private readonly Dictionary _codeColoringClassificationTypes; - private const string TextCategory = "text"; - private static readonly object _syncRoot = new object(); - private static bool _isPriorityIncreased; + public IClassificationType? this[PXCodeType codeType] => + _codeColoringClassificationTypes.TryGetValue(codeType, out IClassificationType type) + ? type + : null; - [MemberNotNullWhen(returnValue: true, nameof(_codeColoringClassificationTypes), nameof(_braceTypeByLevel))] - protected bool AreClassificationsInitialized - { - get; - private set; - } + private readonly Dictionary _braceTypeByLevel; - private Dictionary _codeColoringClassificationTypes = null!; + public IClassificationType? this[int braceLevel] => + _braceTypeByLevel.TryGetValue(braceLevel, out IClassificationType type) + ? type + : null; - public IClassificationType? this[PXCodeType codeType] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get { - return _codeColoringClassificationTypes.TryGetValue(codeType, out IClassificationType type) - ? type - : null; - } - } + [ImportingConstructor] + public PXColorizerTaggerProvider(IClassificationTypeRegistryService classificationRegistry, ITextDocumentFactoryService textDocumentFactory) + { + _classificationRegistry = classificationRegistry; + _textDocumentFactory = textDocumentFactory; - private Dictionary _braceTypeByLevel = null!; + _codeColoringClassificationTypes = GetClassificationTypesForAcumaticaCodeElements(_classificationRegistry); + _braceTypeByLevel = GetClassificationTypesForAngleBraces(_classificationRegistry); + } + + public virtual ITagger? CreateTagger(ITextView textView, ITextBuffer textBuffer) + where T : ITag + { + if (textView == null || textBuffer == null || textView.TextBuffer != textBuffer || !ThreadHelper.CheckAccess()) + return null; - public IClassificationType? this[int braceLevel] + var tagger = textBuffer.Properties.GetOrCreateSingletonProperty(typeof(PXRoslynColorizerTagger), () => { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get { - return _braceTypeByLevel.TryGetValue(braceLevel, out IClassificationType type) - ? type - : null; - } - } - - public virtual ITagger? CreateTagger(ITextView textView, ITextBuffer textBuffer) - where T : ITag + return new PXRoslynColorizerTagger(textBuffer, _textDocumentFactory, this, subscribeToSettingsChanges: true, useCacheChecking: true); + }); + + return tagger as ITagger; + } + + private static Dictionary GetClassificationTypesForAcumaticaCodeElements( + IClassificationTypeRegistryService classificationRegistry) + { + IClassificationType bqlClassificationType = classificationRegistry.GetClassificationType(ColoringConstants.BQLOperatorFormat); + var acumaticaCodeElementsClassificationTypes = new Dictionary { - if (textView == null || textBuffer == null || textView.TextBuffer != textBuffer || !ThreadHelper.CheckAccess()) - return null; + [PXCodeType.Dac] = classificationRegistry.GetClassificationType(ColoringConstants.DacFormat), + [PXCodeType.DacExtension] = classificationRegistry.GetClassificationType(ColoringConstants.DacExtensionFormat), + [PXCodeType.DacField] = classificationRegistry.GetClassificationType(ColoringConstants.DacFieldFormat), + [PXCodeType.BqlParameter] = classificationRegistry.GetClassificationType(ColoringConstants.BQLParameterFormat), + [PXCodeType.BqlOperator] = bqlClassificationType, + [PXCodeType.BqlCommand] = bqlClassificationType, - Initialize(textBuffer); + [PXCodeType.BQLConstantPrefix] = classificationRegistry.GetClassificationType(ColoringConstants.BQLConstantPrefixFormat), + [PXCodeType.BQLConstantEnding] = classificationRegistry.GetClassificationType(ColoringConstants.BQLConstantEndingFormat), - var tagger = textBuffer.Properties.GetOrCreateSingletonProperty(typeof(PXRoslynColorizerTagger), () => - { - return new PXRoslynColorizerTagger(textBuffer, this, subscribeToSettingsChanges: true, useCacheChecking: true); - }); + [PXCodeType.PXGraph] = classificationRegistry.GetClassificationType(ColoringConstants.PXGraphFormat), + [PXCodeType.PXAction] = classificationRegistry.GetClassificationType(ColoringConstants.PXActionFormat), + }; - return tagger as ITagger; - } + return acumaticaCodeElementsClassificationTypes; + } - [MemberNotNull(nameof(_codeColoringClassificationTypes), nameof(_braceTypeByLevel))] - protected void Initialize(ITextBuffer textBuffer) + private static Dictionary GetClassificationTypesForAngleBraces(IClassificationTypeRegistryService classificationRegistry) + { + var braceTypeByLevel = new Dictionary(capacity: ColoringConstants.MaxBraceLevel) { - if (AreClassificationsInitialized) - return; + [0] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_1_Format), + [1] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_2_Format), + [2] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_3_Format), - AreClassificationsInitialized = true; - InitializeClassificationTypes(); - IncreaseCommentFormatTypesPriority(_classificationRegistry, _classificationFormatMapService, - _codeColoringClassificationTypes[PXCodeType.BqlParameter]); - } + [3] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_4_Format), + [4] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_5_Format), + [5] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_6_Format), - [MemberNotNull(nameof(_codeColoringClassificationTypes), nameof(_braceTypeByLevel))] - protected void InitializeClassificationTypes() - { - IClassificationType bqlClassificationType = _classificationRegistry.GetClassificationType(ColoringConstants.BQLOperatorFormat); - - _codeColoringClassificationTypes = new Dictionary - { - [PXCodeType.Dac] = _classificationRegistry.GetClassificationType(ColoringConstants.DacFormat), - [PXCodeType.DacExtension] = _classificationRegistry.GetClassificationType(ColoringConstants.DacExtensionFormat), - [PXCodeType.DacField] = _classificationRegistry.GetClassificationType(ColoringConstants.DacFieldFormat), - [PXCodeType.BqlParameter] = _classificationRegistry.GetClassificationType(ColoringConstants.BQLParameterFormat), - [PXCodeType.BqlOperator] = bqlClassificationType, - [PXCodeType.BqlCommand] = bqlClassificationType, - - [PXCodeType.BQLConstantPrefix] = _classificationRegistry.GetClassificationType(ColoringConstants.BQLConstantPrefixFormat), - [PXCodeType.BQLConstantEnding] = _classificationRegistry.GetClassificationType(ColoringConstants.BQLConstantEndingFormat), - - [PXCodeType.PXGraph] = _classificationRegistry.GetClassificationType(ColoringConstants.PXGraphFormat), - [PXCodeType.PXAction] = _classificationRegistry.GetClassificationType(ColoringConstants.PXActionFormat), - }; - - _braceTypeByLevel = new Dictionary(capacity: ColoringConstants.MaxBraceLevel) - { - [0] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_1_Format), - [1] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_2_Format), - [2] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_3_Format), - - [3] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_4_Format), - [4] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_5_Format), - [5] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_6_Format), - - [6] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_7_Format), - [7] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_8_Format), - [8] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_9_Format), - - [9] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_10_Format), - [10] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_11_Format), - [11] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_12_Format), - - [12] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_13_Format), - [13] = _classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_14_Format), - }; - } - - private static void IncreaseCommentFormatTypesPriority(IClassificationTypeRegistryService registry, IClassificationFormatMapService formatMapService, - IClassificationType highestPriorityType) - { - bool lockTaken = false; - Monitor.TryEnter(_syncRoot, ref lockTaken); - - if (lockTaken) - { - try - { - if (!_isPriorityIncreased) - { - _isPriorityIncreased = true; - IClassificationFormatMap formatMap = formatMapService.GetClassificationFormatMap(category: TextCategory); - IncreaseServiceFormatPriority(formatMap, registry, PredefinedClassificationTypeNames.ExcludedCode, highestPriorityType); - IncreaseServiceFormatPriority(formatMap, registry, PredefinedClassificationTypeNames.Comment, highestPriorityType); - } - } - finally - { - Monitor.Exit(_syncRoot); - } - } - } - - private static void IncreaseServiceFormatPriority(IClassificationFormatMap formatMap, IClassificationTypeRegistryService registry, string formatName, - IClassificationType highestPriorityType) - { - IClassificationType predefinedClassificationType = registry.GetClassificationType(formatName); - IClassificationType artificialClassType = registry.CreateTransientClassificationType(predefinedClassificationType); - TextFormattingRunProperties properties = formatMap.GetExplicitTextProperties(predefinedClassificationType); - - formatMap.AddExplicitTextProperties(artificialClassType, properties, highestPriorityType); - formatMap.SwapPriorities(artificialClassType, predefinedClassificationType); - formatMap.SwapPriorities(highestPriorityType, predefinedClassificationType); - } + [6] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_7_Format), + [7] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_8_Format), + [8] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_9_Format), + + [9] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_10_Format), + [10] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_11_Format), + [11] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_12_Format), + + [12] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_13_Format), + [13] = classificationRegistry.GetClassificationType(ColoringConstants.BraceLevel_14_Format), + }; + + return braceTypeByLevel; } } diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/PXRoslynColorizerTagger.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/PXRoslynColorizerTagger.cs index ff2d857cf..9adf80168 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/PXRoslynColorizerTagger.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/PXRoslynColorizerTagger.cs @@ -22,7 +22,7 @@ namespace Acuminator.Vsix.Coloriser; /// /// A Roslyn-based colorizer tagger. /// -internal partial class PXRoslynColorizerTagger : PXTaggerBase, ITagger, IDisposable +internal partial class PXRoslynColorizerTagger : PXTaggerBase, ITagger { protected internal TagsCacheAsync ClassificationTagsCache { get; } @@ -47,19 +47,23 @@ internal bool LastTaggingWasSuccessful private readonly RoslynWorkspaceProvider _roslynWorkspaceProvider; private readonly SourceTextContainer _cachedTextContainer; - public PXRoslynColorizerTagger(ITextBuffer buffer, PXColorizerTaggerProvider provider, bool subscribeToSettingsChanges, - bool useCacheChecking) : - base(buffer, subscribeToSettingsChanges, useCacheChecking) + public PXRoslynColorizerTagger(ITextBuffer buffer, ITextDocumentFactoryService textDocumentFactory, PXColorizerTaggerProvider provider, + bool subscribeToSettingsChanges, bool useCacheChecking) : + base(buffer, textDocumentFactory, subscribeToSettingsChanges, useCacheChecking) { Provider = provider.CheckIfNull(); _cachedTextContainer = Buffer.AsTextContainer(); ClassificationTagsCache = new TagsCacheAsync(); OutliningsTagsCache = new TagsCacheAsync(); - _roslynWorkspaceProvider = new RoslynWorkspaceProvider(buffer); + // Roslyn workspace provider creation and subscription to workspace events should be done under sync lock to avoid + // unlikely race condition in the constructor. + // The lock is expected to be re-entrant, the Monitor synchronization should not be changed to another synchronization mechanism without a rework. + object workspaceLock = new object(); - lock (_roslynWorkspaceProvider.WorkspaceSubscriptionLocker) + lock (workspaceLock) { + _roslynWorkspaceProvider = RoslynWorkspaceProvider.Create(_cachedTextContainer, workspaceLock); _roslynWorkspaceProvider.WorkspaceChanged += WorkspaceAttachedToDocumentChanged; // Drive initial setup through the same code path as change events. @@ -226,8 +230,10 @@ private void WalkDocumentSyntaxTreeForTags(ParsedDocument document, Cancellation OutliningsTagsCache.CompleteProcessing(); } - public override void Dispose() + protected override void CleanupOnTextDocumentDisposed(object sender, EventArgs e) { + base.CleanupOnTextDocumentDisposed(sender, e); + lock (_roslynWorkspaceProvider.WorkspaceSubscriptionLocker) { _roslynWorkspaceProvider.WorkspaceChanged -= WorkspaceAttachedToDocumentChanged; @@ -235,17 +241,15 @@ public override void Dispose() if (workspaceToUnsubscribe != null) workspaceToUnsubscribe.WorkspaceChanged -= OnWorkspaceChanged; + + _roslynWorkspaceProvider.Dispose(); } - - _roslynWorkspaceProvider.Dispose(); BackgroundTagging?.Dispose(); ClassificationTagsCache.Reset(); OutliningsTagsCache.Reset(); _hasReferenceToAcumaticaPlatform = false; - - base.Dispose(); } private void WorkspaceAttachedToDocumentChanged(object sender, DocumentWorkspaceChangedEventArgs e) diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/Roslyn/RoslynWorkspaceProvider.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/Roslyn/RoslynWorkspaceProvider.cs index d75db6901..5210580f7 100644 --- a/src/Acuminator/Acuminator.Vsix/Coloriser/Roslyn/RoslynWorkspaceProvider.cs +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/Roslyn/RoslynWorkspaceProvider.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Text; namespace Acuminator.Vsix.Coloriser; @@ -28,7 +27,7 @@ internal class RoslynWorkspaceProvider : IDisposable /// This locker has to be externally available because external subscribers like Roslyn colorizer subscribe on workspace changes and have unavoidable race condition
/// that has to use this locker to synchronize with change of the Roslyn workspace associated with a VS text buffer. /// - public object WorkspaceSubscriptionLocker { get; } = new object(); + public object WorkspaceSubscriptionLocker { get; } private Workspace? _workspace; @@ -45,16 +44,24 @@ public Workspace? Workspace public event EventHandler? WorkspaceChanged; - public RoslynWorkspaceProvider(ITextBuffer buffer) + private RoslynWorkspaceProvider(WorkspaceRegistration workspaceRegistration, object workspaceSubscriptionLocker) { - SourceTextContainer sourceTextContainer = buffer.CheckIfNull().AsTextContainer(); + WorkspaceSubscriptionLocker = workspaceSubscriptionLocker; + _workspaceRegistration = workspaceRegistration; + _workspaceRegistration.WorkspaceChanged += OnWorkspaceChanged; + _workspace = GetWorkspaceThatSupportsColoring(_workspaceRegistration); + } - lock (WorkspaceSubscriptionLocker) - { - _workspaceRegistration = Workspace.GetWorkspaceRegistration(sourceTextContainer); - _workspaceRegistration.WorkspaceChanged += OnWorkspaceChanged; + internal static RoslynWorkspaceProvider Create(SourceTextContainer sourceTextContainer, object syncLock) + { + sourceTextContainer.ThrowOnNull(); + syncLock.ThrowOnNull(); - _workspace = GetWorkspaceThatSupportsColoring(_workspaceRegistration); + lock (syncLock) + { + var workspaceRegistration = Workspace.GetWorkspaceRegistration(sourceTextContainer); + var workspaceProvider = new RoslynWorkspaceProvider(workspaceRegistration, syncLock); + return workspaceProvider; } } diff --git a/src/Acuminator/Acuminator.Vsix/Coloriser/TextDocumentDisposedNotification.cs b/src/Acuminator/Acuminator.Vsix/Coloriser/TextDocumentDisposedNotification.cs new file mode 100644 index 000000000..882f646c8 --- /dev/null +++ b/src/Acuminator/Acuminator.Vsix/Coloriser/TextDocumentDisposedNotification.cs @@ -0,0 +1,75 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; + +using Acuminator.Utilities.Common; + +using Microsoft.VisualStudio.Text; + +namespace Acuminator.Vsix.Coloriser; + +/// +/// A text document disposed notification provider. +/// +/// +/// This component is not thread safe and is supposed to run only on the UI thread. +/// +internal class TextDocumentDisposedNotification +{ + private readonly ITextDocumentFactoryService _textDocumentFactory; + private readonly ITextBuffer _textBuffer; + private ITextDocument? _textDocument; + + public event EventHandler? CurrentTextDocumentDisposed; + + public TextDocumentDisposedNotification(ITextDocumentFactoryService textDocumentFactory, ITextBuffer textBuffer) + { + _textDocumentFactory = textDocumentFactory.CheckIfNull(); + _textBuffer = textBuffer.CheckIfNull(); + + SubscribeToDocumentLifetimeEvents(); + } + + private void SubscribeToDocumentLifetimeEvents() + { + // If a document already exists for this buffer just attach the disposed handler. + if (_textDocumentFactory.TryGetTextDocument(_textBuffer, out ITextDocument existingTextDocument)) + { + SubscribeOnTextDocumentDisposedEvent(existingTextDocument); + } + else + { + // If the document is created later, attach to the created event and listen for the right document to be created, then attach the disposed handler. + _textDocumentFactory.TextDocumentCreated += OnTextDocumentCreated; + } + } + + private void OnTextDocumentCreated(object sender, TextDocumentEventArgs e) + { + // Filter out doc creation events for other documents + if (e.TextDocument == null || !ReferenceEquals(_textBuffer, e.TextDocument.TextBuffer)) + return; + + //Dispose of the subscription immediately to always run the handler only once + _textDocumentFactory.TextDocumentCreated -= OnTextDocumentCreated; + SubscribeOnTextDocumentDisposedEvent(e.TextDocument); + } + + private void SubscribeOnTextDocumentDisposedEvent(ITextDocument textDocument) + { + _textDocument = textDocument; + _textDocumentFactory.TextDocumentDisposed += OnTextDocumentDisposed; + } + + private void OnTextDocumentDisposed(object sender, TextDocumentEventArgs e) + { + // Filter out dispose events for other documents + if (!ReferenceEquals(_textDocument, e.TextDocument)) + return; + + //Dispose of the subscription immediately to always run the handler only once + _textDocumentFactory.TextDocumentDisposed -= OnTextDocumentDisposed; + CurrentTextDocumentDisposed?.Invoke(this, EventArgs.Empty); + } +}