Implmement a project system for Razor
This commit is contained in:
parent
4b68a48f1d
commit
5cb11b9bf4
|
|
@ -0,0 +1,22 @@
|
|||
// 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 DefaultProjectSnapshot : ProjectSnapshot
|
||||
{
|
||||
public DefaultProjectSnapshot(Project underlyingProject)
|
||||
{
|
||||
if (underlyingProject == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(underlyingProject));
|
||||
}
|
||||
|
||||
UnderlyingProject = underlyingProject;
|
||||
}
|
||||
|
||||
public override Project UnderlyingProject { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal class DefaultProjectSnapshotManager : ProjectSnapshotManager
|
||||
{
|
||||
private readonly Workspace _workspace;
|
||||
private readonly Dictionary<ProjectId, ProjectSnapshot> _projects;
|
||||
private readonly List<WeakReference<DefaultProjectSnapshotListener>> _listeners;
|
||||
|
||||
public DefaultProjectSnapshotManager(Workspace workspace)
|
||||
{
|
||||
_workspace = workspace;
|
||||
|
||||
_projects = new Dictionary<ProjectId, ProjectSnapshot>();
|
||||
_listeners = new List<WeakReference<DefaultProjectSnapshotListener>>();
|
||||
|
||||
// Attaching the event handler inside before initialization prevents re-entrancy without
|
||||
// losing any notifications.
|
||||
_workspace.WorkspaceChanged += Workspace_WorkspaceChanged;
|
||||
InitializeSolution(_workspace.CurrentSolution);
|
||||
}
|
||||
|
||||
public override IReadOnlyList<ProjectSnapshot> Projects => _projects.Values.ToArray();
|
||||
|
||||
public override ProjectSnapshot FindProject(string projectPath)
|
||||
{
|
||||
if (projectPath == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectPath));
|
||||
}
|
||||
|
||||
foreach (var project in _projects.Values)
|
||||
{
|
||||
if (string.Equals(projectPath, project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return project;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override ProjectSnapshotListener Subscribe()
|
||||
{
|
||||
var subscription = new DefaultProjectSnapshotListener();
|
||||
_listeners.Add(new WeakReference<DefaultProjectSnapshotListener>(subscription));
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private void InitializeSolution(Solution solution)
|
||||
{
|
||||
Debug.Assert(solution != null);
|
||||
|
||||
foreach (var kvp in _projects.ToArray())
|
||||
{
|
||||
_projects.Remove(kvp.Key);
|
||||
NotifyListeners(new ProjectChangeEventArgs(kvp.Value, ProjectChangeKind.Removed));
|
||||
}
|
||||
|
||||
foreach (var project in solution.Projects)
|
||||
{
|
||||
var projectState = new DefaultProjectSnapshot(project);
|
||||
_projects[project.Id] = projectState;
|
||||
|
||||
NotifyListeners(new ProjectChangeEventArgs(projectState, ProjectChangeKind.Added));
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyListeners(ProjectChangeEventArgs e)
|
||||
{
|
||||
for (var i = 0; i < _listeners.Count; i++)
|
||||
{
|
||||
if (_listeners[i].TryGetTarget(out var listener))
|
||||
{
|
||||
listener.Notify(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_listeners.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
|
||||
{
|
||||
Project underlyingProject;
|
||||
ProjectSnapshot snapshot;
|
||||
switch (e.Kind)
|
||||
{
|
||||
case WorkspaceChangeKind.ProjectAdded:
|
||||
{
|
||||
underlyingProject = e.NewSolution.GetProject(e.ProjectId);
|
||||
Debug.Assert(underlyingProject != null);
|
||||
|
||||
snapshot = new DefaultProjectSnapshot(underlyingProject);
|
||||
_projects[e.ProjectId] = snapshot;
|
||||
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Added));
|
||||
break;
|
||||
}
|
||||
|
||||
case WorkspaceChangeKind.ProjectChanged:
|
||||
case WorkspaceChangeKind.ProjectReloaded:
|
||||
{
|
||||
underlyingProject = e.NewSolution.GetProject(e.ProjectId);
|
||||
Debug.Assert(underlyingProject != null);
|
||||
|
||||
snapshot = new DefaultProjectSnapshot(underlyingProject);
|
||||
_projects[e.ProjectId] = snapshot;
|
||||
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
|
||||
break;
|
||||
}
|
||||
|
||||
case WorkspaceChangeKind.ProjectRemoved:
|
||||
{
|
||||
// We're being extra defensive here to avoid crashes.
|
||||
if (_projects.TryGetValue(e.ProjectId, out snapshot))
|
||||
{
|
||||
_projects.Remove(e.ProjectId);
|
||||
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Removed));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WorkspaceChangeKind.SolutionAdded:
|
||||
case WorkspaceChangeKind.SolutionChanged:
|
||||
case WorkspaceChangeKind.SolutionCleared:
|
||||
case WorkspaceChangeKind.SolutionReloaded:
|
||||
case WorkspaceChangeKind.SolutionRemoved:
|
||||
InitializeSolution(e.NewSolution);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.Composition;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
[Shared]
|
||||
[ExportLanguageServiceFactory(typeof(ProjectSnapshotManager), RazorLanguage.Name)]
|
||||
internal class DefaultProjectSnapshotManagerFactory : ILanguageServiceFactory
|
||||
{
|
||||
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||
{
|
||||
if (languageServices == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(languageServices));
|
||||
}
|
||||
|
||||
return new DefaultProjectSnapshotManager(languageServices.WorkspaceServices.Workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.ProjectSystem
|
||||
{
|
||||
internal class ProjectChangeEventArgs : EventArgs
|
||||
{
|
||||
public ProjectChangeEventArgs(ProjectSnapshot project, ProjectChangeKind kind)
|
||||
{
|
||||
Project = project;
|
||||
Kind = kind;
|
||||
}
|
||||
|
||||
public ProjectSnapshot Project { get; }
|
||||
|
||||
public ProjectChangeKind Kind { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal enum ProjectChangeKind
|
||||
{
|
||||
Added,
|
||||
Removed,
|
||||
Changed,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectSnapshot
|
||||
{
|
||||
public abstract Project UnderlyingProject { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Microsoft.CodeAnalysis.Host;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
internal abstract class ProjectSnapshotManager : ILanguageService
|
||||
{
|
||||
public abstract IReadOnlyList<ProjectSnapshot> Projects { get; }
|
||||
|
||||
public abstract ProjectSnapshot FindProject(string projectPath);
|
||||
|
||||
public abstract ProjectSnapshotListener Subscribe();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,5 +6,6 @@ using System.Runtime.CompilerServices;
|
|||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LanguageServices.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.RazorExtension, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||
|
|
|
|||
|
|
@ -61,6 +61,17 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
return hierarchy;
|
||||
}
|
||||
|
||||
public override string GetProjectPath(IVsHierarchy hierarchy)
|
||||
{
|
||||
if (hierarchy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hierarchy));
|
||||
}
|
||||
|
||||
ErrorHandler.ThrowOnFailure(((IVsProject)hierarchy).GetMkDocument((uint)VSConstants.VSITEMID.Root, out var path), VSConstants.E_NOTIMPL);
|
||||
return path;
|
||||
}
|
||||
|
||||
public override bool IsSupportedProject(IVsHierarchy hierarchy)
|
||||
{
|
||||
if (hierarchy == null)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
|
@ -13,17 +14,30 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
internal class DefaultVisualStudioDocumentTracker : VisualStudioDocumentTracker
|
||||
{
|
||||
private readonly ProjectSnapshotManager _projectManager;
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly ITextBuffer _textBuffer;
|
||||
private readonly List<ITextView> _textViews;
|
||||
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
private bool _isSupportedProject;
|
||||
private Workspace _workspace;
|
||||
private ProjectSnapshot _project;
|
||||
private string _projectPath;
|
||||
private ProjectSnapshotListener _subscription;
|
||||
|
||||
public override event EventHandler ContextChanged;
|
||||
|
||||
public DefaultVisualStudioDocumentTracker(TextBufferProjectService projectService, Workspace workspace, ITextBuffer textBuffer)
|
||||
public DefaultVisualStudioDocumentTracker(
|
||||
ProjectSnapshotManager projectManager,
|
||||
TextBufferProjectService projectService,
|
||||
Workspace workspace,
|
||||
ITextBuffer textBuffer)
|
||||
{
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
|
|
@ -39,18 +53,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
_projectManager = projectManager;
|
||||
_projectService = projectService;
|
||||
_textBuffer = textBuffer;
|
||||
_workspace = workspace;
|
||||
_workspace = workspace; // For now we assume that the workspace is the always default VS workspace.
|
||||
|
||||
_textViews = new List<ITextView>();
|
||||
|
||||
Update();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public override bool IsSupportedProject => _isSupportedProject;
|
||||
|
||||
public override ProjectId ProjectId => null;
|
||||
public override Project Project => _project?.UnderlyingProject;
|
||||
|
||||
public override ITextBuffer TextBuffer => _textBuffer;
|
||||
|
||||
|
|
@ -60,49 +75,61 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
private bool Update()
|
||||
private void Initialize()
|
||||
{
|
||||
// Update is called when the state of any of our surrounding systems changes. Here we want to examine the
|
||||
// state of the world and then update properties as necessary.
|
||||
//
|
||||
// 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
|
||||
// unexpected /impossible states.
|
||||
//
|
||||
// We also want to successfully shut down when the buffer is renamed to something other .cshtml.
|
||||
IVsHierarchy project = null;
|
||||
// We also want to successfully shut down if the buffer is something other than .cshtml.
|
||||
IVsHierarchy hierarchy = null;
|
||||
string projectPath = null;
|
||||
var isSupportedProject = false;
|
||||
|
||||
if (_textBuffer.ContentType.IsOfType(RazorLanguage.ContentType) &&
|
||||
(project = _projectService.GetHierarchy(_textBuffer)) != null)
|
||||
{
|
||||
|
||||
// We expect the document to have a hierarchy even if it's not a real 'project'.
|
||||
// However the hierarchy can be null when the document is in the process of closing.
|
||||
isSupportedProject = _projectService.IsSupportedProject(project);
|
||||
}
|
||||
|
||||
// For now we temporarily assume that the workspace is the default VS workspace.
|
||||
var workspace = _workspace;
|
||||
|
||||
var changed = false;
|
||||
changed |= isSupportedProject == _isSupportedProject;
|
||||
changed |= workspace == _workspace;
|
||||
|
||||
if (changed)
|
||||
(hierarchy = _projectService.GetHierarchy(_textBuffer)) != null)
|
||||
{
|
||||
_isSupportedProject = isSupportedProject;
|
||||
_workspace = workspace;
|
||||
projectPath = _projectService.GetProjectPath(hierarchy);
|
||||
isSupportedProject = _projectService.IsSupportedProject(hierarchy);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
if (!isSupportedProject || projectPath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnContextChanged()
|
||||
var project = _projectManager.FindProject(projectPath);
|
||||
|
||||
var subscription = _projectManager.Subscribe();
|
||||
subscription.ProjectChanged += Subscription_ProjectStateChanged;
|
||||
|
||||
_isSupportedProject = isSupportedProject;
|
||||
_projectPath = projectPath;
|
||||
_project = project;
|
||||
_subscription = subscription;
|
||||
}
|
||||
|
||||
private void OnContextChanged(ProjectSnapshot project)
|
||||
{
|
||||
_project = project;
|
||||
|
||||
var handler = ContextChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void Subscription_ProjectStateChanged(object sender, ProjectChangeEventArgs e)
|
||||
{
|
||||
if (_projectPath != null &&
|
||||
string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
OnContextChanged(e.Project);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ using System.Diagnostics;
|
|||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
|
|
@ -21,6 +21,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
[Export(typeof(VisualStudioDocumentTrackerFactory))]
|
||||
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener
|
||||
{
|
||||
private readonly ProjectSnapshotManager _projectManager;
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
|
|
@ -41,6 +42,34 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
_projectService = projectService;
|
||||
_workspace = workspace;
|
||||
|
||||
_projectManager = workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<ProjectSnapshotManager>();
|
||||
}
|
||||
|
||||
// This is only for testing. We want to avoid using the actual Roslyn GetService methods in unit tests.
|
||||
internal DefaultVisualStudioDocumentTrackerFactory(
|
||||
ProjectSnapshotManager projectManager,
|
||||
TextBufferProjectService projectService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_projectManager = projectManager;
|
||||
_projectService = projectService;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
public Workspace Workspace => _workspace;
|
||||
|
|
@ -96,7 +125,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
DefaultVisualStudioDocumentTracker tracker;
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out tracker))
|
||||
{
|
||||
tracker = new DefaultVisualStudioDocumentTracker(_projectService, _workspace, textBuffer);
|
||||
tracker = new DefaultVisualStudioDocumentTracker(_projectManager, _projectService, _workspace, textBuffer);
|
||||
textBuffer.Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public abstract IVsHierarchy GetHierarchy(ITextBuffer textBuffer);
|
||||
|
||||
public abstract bool IsSupportedProject(IVsHierarchy hierarchy);
|
||||
|
||||
public abstract string GetProjectPath(IVsHierarchy hierarchy);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
public abstract bool IsSupportedProject { get; }
|
||||
|
||||
public abstract ProjectId ProjectId { get; }
|
||||
public abstract Project Project { get; }
|
||||
|
||||
public abstract Workspace Workspace { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
<PackageReference Include="xunit" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
// 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.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
|
||||
{
|
||||
public class DefaultProjectStateManagerTest
|
||||
{
|
||||
public DefaultProjectStateManagerTest()
|
||||
{
|
||||
Workspace = new AdhocWorkspace();
|
||||
EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
|
||||
|
||||
ProjectNumberOne = Workspace.CurrentSolution.AddProject("One", "One", LanguageNames.CSharp);
|
||||
ProjectNumberTwo = ProjectNumberOne.Solution.AddProject("Two", "Two", LanguageNames.CSharp);
|
||||
SolutionWithTwoProjects = ProjectNumberTwo.Solution;
|
||||
|
||||
ProjectNumberThree = EmptySolution.GetIsolatedSolution().AddProject("Three", "Three", LanguageNames.CSharp);
|
||||
SolutionWithOneProject = ProjectNumberThree.Solution;
|
||||
}
|
||||
|
||||
private Solution EmptySolution { get; }
|
||||
|
||||
private Solution SolutionWithOneProject { get; }
|
||||
|
||||
private Solution SolutionWithTwoProjects { get; }
|
||||
|
||||
private Project ProjectNumberOne { get; }
|
||||
|
||||
private Project ProjectNumberTwo { get; }
|
||||
|
||||
private Project ProjectNumberThree { get; }
|
||||
|
||||
private Workspace Workspace { get; }
|
||||
|
||||
[Theory]
|
||||
[InlineData(WorkspaceChangeKind.SolutionAdded)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionChanged)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionCleared)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionReloaded)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionRemoved)]
|
||||
public void WorkspaceChanged_SolutionEvents_AddsProjectsInSolution(WorkspaceChangeKind kind)
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new DefaultProjectSnapshotManager(Workspace);
|
||||
|
||||
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
|
||||
// Act
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
|
||||
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WorkspaceChangeKind.SolutionAdded)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionChanged)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionCleared)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionReloaded)]
|
||||
[InlineData(WorkspaceChangeKind.SolutionRemoved)]
|
||||
public void WorkspaceChanged_SolutionEvents_ClearsExistingProjects_AddsProjectsInSolution(WorkspaceChangeKind kind)
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new DefaultProjectSnapshotManager(Workspace);
|
||||
|
||||
// Initialize with a project. This will get removed.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
|
||||
// Act
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
|
||||
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(WorkspaceChangeKind.ProjectChanged)]
|
||||
[InlineData(WorkspaceChangeKind.ProjectReloaded)]
|
||||
public void WorkspaceChanged_ProjectChangeEvents_UpdatesProject(WorkspaceChangeKind kind)
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new DefaultProjectSnapshotManager(Workspace);
|
||||
|
||||
// Initialize with some projects.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
var solution = SolutionWithTwoProjects.WithProjectAssemblyName(ProjectNumberOne.Id, "Changed");
|
||||
e = new WorkspaceChangeEventArgs(kind, oldSolution: SolutionWithTwoProjects, newSolution: solution, projectId: ProjectNumberOne.Id);
|
||||
|
||||
// Act
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p =>
|
||||
{
|
||||
Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id);
|
||||
Assert.Equal("Changed", p.UnderlyingProject.AssemblyName);
|
||||
},
|
||||
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkspaceChanged_ProjectRemovedEvent_RemovesProject()
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new DefaultProjectSnapshotManager(Workspace);
|
||||
|
||||
// Initialize with some projects project.
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
var solution = SolutionWithTwoProjects.RemoveProject(ProjectNumberOne.Id);
|
||||
e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectRemoved, oldSolution: SolutionWithTwoProjects, newSolution: solution, projectId: ProjectNumberOne.Id);
|
||||
|
||||
// Act
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WorkspaceChanged_ProjectAddedEvent_AddsProject()
|
||||
{
|
||||
// Arrange
|
||||
var projectManager = new DefaultProjectSnapshotManager(Workspace);
|
||||
|
||||
var solution = SolutionWithOneProject;
|
||||
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
|
||||
|
||||
// Act
|
||||
projectManager.Workspace_WorkspaceChanged(Workspace, e);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
|
||||
p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.ObjectModel;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
|
@ -17,9 +18,13 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
public class DefaultVisualStudioDocumentTrackerFactoryTest
|
||||
{
|
||||
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(
|
||||
p => p.FindProject(It.IsAny<string>()) == Mock.Of<ProjectSnapshot>() &&
|
||||
p.Subscribe() == Mock.Of<ProjectSnapshotListener>());
|
||||
|
||||
private TextBufferProjectService ProjectService { get; } = Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
s.IsSupportedProject(It.IsAny<IVsHierarchy>()) == true);
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
s.IsSupportedProject(It.IsAny<IVsHierarchy>()) == true);
|
||||
|
||||
private Workspace Workspace { get; } = new AdhocWorkspace();
|
||||
|
||||
|
|
@ -31,7 +36,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
|
|
@ -51,7 +56,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
|
|
@ -103,7 +108,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
|
|
@ -113,7 +118,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
};
|
||||
|
||||
// Preload the buffer's properties with a tracker, so it's like we've already tracked this one.
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[0]);
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
|
|
@ -129,7 +134,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView1 = Mock.Of<IWpfTextView>();
|
||||
var textView2 = Mock.Of<IWpfTextView>();
|
||||
|
|
@ -140,7 +145,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
};
|
||||
|
||||
// Preload the buffer's properties with a tracker, so it's like we've already tracked this one.
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[0]);
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
|
|
@ -156,7 +161,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView1 = Mock.Of<IWpfTextView>();
|
||||
var textView2 = Mock.Of<IWpfTextView>();
|
||||
|
|
@ -168,12 +173,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
};
|
||||
|
||||
// Preload the buffer's properties with a tracker, so it's like we've already tracked this one.
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[0]);
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
tracker.TextViewsInternal.Add(textView2);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[1]);
|
||||
tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, ProjectService, Workspace, buffers[1]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
tracker.TextViewsInternal.Add(textView2);
|
||||
buffers[1].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
|
@ -193,7 +198,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
|
|
@ -213,7 +218,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void GetTracker_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
|
|
@ -225,7 +230,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
var textView = Mock.Of<IWpfTextView>(v => v.BufferGraph == bufferGraph);
|
||||
|
||||
// Preload the buffer's properties with a tracker, so it's like we've already tracked this one.
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[0]);
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
|
|
@ -240,7 +245,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public void GetTracker_WithoutRazorBuffer_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectManager, ProjectService, Workspace);
|
||||
|
||||
var buffers = new Collection<ITextBuffer>();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="xunit.analyzers" />
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
}
|
||||
}
|
||||
|
||||
public ProjectId ProjectId => _documentTracker.ProjectId;
|
||||
public ProjectId ProjectId => _documentTracker.Project?.Id;
|
||||
|
||||
public Workspace Workspace => _documentTracker.Workspace;
|
||||
|
||||
|
|
@ -55,7 +55,6 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
public TagHelperCompletionService TagHelperCompletionService => RazorLanguageServices?.GetRequiredService<TagHelperCompletionService>();
|
||||
|
||||
public TagHelperFactsService TagHelperFactsService => RazorLanguageServices?.GetRequiredService<TagHelperFactsService>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue