File: Workspace\Workspace_Events.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.WorkspaceEventMap;
 
namespace Microsoft.CodeAnalysis;
 
public abstract partial class Workspace
{
    private readonly WorkspaceEventMap _eventMap = new();
 
    internal enum WorkspaceEventType
    {
        DocumentActiveContextChanged,
        DocumentClosed,
        DocumentOpened,
        TextDocumentClosed,
        TextDocumentOpened,
        WorkspaceChange,
        WorkspaceChangedImmediate,
        WorkspaceFailed,
    }
 
    private IWorkspaceEventListenerService? _workspaceEventListenerService;
 
    #region Event Registration
 
    /// <summary>
    /// Registers a handler that is fired whenever the current solution is changed.
    /// </summary>
    internal WorkspaceEventRegistration RegisterWorkspaceChangedHandler(Action<WorkspaceChangeEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.WorkspaceChange, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired *immediately* whenever the current solution is changed.
    /// Handlers should be written to be very fast. Always called from the thread changing the workspace,
    /// regardless of the preferences indicated by the passed in options. This thread my vary depending
    /// on the workspace.
    /// </summary>
    internal WorkspaceEventRegistration RegisterWorkspaceChangedImmediateHandler(Action<WorkspaceChangeEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.WorkspaceChangedImmediate, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired whenever the workspace or part of its solution model
    /// fails to access a file or other external resource.
    /// </summary>
    internal WorkspaceEventRegistration RegisterWorkspaceFailedHandler(Action<WorkspaceDiagnosticEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.WorkspaceFailed, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired when a <see cref="Document"/> is opened in the editor.
    /// </summary>
    internal WorkspaceEventRegistration RegisterDocumentOpenedHandler(Action<DocumentEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.DocumentOpened, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired when a <see cref="Document"/> is closed in the editor.
    /// </summary>
    internal WorkspaceEventRegistration RegisterDocumentClosedHandler(Action<DocumentEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.DocumentClosed, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired when any <see cref="TextDocument"/> is opened in the editor.
    /// </summary>
    internal WorkspaceEventRegistration RegisterTextDocumentOpenedHandler(Action<TextDocumentEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.TextDocumentOpened, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired when any <see cref="TextDocument"/> is closed in the editor.
    /// </summary>
    internal WorkspaceEventRegistration RegisterTextDocumentClosedHandler(Action<TextDocumentEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.TextDocumentClosed, handler, options);
 
    /// <summary>
    /// Registers a handler that is fired when the active context document associated with a buffer 
    /// changes.
    /// </summary>
    internal WorkspaceEventRegistration RegisterDocumentActiveContextChangedHandler(Action<DocumentActiveContextChangedEventArgs> handler, WorkspaceEventOptions? options = null)
        => RegisterHandler(WorkspaceEventType.DocumentActiveContextChanged, handler, options);
 
    private WorkspaceEventRegistration RegisterHandler<TEventArgs>(WorkspaceEventType eventType, Action<TEventArgs> handler, WorkspaceEventOptions? options = null)
        where TEventArgs : EventArgs
    {
        var handlerAndOptions = new WorkspaceEventHandlerAndOptions(args => handler((TEventArgs)args), options ?? WorkspaceEventOptions.DefaultOptions);
 
        return _eventMap.AddEventHandler(eventType, handlerAndOptions);
    }
 
    #endregion
 
    protected Task RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind kind, Solution oldSolution, Solution newSolution, ProjectId? projectId = null, DocumentId? documentId = null)
    {
        if (newSolution == null)
            throw new ArgumentNullException(nameof(newSolution));
 
        if (oldSolution == newSolution)
            return Task.CompletedTask;
 
        if (projectId == null && documentId != null)
            projectId = documentId.ProjectId;
 
        WorkspaceChangeEventArgs? args = null;
 
        var immediateHandlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceChangedImmediate);
        if (immediateHandlerSet.HasHandlers)
        {
            args = new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId);
            immediateHandlerSet.RaiseEvent(args, shouldRaiseEvent: static option => true);
        }
 
        var handlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceChange);
        if (handlerSet.HasHandlers)
        {
            args ??= new WorkspaceChangeEventArgs(kind, oldSolution, newSolution, projectId, documentId);
            return this.ScheduleTask(args, handlerSet);
        }
 
        return Task.CompletedTask;
    }
 
    protected internal virtual void OnWorkspaceFailed(WorkspaceDiagnostic diagnostic)
    {
        var handlerSet = GetEventHandlers(WorkspaceEventType.WorkspaceFailed);
        if (handlerSet.HasHandlers)
        {
            var args = new WorkspaceDiagnosticEventArgs(diagnostic);
            handlerSet.RaiseEvent(args, shouldRaiseEvent: static option => true);
        }
    }
 
    protected Task RaiseDocumentOpenedEventAsync(Document document)
        => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), WorkspaceEventType.DocumentOpened);
 
