internal Task HandleEndBuildAsync(BuildEventArgs args)
        {
            if (!args.Success)
            {
                // Build failed
                return(Task.CompletedTask);
            }

            return(_projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync((projectItem, ct) =>
            {
                if (!_projectService.IsSupportedProject(projectItem))
                {
                    // We're hooked into all build events, it's possible to get called with an unsupported project item type.
                    return;
                }

                var projectPath = _projectService.GetProjectPath(projectItem);
                var projectSnapshot = _projectManager.GetLoadedProject(projectPath);
                if (projectSnapshot != null)
                {
                    var workspaceProject = _projectManager.Workspace.CurrentSolution?.Projects.FirstOrDefault(
                        project => FilePathComparer.Instance.Equals(project.FilePath, projectSnapshot.FilePath));
                    if (workspaceProject != null)
                    {
                        // Trigger a tag helper update by forcing the project manager to see the workspace Project
                        // from the current solution.
                        _workspaceStateGenerator.Update(workspaceProject, projectSnapshot, CancellationToken.None);
                    }
                }
            }, args.SolutionItem, CancellationToken.None));
        }
            public override ValueTask ProcessAsync(CancellationToken cancellationToken)
            {
                var task = _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(
                    () => _workspaceStateGenerator.Update(_workspaceProject, _projectSnapshot, cancellationToken),
                    cancellationToken);

                return(new ValueTask(task));
            }
        // This gets called when the project has finished building.
        public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel)
        {
            var projectPath     = _projectService.GetProjectPath(pHierProj);
            var projectSnapshot = _projectManager.GetLoadedProject(projectPath);

            if (projectSnapshot != null)
            {
                var workspaceProject = _projectManager.Workspace.CurrentSolution.Projects.FirstOrDefault(
                    wp => FilePathComparer.Instance.Equals(wp.FilePath, projectSnapshot.FilePath));
                if (workspaceProject != null)
                {
                    // Trigger a tag helper update by forcing the project manager to see the workspace Project
                    // from the current solution.
                    _workspaceStateGenerator.Update(workspaceProject, projectSnapshot, CancellationToken.None);
                }
            }

            return(VSConstants.S_OK);
        }
        internal Task OnProjectBuiltAsync(IVsHierarchy projectHierarchy, CancellationToken cancellationToken)
        {
            var projectFilePath = _projectService.GetProjectPath(projectHierarchy);

            return(_projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() =>
            {
                var projectSnapshot = _projectManager.GetLoadedProject(projectFilePath);
                if (projectSnapshot != null)
                {
                    var workspaceProject = _projectManager.Workspace.CurrentSolution.Projects.FirstOrDefault(
                        wp => FilePathComparer.Instance.Equals(wp.FilePath, projectSnapshot.FilePath));
                    if (workspaceProject != null)
                    {
                        // Trigger a tag helper update by forcing the project manager to see the workspace Project
                        // from the current solution.
                        _workspaceStateGenerator.Update(workspaceProject, projectSnapshot, cancellationToken);
                    }
                }
            }, cancellationToken));
        }
        private void ExecuteUpdate(object state)
        {
            var projectFilePath = SelectedProject?.FilePath;

            if (projectFilePath == null)
            {
                return;
            }

            var projectSnapshot = _projectManager.GetLoadedProject(projectFilePath);

            if (projectSnapshot != null)
            {
                var workspaceProject = _workspace.CurrentSolution.Projects.FirstOrDefault(
                    wp => FilePathComparer.Instance.Equals(wp.FilePath, SelectedProject.FilePath));
                var solution = _workspace.CurrentSolution;
                if (workspaceProject != null)
                {
                    _workspaceStateGenerator.Update(workspaceProject, projectSnapshot);
                }
            }
        }
        // Internal for testing
        internal void ProjectOperations_EndBuild(object sender, BuildEventArgs args)
        {
            if (args == null)
            {
                throw new ArgumentNullException(nameof(args));
            }

            _foregroundDispatcher.AssertForegroundThread();

            if (!args.Success)
            {
                // Build failed
                return;
            }

            var projectItem = args.SolutionItem;

            if (!_projectService.IsSupportedProject(projectItem))
            {
                // We're hooked into all build events, it's possible to get called with an unsupported project item type.
                return;
            }

            var projectPath     = _projectService.GetProjectPath(projectItem);
            var projectSnapshot = _projectManager.GetLoadedProject(projectPath);

            if (projectSnapshot != null)
            {
                var workspaceProject = _projectManager.Workspace.CurrentSolution?.Projects.FirstOrDefault(
                    project => FilePathComparer.Instance.Equals(project.FilePath, projectSnapshot.FilePath));
                if (workspaceProject != null)
                {
                    // Trigger a tag helper update by forcing the project manager to see the workspace Project
                    // from the current solution.
                    _workspaceStateGenerator.Update(workspaceProject, projectSnapshot);
                }
            }
        }
        // Internal for testing, virtual for temporary VSCode workaround
        internal virtual void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
        {
            Project project;

            switch (e.Kind)
            {
            case WorkspaceChangeKind.ProjectAdded:
            {
                project = e.NewSolution.GetProject(e.ProjectId);

                Debug.Assert(project != null);

                if (TryGetProjectSnapshot(project.FilePath, out var projectSnapshot))
                {
                    _workspaceStateGenerator.Update(project, projectSnapshot);
                }
                break;
            }

            case WorkspaceChangeKind.ProjectChanged:
            case WorkspaceChangeKind.ProjectReloaded:
            {
                project = e.NewSolution.GetProject(e.ProjectId);

                if (TryGetProjectSnapshot(project?.FilePath, out var _))
                {
                    EnqueueUpdate(e.ProjectId);
                }
                break;
            }

            case WorkspaceChangeKind.ProjectRemoved:
            {
                project = e.OldSolution.GetProject(e.ProjectId);
                Debug.Assert(project != null);

                if (TryGetProjectSnapshot(project?.FilePath, out var projectSnapshot))
                {
                    _workspaceStateGenerator.Update(workspaceProject: null, projectSnapshot);
                }

                break;
            }

            case WorkspaceChangeKind.DocumentChanged:
            case WorkspaceChangeKind.DocumentReloaded:
            {
                // This is the case when a component declaration file changes on disk. We have an MSBuild
                // generator configured by the SDK that will poke these files on disk when a component
                // is saved, or loses focus in the editor.
                project = e.OldSolution.GetProject(e.ProjectId);
                var document = project.GetDocument(e.DocumentId);

                if (document.FilePath == null)
                {
                    break;
                }

                // Using EndsWith because Path.GetExtension will ignore everything before .cs
                // Using Ordinal because the SDK generates these filenames.
                if (document.FilePath.EndsWith(".cshtml.g.cs", StringComparison.Ordinal) || document.FilePath.EndsWith(".razor.g.cs", StringComparison.Ordinal))
                {
                    EnqueueUpdate(e.ProjectId);
                }

                break;
            }

            case WorkspaceChangeKind.SolutionAdded:
            case WorkspaceChangeKind.SolutionChanged:
            case WorkspaceChangeKind.SolutionCleared:
            case WorkspaceChangeKind.SolutionReloaded:
            case WorkspaceChangeKind.SolutionRemoved:

                if (e.OldSolution != null)
                {
                    foreach (var p in e.OldSolution.Projects)
                    {
                        if (TryGetProjectSnapshot(p?.FilePath, out var projectSnapshot))
                        {
                            _workspaceStateGenerator.Update(workspaceProject: null, projectSnapshot);
                        }
                    }
                }

                InitializeSolution(e.NewSolution);
                break;
            }
        }
        // Internal for testing, virtual for temporary VSCode workaround
        internal virtual void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
        {
            Project project;

            switch (e.Kind)
            {
            case WorkspaceChangeKind.ProjectAdded:
            {
                project = e.NewSolution.GetProject(e.ProjectId);

                Debug.Assert(project != null);

                if (TryGetProjectSnapshot(project.FilePath, out var projectSnapshot))
                {
                    _workspaceStateGenerator.Update(project, projectSnapshot);
                }
                break;
            }

            case WorkspaceChangeKind.ProjectChanged:
            case WorkspaceChangeKind.ProjectReloaded:
            {
                project = e.NewSolution.GetProject(e.ProjectId);

                if (TryGetProjectSnapshot(project?.FilePath, out var _))
                {
                    EnqueueUpdate(e.ProjectId);
                }
                break;
            }

            case WorkspaceChangeKind.ProjectRemoved:
            {
                project = e.OldSolution.GetProject(e.ProjectId);
                Debug.Assert(project != null);

                if (TryGetProjectSnapshot(project?.FilePath, out var projectSnapshot))
                {
                    _workspaceStateGenerator.Update(workspaceProject: null, projectSnapshot);
                }

                break;
            }

            case WorkspaceChangeKind.DocumentChanged:
            case WorkspaceChangeKind.DocumentReloaded:
            {
                // This is the case when a component declaration file changes on disk. We have an MSBuild
                // generator configured by the SDK that will poke these files on disk when a component
                // is saved, or loses focus in the editor.
                project = e.OldSolution.GetProject(e.ProjectId);
                var document = project.GetDocument(e.DocumentId);

                if (document.FilePath == null)
                {
                    return;
                }

                // Using EndsWith because Path.GetExtension will ignore everything before .cs
                // Using Ordinal because the SDK generates these filenames.
                // Stll have .cshtml.g.cs and .razor.g.cs for Razor.VSCode scenarios.
                if (document.FilePath.EndsWith(".cshtml.g.cs", StringComparison.Ordinal) ||
                    document.FilePath.EndsWith(".razor.g.cs", StringComparison.Ordinal) ||
                    document.FilePath.EndsWith(".razor", StringComparison.Ordinal) ||

                    // VSCode's background C# document
                    document.FilePath.EndsWith("__bg__virtual.cs"))
                {
                    EnqueueUpdate(e.ProjectId);
                    return;
                }

                // We now know we're not operating directly on a Razor file. However, it's possible the user is operating on a partial class that is associated with a Razor file.

                if (IsPartialComponentClass(document))
                {
                    EnqueueUpdate(e.ProjectId);
                }

                break;
            }

            case WorkspaceChangeKind.SolutionAdded:
            case WorkspaceChangeKind.SolutionChanged:
            case WorkspaceChangeKind.SolutionCleared:
            case WorkspaceChangeKind.SolutionReloaded:
            case WorkspaceChangeKind.SolutionRemoved:

                if (e.OldSolution != null)
                {
                    foreach (var p in e.OldSolution.Projects)
                    {
                        if (TryGetProjectSnapshot(p?.FilePath, out var projectSnapshot))
                        {
                            _workspaceStateGenerator.Update(workspaceProject: null, projectSnapshot);
                        }
                    }
                }

                InitializeSolution(e.NewSolution);
                break;
            }
        }