Add a background listener for notifications
This change adds an actual background worker for listening to project change notifications and starts sending updates when the project's razor dependencies change. I had to do a litle surgery to get things working. There were plenty of small bug fixes. Additionally I got rid of the WeakReferences for tracking listeners. I was seeing TextBuffers hanging around in VS longer than I expected and the WeakReferences weren't getting cleaned up. I think it's better that we just track the lifetime.
This commit is contained in:
parent
7a0abc3f67
commit
6e6a24cbb4
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal class DefaultErrorReporter : ErrorReporter
|
||||
{
|
||||
public override void ReportError(Exception exception)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public override void ReportError(Exception exception, Project project)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Composition;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
[Shared]
|
||||
[ExportWorkspaceServiceFactory(typeof(ErrorReporter))]
|
||||
internal class DefaultErrorReporterFactory : IWorkspaceServiceFactory
|
||||
{
|
||||
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
|
||||
{
|
||||
return new DefaultErrorReporter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal abstract class ErrorReporter : IWorkspaceService
|
||||
{
|
||||
public abstract void ReportError(Exception exception);
|
||||
|
||||
public abstract void ReportError(Exception exception, Project project);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal class DefaultProjectSnapshotListener : ProjectSnapshotListener
|
||||
{
|
||||
public override event EventHandler<ProjectChangeEventArgs> ProjectChanged;
|
||||
|
||||
internal void Notify(ProjectChangeEventArgs e)
|
||||
{
|
||||
var handler = ProjectChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,12 +9,38 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
internal class DefaultProjectSnapshotManager : ProjectSnapshotManagerBase
|
||||
{
|
||||
private readonly ProjectSnapshotChangeTrigger[] _triggers;
|
||||
private readonly Dictionary<ProjectId, DefaultProjectSnapshot> _projects;
|
||||
private readonly List<WeakReference<DefaultProjectSnapshotListener>> _listeners;
|
||||
public override event EventHandler<ProjectChangeEventArgs> Changed;
|
||||
|
||||
public DefaultProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
|
||||
private readonly ErrorReporter _errorReporter;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly ProjectSnapshotChangeTrigger[] _triggers;
|
||||
private readonly ProjectSnapshotWorkerQueue _workerQueue;
|
||||
private readonly ProjectSnapshotWorker _worker;
|
||||
|
||||
private readonly Dictionary<ProjectId, DefaultProjectSnapshot> _projects;
|
||||
|
||||
public DefaultProjectSnapshotManager(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ErrorReporter errorReporter,
|
||||
ProjectSnapshotWorker worker,
|
||||
IEnumerable<ProjectSnapshotChangeTrigger> triggers,
|
||||
Workspace workspace)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (errorReporter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorReporter));
|
||||
}
|
||||
|
||||
if (worker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(worker));
|
||||
}
|
||||
|
||||
if (triggers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(triggers));
|
||||
|
|
@ -25,11 +51,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_errorReporter = errorReporter;
|
||||
_worker = worker;
|
||||
_triggers = triggers.ToArray();
|
||||
Workspace = workspace;
|
||||
|
||||
_projects = new Dictionary<ProjectId, DefaultProjectSnapshot>();
|
||||
_listeners = new List<WeakReference<DefaultProjectSnapshotListener>>();
|
||||
_workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker);
|
||||
|
||||
for (var i = 0; i < _triggers.Length; i++)
|
||||
{
|
||||
|
|
@ -37,18 +66,27 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
}
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ProjectSnapshot> Projects => _projects.Values.ToArray();
|
||||
public override IReadOnlyList<ProjectSnapshot> Projects
|
||||
{
|
||||
get
|
||||
{
|
||||
return _projects.Values.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultProjectSnapshot FindProject(ProjectId id)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
_projects.TryGetValue(id, out var project);
|
||||
return project;
|
||||
}
|
||||
|
||||
public override Workspace Workspace { get; }
|
||||
|
||||
public override ProjectSnapshotListener Subscribe()
|
||||
{
|
||||
var subscription = new DefaultProjectSnapshotListener();
|
||||
_listeners.Add(new WeakReference<DefaultProjectSnapshotListener>(subscription));
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public override void ProjectAdded(Project underlyingProject)
|
||||
{
|
||||
if (underlyingProject == null)
|
||||
|
|
@ -60,7 +98,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
_projects[underlyingProject.Id] = snapshot;
|
||||
|
||||
// New projects always start dirty, need to compute state in the background.
|
||||
NotifyBackgroundWorker();
|
||||
NotifyBackgroundWorker(snapshot.UnderlyingProject);
|
||||
|
||||
// We need to notify listeners about every project add.
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Added));
|
||||
|
|
@ -84,12 +122,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// We don't need to notify listeners yet because we don't have any **new** computed state. However we do
|
||||
// need to trigger the background work to asynchronously compute the effect of the updates.
|
||||
NotifyBackgroundWorker();
|
||||
NotifyBackgroundWorker(snapshot.UnderlyingProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ProjectChanged(ProjectSnapshotUpdateContext update)
|
||||
public override void ProjectUpdated(ProjectSnapshotUpdateContext update)
|
||||
{
|
||||
if (update == null)
|
||||
{
|
||||
|
|
@ -106,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// It's possible that the snapshot can still be dirty if we got a project update while computing state in
|
||||
// the background. We need to trigger the background work to asynchronously compute the effect of the updates.
|
||||
NotifyBackgroundWorker();
|
||||
NotifyBackgroundWorker(snapshot.UnderlyingProject);
|
||||
}
|
||||
|
||||
// Now we need to know if the changes that we applied are significant. If that's the case then
|
||||
|
|
@ -146,25 +184,29 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
}
|
||||
|
||||
// virtual so it can be overridden in tests
|
||||
protected virtual void NotifyBackgroundWorker()
|
||||
protected virtual void NotifyBackgroundWorker(Project project)
|
||||
{
|
||||
|
||||
_workerQueue.Enqueue(project);
|
||||
}
|
||||
|
||||
// virtual so it can be overridden in tests
|
||||
protected virtual void NotifyListeners(ProjectChangeEventArgs e)
|
||||
{
|
||||
for (var i = 0; i < _listeners.Count; i++)
|
||||
var handler = Changed;
|
||||
if (handler != null)
|
||||
{
|
||||
if (_listeners[i].TryGetTarget(out var listener))
|
||||
{
|
||||
listener.Notify(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_listeners.RemoveAt(i--);
|
||||
}
|
||||
handler(this, e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReportError(Exception exception)
|
||||
{
|
||||
_errorReporter.ReportError(exception);
|
||||
}
|
||||
|
||||
public override void ReportError(Exception exception, Project project)
|
||||
{
|
||||
_errorReporter.ReportError(exception, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
private readonly IEnumerable<ProjectSnapshotChangeTrigger> _triggers;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultProjectSnapshotManagerFactory([ImportMany] IEnumerable<ProjectSnapshotChangeTrigger> triggers)
|
||||
public DefaultProjectSnapshotManagerFactory(
|
||||
[ImportMany] IEnumerable<ProjectSnapshotChangeTrigger> triggers)
|
||||
{
|
||||
_triggers = triggers;
|
||||
}
|
||||
|
|
@ -28,7 +29,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
throw new ArgumentNullException(nameof(languageServices));
|
||||
}
|
||||
|
||||
return new DefaultProjectSnapshotManager(_triggers, languageServices.WorkspaceServices.Workspace);
|
||||
return new DefaultProjectSnapshotManager(
|
||||
languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>(),
|
||||
languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>(),
|
||||
languageServices.GetRequiredService<ProjectSnapshotWorker>(),
|
||||
_triggers,
|
||||
languageServices.WorkspaceServices.Workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal class DefaultProjectSnapshotWorker : ProjectSnapshotWorker
|
||||
{
|
||||
private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
|
||||
public DefaultProjectSnapshotWorker(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ProjectExtensibilityConfigurationFactory configurationFactory)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (configurationFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurationFactory));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_configurationFactory = configurationFactory;
|
||||
}
|
||||
|
||||
public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (update == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(update));
|
||||
}
|
||||
|
||||
// Don't block the main thread
|
||||
if (_foregroundDispatcher.IsForegroundThread)
|
||||
{
|
||||
return Task.Factory.StartNew(ProjectUpdatesCoreAsync, update, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.BackgroundScheduler);
|
||||
}
|
||||
|
||||
return ProjectUpdatesCoreAsync(update);
|
||||
}
|
||||
|
||||
private async Task ProjectUpdatesCoreAsync(object state)
|
||||
{
|
||||
var update = (ProjectSnapshotUpdateContext)state;
|
||||
|
||||
// We'll have more things to process here, but for now we're just hardcoding the configuration.
|
||||
|
||||
var configuration = await _configurationFactory.GetConfigurationAsync(update.UnderlyingProject);
|
||||
update.Configuration = configuration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Composition;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
[Shared]
|
||||
[ExportLanguageServiceFactory(typeof(ProjectSnapshotWorker), RazorLanguage.Name)]
|
||||
internal class DefaultProjectSnapshotWorkerFactory : ILanguageServiceFactory
|
||||
{
|
||||
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||
{
|
||||
return new DefaultProjectSnapshotWorker(
|
||||
languageServices.WorkspaceServices.GetRequiredService<ForegroundDispatcher>(),
|
||||
languageServices.GetRequiredService<ProjectExtensibilityConfigurationFactory>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
public override IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
|
||||
|
||||
// MVC: '2.0.0' (fallback) or MVC: '2.1.3'
|
||||
public override string DisplayName => $"MVC: {MvcAssembly.Identity.Version.ToString(3)}" + (Kind == ProjectExtensibilityConfigurationKind.Fallback? " (fallback)" : string.Empty);
|
||||
|
||||
public override ProjectExtensibilityConfigurationKind Kind { get; }
|
||||
|
||||
public override ProjectExtensibilityAssembly RazorAssembly { get; }
|
||||
|
|
@ -48,7 +51,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
}
|
||||
|
||||
// We're intentionally ignoring the 'Kind' here. That's mostly for diagnostics and doesn't influence any behavior.
|
||||
return Enumerable.SequenceEqual(Assemblies.OrderBy(a => a.Identity.Name), other.Assemblies.OrderBy(a => a.Identity.Name));
|
||||
return Enumerable.SequenceEqual(
|
||||
Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
|
||||
other.Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
|
||||
AssemblyIdentityEqualityComparer.NameAndVersion);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
|
|
@ -61,5 +67,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
public abstract IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
|
||||
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
public abstract ProjectExtensibilityConfigurationKind Kind { get; }
|
||||
|
||||
public abstract ProjectExtensibilityAssembly RazorAssembly { get; }
|
||||
|
|
@ -20,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return base.Equals(obj as ProjectExtensibilityConfiguration);
|
||||
return Equals(obj as ProjectExtensibilityConfiguration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectSnapshotListener
|
||||
{
|
||||
public abstract event EventHandler<ProjectChangeEventArgs> ProjectChanged;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
|
||||
|
|
@ -8,8 +9,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
internal abstract class ProjectSnapshotManager : ILanguageService
|
||||
{
|
||||
public abstract event EventHandler<ProjectChangeEventArgs> Changed;
|
||||
|
||||
public abstract IReadOnlyList<ProjectSnapshot> Projects { get; }
|
||||
|
||||
public abstract ProjectSnapshotListener Subscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectSnapshotManagerBase : ProjectSnapshotManager
|
||||
|
|
@ -11,10 +13,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
public abstract void ProjectChanged(Project underlyingProject);
|
||||
|
||||
public abstract void ProjectChanged(ProjectSnapshotUpdateContext update);
|
||||
public abstract void ProjectUpdated(ProjectSnapshotUpdateContext update);
|
||||
|
||||
public abstract void ProjectRemoved(Project underlyingProject);
|
||||
|
||||
public abstract void ProjectsCleared();
|
||||
|
||||
public abstract void ReportError(Exception exception);
|
||||
|
||||
public abstract void ReportError(Exception exception, Project project);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectSnapshotWorker : ILanguageService
|
||||
{
|
||||
public abstract Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal class ProjectSnapshotWorkerQueue
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly DefaultProjectSnapshotManager _projectManager;
|
||||
private readonly ProjectSnapshotWorker _projectWorker;
|
||||
|
||||
private readonly Dictionary<ProjectId, Project> _projects;
|
||||
private Timer _timer;
|
||||
|
||||
public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
if (projectWorker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectWorker));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_projectManager = projectManager;
|
||||
_projectWorker = projectWorker;
|
||||
|
||||
_projects = new Dictionary<ProjectId, Project>();
|
||||
}
|
||||
|
||||
public bool HasPendingNotifications
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_projects)
|
||||
{
|
||||
return _projects.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Used in unit tests to control the timer delay.
|
||||
public TimeSpan Delay { get; set; } = TimeSpan.FromSeconds(2);
|
||||
|
||||
#if DEBUG
|
||||
public bool IsScheduledOrRunning => _timer != null;
|
||||
|
||||
// Used in unit tests to ensure we can control when background work starts.
|
||||
public ManualResetEventSlim BlockBackgroundWorkStart { get; set; }
|
||||
|
||||
// Used in unit tests to ensure we can know when background work finishes.
|
||||
public ManualResetEventSlim NotifyBackgroundWorkFinish { get; set; }
|
||||
|
||||
// Used in unit tests to ensure we can be notified when all completes.
|
||||
public ManualResetEventSlim NotifyForegroundWorkFinish { get; set; }
|
||||
#endif
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void OnStartingBackgroundWork()
|
||||
{
|
||||
|
||||
if (BlockBackgroundWorkStart != null)
|
||||
{
|
||||
BlockBackgroundWorkStart.Wait();
|
||||
BlockBackgroundWorkStart.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void OnFinishingBackgroundWork()
|
||||
{
|
||||
if (NotifyBackgroundWorkFinish != null)
|
||||
{
|
||||
NotifyBackgroundWorkFinish.Set();
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void OnFinishingForegroundWork()
|
||||
{
|
||||
if (NotifyForegroundWorkFinish != null)
|
||||
{
|
||||
NotifyForegroundWorkFinish.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(Project project)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
lock (_projects)
|
||||
{
|
||||
// We only want to store the last 'seen' version of any given project. That way when we pick one to process
|
||||
// it's always the best version to use.
|
||||
_projects[project.Id] = project;
|
||||
|
||||
StartWorker();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StartWorker()
|
||||
{
|
||||
// Access to the timer is protected by the lock in Enqueue and in Timer_Tick
|
||||
if (_timer == null)
|
||||
{
|
||||
// Timer will fire after a fixed delay, but only once.
|
||||
_timer = new Timer(Timer_Tick, null, Delay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
private async void Timer_Tick(object state) // Yeah I know.
|
||||
{
|
||||
try
|
||||
{
|
||||
_foregroundDispatcher.AssertBackgroundThread();
|
||||
|
||||
// Timer is stopped.
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
OnStartingBackgroundWork();
|
||||
|
||||
Project[] work;
|
||||
lock (_projects)
|
||||
{
|
||||
work = _projects.Values.ToArray();
|
||||
_projects.Clear();
|
||||
}
|
||||
|
||||
var updates = new(ProjectSnapshotUpdateContext context, Exception exception)[work.Length];
|
||||
for (var i = 0; i < work.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
updates[i] = (new ProjectSnapshotUpdateContext(work[i]), null);
|
||||
await _projectWorker.ProcessUpdateAsync(updates[i].context);
|
||||
}
|
||||
catch (Exception projectException)
|
||||
{
|
||||
updates[i] = (updates[i].context, projectException);
|
||||
}
|
||||
}
|
||||
|
||||
OnFinishingBackgroundWork();
|
||||
|
||||
// We need to get back to the UI thread to update the project system.
|
||||
await Task.Factory.StartNew(PersistUpdates, updates, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
|
||||
lock (_projects)
|
||||
{
|
||||
// Resetting the timer allows another batch of work to start.
|
||||
_timer.Dispose();
|
||||
_timer = null;
|
||||
|
||||
// If more work came in while we were running start the worker again.
|
||||
if (_projects.Count > 0)
|
||||
{
|
||||
StartWorker();
|
||||
}
|
||||
}
|
||||
|
||||
OnFinishingForegroundWork();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// This is something totally unexpected, let's just send it over to the workspace.
|
||||
await Task.Factory.StartNew(() => _projectManager.ReportError(ex), CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler);
|
||||
}
|
||||
}
|
||||
|
||||
private void PersistUpdates(object state)
|
||||
{
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
var updates = ((ProjectSnapshotUpdateContext context, Exception exception)[])state;
|
||||
|
||||
for (var i = 0; i < updates.Length; i++)
|
||||
{
|
||||
var update = updates[i];
|
||||
if (update.exception == null)
|
||||
{
|
||||
_projectManager.ProjectUpdated(update.context);
|
||||
}
|
||||
else
|
||||
{
|
||||
_projectManager.ReportError(update.exception, update.context?.UnderlyingProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
foreach (var project in solution.Projects)
|
||||
{
|
||||
_projectManager.ProjectAdded(project);
|
||||
if (project.Language == LanguageNames.CSharp)
|
||||
{
|
||||
_projectManager.ProjectAdded(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,7 +45,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
underlyingProject = e.NewSolution.GetProject(e.ProjectId);
|
||||
Debug.Assert(underlyingProject != null);
|
||||
|
||||
_projectManager.ProjectAdded(underlyingProject);
|
||||
if (underlyingProject.Language == LanguageNames.CSharp)
|
||||
{
|
||||
_projectManager.ProjectAdded(underlyingProject);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal abstract class AssemblyIdentityEqualityComparer : IEqualityComparer<AssemblyIdentity>
|
||||
{
|
||||
public static readonly AssemblyIdentityEqualityComparer NameAndVersion = new NameAndVersionEqualityComparer();
|
||||
|
||||
public abstract bool Equals(AssemblyIdentity x, AssemblyIdentity y);
|
||||
|
||||
public abstract int GetHashCode(AssemblyIdentity obj);
|
||||
|
||||
private class NameAndVersionEqualityComparer : AssemblyIdentityEqualityComparer
|
||||
{
|
||||
public override bool Equals(AssemblyIdentity x, AssemblyIdentity y)
|
||||
{
|
||||
if (object.ReferenceEquals(x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (x == null ^ y == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && object.Equals(x.Version, y.Version);
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode(AssemblyIdentity obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(obj.Name, StringComparer.OrdinalIgnoreCase);
|
||||
hash.Add(obj.Version);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,17 +19,22 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
internal class DefaultTagHelperResolver : TagHelperResolver
|
||||
{
|
||||
private readonly ErrorReporter _errorReporter;
|
||||
private readonly Workspace _workspace;
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public DefaultTagHelperResolver(Workspace workspace, IServiceProvider services)
|
||||
public DefaultTagHelperResolver(ErrorReporter errorReporter, Workspace workspace)
|
||||
{
|
||||
_errorReporter = errorReporter;
|
||||
_workspace = workspace;
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<TagHelperResolutionResult> GetTagHelpersAsync(Project project)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(project));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TagHelperResolutionResult result;
|
||||
|
|
@ -67,15 +72,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var log = GetActivityLog();
|
||||
if (log != null)
|
||||
{
|
||||
var hr = log.LogEntry(
|
||||
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
|
||||
"Razor Language Services",
|
||||
$"Error discovering TagHelpers:{Environment.NewLine}{exception}");
|
||||
ErrorHandler.ThrowOnFailure(hr);
|
||||
}
|
||||
_errorReporter.ReportError(exception, project);
|
||||
|
||||
throw new RazorLanguageServiceException(
|
||||
typeof(DefaultTagHelperResolver).FullName,
|
||||
|
|
@ -121,10 +118,5 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
return serializer.Deserialize<TagHelperResolutionResult>(reader);
|
||||
}
|
||||
}
|
||||
|
||||
private IVsActivityLog GetActivityLog()
|
||||
{
|
||||
return _services.GetService(typeof(SVsActivityLog)) as IVsActivityLog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using System.ComponentModel.Composition;
|
|||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
|
|
@ -15,12 +14,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
[Import]
|
||||
public VisualStudioWorkspace Workspace { get; set; }
|
||||
|
||||
[Import]
|
||||
public SVsServiceProvider Services { get; set; }
|
||||
|
||||
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||
{
|
||||
return new DefaultTagHelperResolver(Workspace, Services);
|
||||
return new DefaultTagHelperResolver(Workspace.Services.GetRequiredService<ErrorReporter>(), Workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
private bool _isSupportedProject;
|
||||
private ProjectSnapshot _project;
|
||||
private string _projectPath;
|
||||
private ProjectSnapshotListener _subscription;
|
||||
|
||||
public override event EventHandler ContextChanged;
|
||||
|
||||
|
|
@ -59,13 +58,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
_workspace = workspace; // For now we assume that the workspace is the always default VS workspace.
|
||||
|
||||
_textViews = new List<ITextView>();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
internal override ProjectExtensibilityConfiguration Configuration => _project.Configuration;
|
||||
|
||||
public override bool IsSupportedProject => _isSupportedProject;
|
||||
|
||||
public override Project Project => _project?.UnderlyingProject;
|
||||
public override Project Project => _workspace.CurrentSolution.GetProject(_project.UnderlyingProject.Id);
|
||||
|
||||
public override ITextBuffer TextBuffer => _textBuffer;
|
||||
|
||||
|
|
@ -75,7 +74,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
private void Initialize()
|
||||
public void Subscribe()
|
||||
{
|
||||
// Fundamentally we have a Razor half of the world as as soon as the document is open - and then later
|
||||
// the C# half of the world will be initialized. This code is in general pretty tolerant of
|
||||
|
|
@ -101,17 +100,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
return;
|
||||
}
|
||||
|
||||
var project = _projectManager.GetProjectWithFilePath(projectPath);
|
||||
|
||||
var subscription = _projectManager.Subscribe();
|
||||
subscription.ProjectChanged += Subscription_ProjectStateChanged;
|
||||
|
||||
_isSupportedProject = isSupportedProject;
|
||||
_projectPath = projectPath;
|
||||
_project = project;
|
||||
_subscription = subscription;
|
||||
_project = _projectManager.GetProjectWithFilePath(projectPath);
|
||||
_projectManager.Changed += ProjectManager_Changed;
|
||||
|
||||
OnContextChanged(_project);
|
||||
}
|
||||
|
||||
|
||||
public void Unsubscribe()
|
||||
{
|
||||
_projectManager.Changed -= ProjectManager_Changed;
|
||||
}
|
||||
|
||||
private void OnContextChanged(ProjectSnapshot project)
|
||||
{
|
||||
_project = project;
|
||||
|
|
@ -123,7 +124,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
}
|
||||
}
|
||||
|
||||
private void Subscription_ProjectStateChanged(object sender, ProjectChangeEventArgs e)
|
||||
private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
|
||||
{
|
||||
if (_projectPath != null &&
|
||||
string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -28,15 +28,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
[ImportingConstructor]
|
||||
public DefaultVisualStudioDocumentTrackerFactory(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
TextBufferProjectService projectService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
|
|
@ -46,11 +40,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
|
||||
_projectService = projectService;
|
||||
_workspace = workspace;
|
||||
|
||||
_foregroundDispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
||||
_projectManager = workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<ProjectSnapshotManager>();
|
||||
}
|
||||
|
||||
|
|
@ -151,6 +145,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
if (!tracker.TextViewsInternal.Contains(textView))
|
||||
{
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
if (tracker.TextViewsInternal.Count == 1)
|
||||
{
|
||||
tracker.Subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -182,6 +180,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
if (textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out tracker))
|
||||
{
|
||||
tracker.TextViewsInternal.Remove(textView);
|
||||
if (tracker.TextViewsInternal.Count == 0)
|
||||
{
|
||||
tracker.Unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
|
|
@ -13,6 +14,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
public abstract event EventHandler ContextChanged;
|
||||
|
||||
internal abstract ProjectExtensibilityConfiguration Configuration { get; }
|
||||
|
||||
public abstract bool IsSupportedProject { get; }
|
||||
|
||||
public abstract Project Project { get; }
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
|
|
@ -17,9 +16,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
[ImportingConstructor]
|
||||
public LegacyTagHelperResolver(
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace,
|
||||
[Import(typeof(SVsServiceProvider))] IServiceProvider services) :
|
||||
base(workspace, services)
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
: base(workspace.Services.GetRequiredService<ErrorReporter>(), workspace)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
internal class VisualStudioErrorReporter : ErrorReporter
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
public VisualStudioErrorReporter(IServiceProvider services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public override void ReportError(Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var activityLog = GetActivityLog();
|
||||
if (activityLog != null)
|
||||
{
|
||||
var hr = activityLog.LogEntry(
|
||||
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
|
||||
"Razor Language Services",
|
||||
$"Error encountered:{Environment.NewLine}{exception}");
|
||||
ErrorHandler.ThrowOnFailure(hr);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReportError(Exception exception, Project project)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var activityLog = GetActivityLog();
|
||||
if (activityLog != null)
|
||||
{
|
||||
var hr = activityLog.LogEntry(
|
||||
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
|
||||
"Razor Language Services",
|
||||
$"Error encountered from project '{project?.Name}':{Environment.NewLine}{exception}");
|
||||
ErrorHandler.ThrowOnFailure(hr);
|
||||
}
|
||||
}
|
||||
|
||||
private IVsActivityLog GetActivityLog()
|
||||
{
|
||||
return _services.GetService(typeof(SVsActivityLog)) as IVsActivityLog;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
|
||||
// Adding some computed state
|
||||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.Reset();
|
||||
|
||||
project = project.WithAssemblyName("Test1"); // Simulate a project change
|
||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
|
||||
// Assert
|
||||
var snapshot = ProjectManager.GetSnapshot(project.Id);
|
||||
|
|
@ -95,7 +95,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ProjectManager.Reset();
|
||||
|
||||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.Reset();
|
||||
|
||||
project = project.WithAssemblyName("Test1"); // Simulate a project change
|
||||
|
|
@ -103,7 +103,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ProjectManager.Reset();
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
|
||||
// Assert
|
||||
var snapshot = ProjectManager.GetSnapshot(project.Id);
|
||||
|
|
@ -132,7 +132,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ProjectManager.Reset();
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectChanged(update);
|
||||
ProjectManager.ProjectUpdated(update);
|
||||
|
||||
// Assert
|
||||
var snapshot = ProjectManager.GetSnapshot(project.Id);
|
||||
|
|
@ -152,7 +152,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ProjectManager.Reset();
|
||||
|
||||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
|
||||
|
||||
project = project.WithAssemblyName("Test1"); // Simulate a project change
|
||||
ProjectManager.ProjectChanged(project);
|
||||
|
|
@ -166,7 +166,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ProjectManager.Reset();
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectChanged(update); // Still dirty because the project changed while computing the update
|
||||
ProjectManager.ProjectUpdated(update); // Still dirty because the project changed while computing the update
|
||||
|
||||
// Assert
|
||||
var snapshot = ProjectManager.GetSnapshot(project.Id);
|
||||
|
|
@ -200,7 +200,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
|
||||
|
||||
// Act
|
||||
ProjectManager.ProjectChanged(new ProjectSnapshotUpdateContext(project));
|
||||
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project));
|
||||
|
||||
// Assert
|
||||
Assert.Empty(ProjectManager.Projects);
|
||||
|
|
@ -266,7 +266,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
|
||||
{
|
||||
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
|
||||
: base(triggers, workspace)
|
||||
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), triggers, workspace)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
ListenersNotified = true;
|
||||
}
|
||||
|
||||
protected override void NotifyBackgroundWorker()
|
||||
protected override void NotifyBackgroundWorker(Project project)
|
||||
{
|
||||
WorkerStarted = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
|
|
@ -45,7 +49,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// Arrange
|
||||
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
|
||||
var projectManager = new DefaultProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
|
||||
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
|
||||
|
|
@ -69,7 +73,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// Arrange
|
||||
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
|
||||
var projectManager = new DefaultProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
|
||||
// Initialize with a project. This will get removed.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
|
||||
|
|
@ -94,7 +98,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// Arrange
|
||||
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
|
||||
var projectManager = new DefaultProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
|
||||
// Initialize with some projects.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
|
|
@ -122,7 +126,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// Arrange
|
||||
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
|
||||
var projectManager = new DefaultProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
|
||||
// Initialize with some projects project.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
|
|
@ -145,7 +149,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
{
|
||||
// Arrange
|
||||
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
|
||||
var projectManager = new DefaultProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
|
||||
|
||||
var solution = SolutionWithOneProject;
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
|
||||
|
|
@ -158,5 +162,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
|||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
|
||||
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
|
||||
{
|
||||
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
|
||||
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), new TestProjectSnapshotWorker(), triggers, workspace)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void NotifyBackgroundWorker(Project project)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class TestProjectSnapshotWorker : ProjectSnapshotWorker
|
||||
{
|
||||
public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
private static IReadOnlyList<ProjectSnapshot> Projects = new List<ProjectSnapshot>();
|
||||
|
||||
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(
|
||||
p => p.Projects == Projects &&
|
||||
p.Subscribe() == Mock.Of<ProjectSnapshotListener>());
|
||||
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(p => p.Projects == Projects);
|
||||
|
||||
private TextBufferProjectService ProjectService { get; } = Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
public class DefaultProjectSnapshotWorkerTest : ForegroundDispatcherTestBase
|
||||
{
|
||||
public DefaultProjectSnapshotWorkerTest()
|
||||
{
|
||||
Project = new AdhocWorkspace().AddProject("Test1", LanguageNames.CSharp);
|
||||
|
||||
CompletionSource = new TaskCompletionSource<ProjectExtensibilityConfiguration>();
|
||||
ConfigurationFactory = Mock.Of<ProjectExtensibilityConfigurationFactory>(f => f.GetConfigurationAsync(It.IsAny<Project>(), default(CancellationToken)) == CompletionSource.Task);
|
||||
}
|
||||
|
||||
private Project Project { get; }
|
||||
|
||||
private ProjectExtensibilityConfigurationFactory ConfigurationFactory { get; }
|
||||
|
||||
private TaskCompletionSource<ProjectExtensibilityConfiguration> CompletionSource { get; }
|
||||
|
||||
[ForegroundFact]
|
||||
public async Task ProcessUpdateAsync_DoesntBlockForegroundThread()
|
||||
{
|
||||
// Arrange
|
||||
var worker = new DefaultProjectSnapshotWorker(Dispatcher, ConfigurationFactory);
|
||||
|
||||
var context = new ProjectSnapshotUpdateContext(Project);
|
||||
|
||||
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
|
||||
|
||||
// Act 1 -- We want to verify that this doesn't block the main thread
|
||||
var task = worker.ProcessUpdateAsync(context);
|
||||
|
||||
// Assert 1
|
||||
//
|
||||
// We haven't let the background task proceed yet, so this is still null.
|
||||
Assert.Null(context.Configuration);
|
||||
|
||||
// Act 2 - Ok let's go
|
||||
CompletionSource.SetResult(configuration);
|
||||
await task;
|
||||
|
||||
// Assert 2
|
||||
Assert.Same(configuration, context.Configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
// These tests are really integration tests. There isn't a good way to unit test this functionality since
|
||||
// the only thing in here is threading.
|
||||
public class ProjectSnapshotWorkerQueueTest : ForegroundDispatcherTestBase
|
||||
{
|
||||
public ProjectSnapshotWorkerQueueTest()
|
||||
{
|
||||
Workspace = new AdhocWorkspace();
|
||||
|
||||
Project1 = Workspace.CurrentSolution.AddProject("Test1", "Test1", LanguageNames.CSharp);
|
||||
Project2 = Workspace.CurrentSolution.AddProject("Test2", "Test2", LanguageNames.CSharp);
|
||||
}
|
||||
|
||||
public Project Project1 { get; }
|
||||
|
||||
public Project Project2 { get; }
|
||||
|
||||
public Workspace Workspace { get; }
|
||||
|
||||
[ForegroundFact]
|
||||
public async Task Queue_ProcessesNotifications_AndGoesBackToSleep()
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
|
||||
var projectWorker = new TestProjectSnapshotWorker();
|
||||
|
||||
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
|
||||
{
|
||||
Delay = TimeSpan.FromMilliseconds(1),
|
||||
BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false),
|
||||
NotifyBackgroundWorkFinish = new ManualResetEventSlim(initialState: false),
|
||||
NotifyForegroundWorkFinish = new ManualResetEventSlim(initialState: false),
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
queue.Enqueue(Project1);
|
||||
|
||||
Assert.True(queue.IsScheduledOrRunning);
|
||||
Assert.True(queue.HasPendingNotifications);
|
||||
|
||||
// Allow the background work to proceed.
|
||||
queue.BlockBackgroundWorkStart.Set();
|
||||
|
||||
// Get off the foreground thread and allow the updates to flow through.
|
||||
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
|
||||
|
||||
Assert.False(queue.IsScheduledOrRunning);
|
||||
Assert.False(queue.HasPendingNotifications);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public async Task Queue_ProcessesNotifications_AndRestarts()
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
|
||||
var projectWorker = new TestProjectSnapshotWorker();
|
||||
|
||||
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
|
||||
{
|
||||
Delay = TimeSpan.FromMilliseconds(1),
|
||||
BlockBackgroundWorkStart = new ManualResetEventSlim(initialState: false),
|
||||
NotifyBackgroundWorkFinish = new ManualResetEventSlim(initialState: false),
|
||||
NotifyForegroundWorkFinish = new ManualResetEventSlim(initialState: false),
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
queue.Enqueue(Project1);
|
||||
|
||||
Assert.True(queue.IsScheduledOrRunning);
|
||||
Assert.True(queue.HasPendingNotifications);
|
||||
|
||||
// Allow the background work to proceed.
|
||||
queue.BlockBackgroundWorkStart.Set();
|
||||
|
||||
queue.NotifyBackgroundWorkFinish.Wait(); // Block the foreground thread so we can queue another notification.
|
||||
|
||||
Assert.True(queue.IsScheduledOrRunning);
|
||||
Assert.False(queue.HasPendingNotifications);
|
||||
|
||||
queue.Enqueue(Project2);
|
||||
|
||||
Assert.True(queue.HasPendingNotifications); // Now we should see the worker restart when it finishes.
|
||||
|
||||
// Get off the foreground thread and allow the updates to flow through.
|
||||
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
|
||||
|
||||
queue.NotifyBackgroundWorkFinish.Reset();
|
||||
queue.NotifyForegroundWorkFinish.Reset();
|
||||
|
||||
// It should start running again right away.
|
||||
Assert.True(queue.IsScheduledOrRunning);
|
||||
Assert.True(queue.HasPendingNotifications);
|
||||
|
||||
// Allow the background work to proceed.
|
||||
queue.BlockBackgroundWorkStart.Set();
|
||||
|
||||
// Get off the foreground thread and allow the updates to flow through.
|
||||
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
|
||||
|
||||
Assert.False(queue.IsScheduledOrRunning);
|
||||
Assert.False(queue.HasPendingNotifications);
|
||||
}
|
||||
|
||||
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
|
||||
{
|
||||
public TestProjectSnapshotManager(ForegroundDispatcher foregroundDispatcher, Workspace workspace)
|
||||
: base(foregroundDispatcher, Mock.Of<ErrorReporter>(), new TestProjectSnapshotWorker(), Enumerable.Empty<ProjectSnapshotChangeTrigger>(), workspace)
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultProjectSnapshot GetSnapshot(ProjectId id)
|
||||
{
|
||||
return Projects.Cast<DefaultProjectSnapshot>().FirstOrDefault(s => s.UnderlyingProject.Id == id);
|
||||
}
|
||||
|
||||
protected override void NotifyListeners(ProjectChangeEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void NotifyBackgroundWorker(Project project)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class TestProjectSnapshotWorker : ProjectSnapshotWorker
|
||||
{
|
||||
public TestProjectSnapshotWorker()
|
||||
{
|
||||
}
|
||||
|
||||
public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
||||
|
|
@ -25,6 +23,8 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
_documentTracker = documentTracker;
|
||||
}
|
||||
|
||||
public string Configuration => _documentTracker.Configuration?.DisplayName;
|
||||
|
||||
public bool IsSupportedDocument => _documentTracker.IsSupportedProject;
|
||||
|
||||
public Project Project
|
||||
|
|
@ -43,18 +43,6 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
public ProjectId ProjectId => _documentTracker.Project?.Id;
|
||||
|
||||
public Workspace Workspace => _documentTracker.Workspace;
|
||||
|
||||
public HostLanguageServices RazorLanguageServices => Workspace?.Services.GetLanguageServices(RazorLanguage.Name);
|
||||
|
||||
public TagHelperResolver TagHelperResolver => RazorLanguageServices?.GetRequiredService<TagHelperResolver>();
|
||||
|
||||
public RazorSyntaxFactsService RazorSyntaxFactsService => RazorLanguageServices?.GetRequiredService<RazorSyntaxFactsService>();
|
||||
|
||||
public RazorTemplateEngineFactoryService RazorTemplateEngineFactoryService => RazorLanguageServices?.GetRequiredService<RazorTemplateEngineFactoryService>();
|
||||
|
||||
public TagHelperCompletionService TagHelperCompletionService => RazorLanguageServices?.GetRequiredService<TagHelperCompletionService>();
|
||||
|
||||
public TagHelperFactsService TagHelperFactsService => RazorLanguageServices?.GetRequiredService<TagHelperFactsService>();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@
|
|||
<Label Grid.Column="1" Grid.Row="0" Content="{Binding Project.Name}"/>
|
||||
<Label Grid.Column="0" Grid.Row="1" Content="Is Supported"/>
|
||||
<Label Grid.Column="1" Grid.Row="1" Content="{Binding IsSupportedDocument}"/>
|
||||
<Label Grid.Column="0" Grid.Row="2" Content="Workspace"/>
|
||||
<Label Grid.Column="1" Grid.Row="2" Content="{Binding Workspace}"/>
|
||||
<Label Grid.Column="0" Grid.Row="2" Content="Configuration"/>
|
||||
<Label Grid.Column="1" Grid.Row="2" Content="{Binding Configuration}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
|||
Loading…
Reference in New Issue