    protected Task RaiseTextDocumentOpenedEventAsync(TextDocument document)
        => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), WorkspaceEventType.TextDocumentOpened);
 
    private Task RaiseTextDocumentOpenedOrClosedEventAsync<TDocument, TDocumentEventArgs>(
        TDocument document,
        TDocumentEventArgs args,
        WorkspaceEventType eventType)
        where TDocument : TextDocument
        where TDocumentEventArgs : EventArgs
    {
        var handlerSet = GetEventHandlers(eventType);
        if (handlerSet.HasHandlers && document != null)
            return this.ScheduleTask(args, handlerSet);
 
        return Task.CompletedTask;
    }
 
    protected Task RaiseDocumentClosedEventAsync(Document document)
        => RaiseTextDocumentOpenedOrClosedEventAsync(document, new DocumentEventArgs(document), WorkspaceEventType.DocumentClosed);
 
    protected Task RaiseTextDocumentClosedEventAsync(TextDocument document)
        => RaiseTextDocumentOpenedOrClosedEventAsync(document, new TextDocumentEventArgs(document), WorkspaceEventType.TextDocumentClosed);
 
    [Obsolete("This member is obsolete. Use the RaiseDocumentActiveContextChangedEventAsync(SourceTextContainer, DocumentId, DocumentId) overload instead.", error: true)]
    protected Task RaiseDocumentActiveContextChangedEventAsync(Document document)
        => throw new NotImplementedException();
 
    protected Task RaiseDocumentActiveContextChangedEventAsync(SourceTextContainer sourceTextContainer, DocumentId oldActiveContextDocumentId, DocumentId newActiveContextDocumentId)
    {
        if (sourceTextContainer == null || oldActiveContextDocumentId == null || newActiveContextDocumentId == null)
            return Task.CompletedTask;
 
        var handlerSet = GetEventHandlers(WorkspaceEventType.DocumentActiveContextChanged);
        if (handlerSet.HasHandlers)
        {
            // Capture the current solution snapshot (inside the _serializationLock of OnDocumentContextUpdated)
            var currentSolution = this.CurrentSolution;
            var args = new DocumentActiveContextChangedEventArgs(currentSolution, sourceTextContainer, oldActiveContextDocumentId, newActiveContextDocumentId);
 
            return this.ScheduleTask(args, handlerSet);
        }
 
        return Task.CompletedTask;
    }
 
    private EventHandlerSet GetEventHandlers(WorkspaceEventType eventType)
    {
        // this will register features that want to listen to workspace events
        // lazily first time workspace event is actually fired
        EnsureEventListeners();
        return _eventMap.GetEventHandlerSet(eventType);
    }
 
    private void EnsureEventListeners()
    {
        // Cache this service so it doesn't need to be retrieved from MEF during disposal.
        _workspaceEventListenerService ??= this.Services.GetService<IWorkspaceEventListenerService>();
 
        _workspaceEventListenerService?.EnsureListeners();
    }
}