Fixes #1632 - track textviews in the document tracker
I've stripped out some of the dead code and complexity from the document tracker in an attempt to simplify it. I will bring this back as part of the multi-targeting work.
This commit is contained in:
parent
8b4fc0cef6
commit
d87e0f7fbd
|
|
@ -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;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
public abstract class RazorDocumentTracker
|
||||
{
|
||||
public abstract event EventHandler ContextChanged;
|
||||
|
||||
public abstract bool IsSupportedDocument { get; }
|
||||
|
||||
public abstract ProjectId ProjectId { get; }
|
||||
|
||||
public abstract SourceTextContainer TextContainer { get; }
|
||||
|
||||
public abstract Workspace Workspace { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
|
||||
namespace Microsoft.VisualStudio.Text
|
||||
{
|
||||
internal static class BufferGraphExtensions
|
||||
{
|
||||
public static Collection<ITextBuffer> GetRazorBuffers(this IBufferGraph bufferGraph)
|
||||
{
|
||||
if (bufferGraph == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bufferGraph));
|
||||
}
|
||||
|
||||
return bufferGraph.GetTextBuffers(TextBufferExtensions.IsRazorBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,267 +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;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
internal class DefaultTextViewRazorDocumentTracker : RazorDocumentTracker
|
||||
{
|
||||
private readonly DefaultTextViewRazorDocumentTrackerService _service;
|
||||
private readonly ITextView _textView;
|
||||
|
||||
private DocumentId _documentId;
|
||||
private ITextBuffer _razorBuffer;
|
||||
private bool _isSupportedProject;
|
||||
private ITextBuffer _cSharpBuffer;
|
||||
private SourceTextContainer _textContainer;
|
||||
private Workspace _workspace;
|
||||
private WorkspaceRegistration _workspaceRegistration;
|
||||
|
||||
public override event EventHandler ContextChanged;
|
||||
|
||||
public DefaultTextViewRazorDocumentTracker(DefaultTextViewRazorDocumentTrackerService service, ITextView textView)
|
||||
{
|
||||
if (service == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(service));
|
||||
}
|
||||
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
_service = service;
|
||||
_textView = textView;
|
||||
|
||||
Update();
|
||||
|
||||
_textView.Closed += TextView_Closed;
|
||||
|
||||
_textView.BufferGraph.GraphBufferContentTypeChanged += BufferGraph_GraphBufferContentTypeChanged;
|
||||
_textView.BufferGraph.GraphBuffersChanged += BufferGraph_GraphBuffersChanged;
|
||||
}
|
||||
|
||||
public override bool IsSupportedDocument => _isSupportedProject && _textContainer != null;
|
||||
|
||||
public override ProjectId ProjectId => _documentId?.ProjectId;
|
||||
|
||||
public override SourceTextContainer TextContainer => _textContainer;
|
||||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
private bool Update()
|
||||
{
|
||||
// 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.
|
||||
|
||||
// If there's no Razor buffer, then there's really nothing to do. This isn't a Razor file.
|
||||
var razorBuffer = _textView.BufferGraph.GetTextBuffers(IsRazorBuffer).FirstOrDefault();
|
||||
|
||||
// The source container is tied to the Razor buffer since that's what we want to watch for changes.
|
||||
SourceTextContainer textContainer = null;
|
||||
if (razorBuffer != null)
|
||||
{
|
||||
textContainer = razorBuffer.AsTextContainer();
|
||||
}
|
||||
|
||||
bool isSupportedProject = false;
|
||||
if (razorBuffer != null)
|
||||
{
|
||||
// The project can be null when the document is in the process of closing.
|
||||
var project = _service.GetProject(razorBuffer);
|
||||
|
||||
if (project != null)
|
||||
{
|
||||
isSupportedProject = _service.IsSupportedProject(project);
|
||||
}
|
||||
}
|
||||
|
||||
// The C# buffer is the other buffer in the graph that is C# (only if we already found a Razor buffer).
|
||||
// We're not currently validating that it's a child of the Razor buffer even though we expect that.
|
||||
ITextBuffer cSharpBuffer = null;
|
||||
if (razorBuffer != null)
|
||||
{
|
||||
cSharpBuffer = _textView.BufferGraph.GetTextBuffers(IsCSharpBuffer).FirstOrDefault();
|
||||
}
|
||||
|
||||
// Now if we have a C# buffer we want to watch for it be attached to a workspace.
|
||||
SourceTextContainer cSharpTextContainer = null;
|
||||
WorkspaceRegistration workspaceRegistration = null;
|
||||
if (cSharpBuffer != null)
|
||||
{
|
||||
cSharpTextContainer = cSharpBuffer.AsTextContainer();
|
||||
Debug.Assert(cSharpTextContainer != null);
|
||||
|
||||
workspaceRegistration = Workspace.GetWorkspaceRegistration(cSharpTextContainer);
|
||||
}
|
||||
|
||||
// Now finally we can see if we have a workspace
|
||||
Workspace workspace = null;
|
||||
if (workspaceRegistration != null)
|
||||
{
|
||||
workspace = workspaceRegistration.Workspace;
|
||||
}
|
||||
|
||||
// Now we know if the Roslyn state for this document has been initialized, let's look for a project.
|
||||
DocumentId documentId = null;
|
||||
if (cSharpTextContainer != null && workspace != null)
|
||||
{
|
||||
documentId = workspace.GetDocumentIdInCurrentContext(cSharpTextContainer);
|
||||
}
|
||||
|
||||
// As a special case, we want to default to the VisualStudioWorkspace until we find out otherwise
|
||||
// This lets us start working before a project gets initialized.
|
||||
if (isSupportedProject && workspace == null)
|
||||
{
|
||||
workspace = _service.Workspace;
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
changed |= razorBuffer == _razorBuffer;
|
||||
changed |= textContainer == _textContainer;
|
||||
changed |= isSupportedProject == _isSupportedProject;
|
||||
changed |= cSharpBuffer == _cSharpBuffer;
|
||||
changed |= workspaceRegistration == _workspaceRegistration;
|
||||
changed |= workspace == _workspace;
|
||||
changed |= documentId == _documentId;
|
||||
|
||||
// Now if nothing has changed we're all done!
|
||||
if (!changed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// OK, so something did change, let's commit the changes.
|
||||
//
|
||||
// These are all of the straightforward ones.
|
||||
_razorBuffer = razorBuffer;
|
||||
_textContainer = textContainer;
|
||||
_isSupportedProject = isSupportedProject;
|
||||
_cSharpBuffer = cSharpBuffer;
|
||||
_documentId = documentId;
|
||||
|
||||
// Now these ones we subscribe to events so it's a little tricky.
|
||||
if (_workspaceRegistration != null)
|
||||
{
|
||||
_workspaceRegistration.WorkspaceChanged -= WorkspaceRegistration_WorkspaceChanged;
|
||||
}
|
||||
if (workspaceRegistration != null)
|
||||
{
|
||||
workspaceRegistration.WorkspaceChanged += WorkspaceRegistration_WorkspaceChanged;
|
||||
}
|
||||
|
||||
if (_workspace != null)
|
||||
{
|
||||
_workspace.DocumentActiveContextChanged -= Workspace_DocumentActiveContextChanged;
|
||||
}
|
||||
if (workspace != null)
|
||||
{
|
||||
workspace.DocumentActiveContextChanged += Workspace_DocumentActiveContextChanged;
|
||||
}
|
||||
|
||||
_workspaceRegistration = workspaceRegistration;
|
||||
_workspace = workspace;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsCSharpBuffer(ITextBuffer textBuffer)
|
||||
{
|
||||
return textBuffer.ContentType.IsOfType("CSharp");
|
||||
}
|
||||
|
||||
private static bool IsRazorBuffer(ITextBuffer textBuffer)
|
||||
{
|
||||
return textBuffer.ContentType.IsOfType(RazorLanguage.ContentType);
|
||||
}
|
||||
|
||||
private void BufferGraph_GraphBuffersChanged(object sender, GraphBuffersChangedEventArgs e)
|
||||
{
|
||||
if (Update())
|
||||
{
|
||||
OnContextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void BufferGraph_GraphBufferContentTypeChanged(object sender, GraphBufferContentTypeChangedEventArgs e)
|
||||
{
|
||||
if (Update())
|
||||
{
|
||||
OnContextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void WorkspaceRegistration_WorkspaceChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (Update())
|
||||
{
|
||||
OnContextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void Workspace_DocumentActiveContextChanged(object sender, DocumentActiveContextChangedEventArgs e)
|
||||
{
|
||||
var textBuffer = e.SourceTextContainer.GetTextBuffer();
|
||||
if (textBuffer != null && (textBuffer == _cSharpBuffer || textBuffer == _razorBuffer))
|
||||
{
|
||||
if (Update())
|
||||
{
|
||||
OnContextChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TextView_Closed(object sender, EventArgs e)
|
||||
{
|
||||
_textView.BufferGraph.GraphBufferContentTypeChanged -= BufferGraph_GraphBufferContentTypeChanged;
|
||||
_textView.BufferGraph.GraphBuffersChanged -= BufferGraph_GraphBuffersChanged;
|
||||
|
||||
if (_workspaceRegistration != null)
|
||||
{
|
||||
_workspaceRegistration.WorkspaceChanged -= WorkspaceRegistration_WorkspaceChanged;
|
||||
}
|
||||
|
||||
if (_workspace != null)
|
||||
{
|
||||
_workspace.DocumentActiveContextChanged -= Workspace_DocumentActiveContextChanged;
|
||||
}
|
||||
|
||||
_textView.Closed -= TextView_Closed;
|
||||
|
||||
_razorBuffer = null;
|
||||
_textContainer = null;
|
||||
_isSupportedProject = false;
|
||||
_cSharpBuffer = null;
|
||||
_workspaceRegistration = null;
|
||||
_workspace = null;
|
||||
_documentId = null;
|
||||
|
||||
OnContextChanged();
|
||||
}
|
||||
|
||||
private void OnContextChanged()
|
||||
{
|
||||
var handler = ContextChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,177 +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;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
[ContentType(RazorLanguage.ContentType)]
|
||||
[TextViewRole(PredefinedTextViewRoles.Editable)]
|
||||
[Export(typeof(IWpfTextViewConnectionListener))]
|
||||
[Export(typeof(TextViewRazorDocumentTrackerService))]
|
||||
internal class DefaultTextViewRazorDocumentTrackerService : TextViewRazorDocumentTrackerService, IWpfTextViewConnectionListener
|
||||
{
|
||||
private readonly ITextDocumentFactoryService _documentFactory;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
private RunningDocumentTable _runningDocumentTable;
|
||||
private IVsSolution _solution;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultTextViewRazorDocumentTrackerService(
|
||||
[Import(typeof(SVsServiceProvider))] IServiceProvider services,
|
||||
ITextDocumentFactoryService documentFactory,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (documentFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentFactory));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_services = services;
|
||||
_documentFactory = documentFactory;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
// Lazy to avoid needing this in unit tests.
|
||||
private RunningDocumentTable RunningDocumentTable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runningDocumentTable == null)
|
||||
{
|
||||
_runningDocumentTable = new RunningDocumentTable(_services);
|
||||
}
|
||||
|
||||
return _runningDocumentTable;
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy to avoid needing this in unit tests.
|
||||
private IVsSolution Solution
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_solution == null)
|
||||
{
|
||||
_solution = (IVsSolution)_services.GetService(typeof(SVsSolution));
|
||||
}
|
||||
|
||||
return _solution;
|
||||
}
|
||||
}
|
||||
|
||||
public Workspace Workspace => _workspace;
|
||||
|
||||
public override RazorDocumentTracker CreateTracker(ITextView textView)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
if (!textView.Properties.TryGetProperty<RazorDocumentTracker>(typeof(RazorDocumentTracker), out var tracker))
|
||||
{
|
||||
tracker = new DefaultTextViewRazorDocumentTracker(this, textView);
|
||||
textView.Properties.AddProperty(typeof(RazorDocumentTracker), tracker);
|
||||
}
|
||||
|
||||
return tracker;
|
||||
}
|
||||
|
||||
public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(textView));
|
||||
}
|
||||
|
||||
if (subjectBuffers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||
}
|
||||
|
||||
// This means a Razor buffer has been attached to this ITextView or the ITextView is just opening with Razor content.
|
||||
//
|
||||
// Call CreateTracker just for the side effect. The tracker will do all of the real work after it's initialized.
|
||||
CreateTracker(textView);
|
||||
}
|
||||
|
||||
public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(textView));
|
||||
}
|
||||
|
||||
if (subjectBuffers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||
}
|
||||
|
||||
// This means a Razor buffer has be detached from this ITextView or the ITextView is closing.
|
||||
//
|
||||
// Do nothing, the tracker will update itself.
|
||||
}
|
||||
|
||||
public virtual IVsHierarchy GetProject(ITextBuffer textBuffer)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
// If there's no document we can't find the FileName, or look for a matching hierarchy.
|
||||
if (!_documentFactory.TryGetTextDocument(textBuffer, out var textDocument))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
RunningDocumentTable.FindDocument(textDocument.FilePath, out var hierarchy, out uint itemId, out uint cookie);
|
||||
|
||||
// We don't currently try to look a Roslyn ProjectId at this point, we just want to know some
|
||||
// basic things.
|
||||
// See https://github.com/dotnet/roslyn/blob/4e3db2b7a0732d45a720e9ed00c00cd22ab67a14/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs#L47
|
||||
// for a more complete implementation.
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
public virtual bool IsSupportedProject(IVsHierarchy project)
|
||||
{
|
||||
if (project == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(project));
|
||||
}
|
||||
|
||||
return project.IsCapabilityMatch("DotNetCoreWeb");
|
||||
}
|
||||
|
||||
public static IEnumerable<ITextView> GetTextViews(ITextBuffer textBuffer)
|
||||
{
|
||||
// TODO: Extract text views from buffer
|
||||
|
||||
return new[] { (ITextView)null };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// 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.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Infrastructure methods to find project information from an <see cref="ITextBuffer"/>.
|
||||
/// </summary>
|
||||
[Export(typeof(TextBufferProjectService))]
|
||||
internal class DefaultTextBufferProjectService : TextBufferProjectService
|
||||
{
|
||||
private readonly RunningDocumentTable _documentTable;
|
||||
private readonly ITextDocumentFactoryService _documentFactory;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultTextBufferProjectService(
|
||||
[Import(typeof(SVsServiceProvider))] IServiceProvider services,
|
||||
ITextDocumentFactoryService documentFactory,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (documentFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentFactory));
|
||||
}
|
||||
|
||||
_documentFactory = documentFactory;
|
||||
_documentTable = new RunningDocumentTable(services);
|
||||
}
|
||||
|
||||
public override IVsHierarchy GetHierarchy(ITextBuffer textBuffer)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
// If there's no document we can't find the FileName, or look for a matching hierarchy.
|
||||
if (!_documentFactory.TryGetTextDocument(textBuffer, out var textDocument))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
_documentTable.FindDocument(textDocument.FilePath, out var hierarchy, out uint itemId, out uint cookie);
|
||||
|
||||
// We don't currently try to look a Roslyn ProjectId at this point, we just want to know some
|
||||
// basic things.
|
||||
// See https://github.com/dotnet/roslyn/blob/4e3db2b7a0732d45a720e9ed00c00cd22ab67a14/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs#L47
|
||||
// for a more complete implementation.
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
public override bool IsSupportedProject(IVsHierarchy hierarchy)
|
||||
{
|
||||
if (hierarchy == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(hierarchy));
|
||||
}
|
||||
|
||||
return hierarchy.IsCapabilityMatch("DotNetCoreWeb");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// 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;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
internal class DefaultVisualStudioDocumentTracker : VisualStudioDocumentTracker
|
||||
{
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly ITextBuffer _textBuffer;
|
||||
private readonly List<ITextView> _textViews;
|
||||
|
||||
private bool _isSupportedProject;
|
||||
private Workspace _workspace;
|
||||
|
||||
public override event EventHandler ContextChanged;
|
||||
|
||||
public DefaultVisualStudioDocumentTracker(TextBufferProjectService projectService, Workspace workspace, ITextBuffer textBuffer)
|
||||
{
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
_projectService = projectService;
|
||||
_textBuffer = textBuffer;
|
||||
_workspace = workspace;
|
||||
|
||||
_textViews = new List<ITextView>();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public override bool IsSupportedProject => _isSupportedProject;
|
||||
|
||||
public override ProjectId ProjectId => null;
|
||||
|
||||
public override ITextBuffer TextBuffer => _textBuffer;
|
||||
|
||||
public override IReadOnlyList<ITextView> TextViews => _textViews;
|
||||
|
||||
public IList<ITextView> TextViewsInternal => _textViews;
|
||||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
private bool Update()
|
||||
{
|
||||
// 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;
|
||||
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)
|
||||
{
|
||||
_isSupportedProject = isSupportedProject;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void OnContextChanged()
|
||||
{
|
||||
var handler = ContextChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
// 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.ObjectModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[ContentType(RazorLanguage.ContentType)]
|
||||
[TextViewRole(PredefinedTextViewRoles.Document)]
|
||||
[Export(typeof(IWpfTextViewConnectionListener))]
|
||||
[Export(typeof(VisualStudioDocumentTrackerFactory))]
|
||||
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener
|
||||
{
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultVisualStudioDocumentTrackerFactory(
|
||||
TextBufferProjectService projectService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_projectService = projectService;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
public Workspace Workspace => _workspace;
|
||||
|
||||
public override VisualStudioDocumentTracker GetTracker(ITextView textView)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
// While it's definitely possible to have multiple Razor text buffers attached to the same text view, there's
|
||||
// no real scenario for it. This method always returns the tracker for the first Razor text buffer, but the
|
||||
// other functionality for this class will maintain state correctly for the other buffers.
|
||||
var textBuffer = textView.BufferGraph.GetRazorBuffers().FirstOrDefault();
|
||||
if (textBuffer == null)
|
||||
{
|
||||
// No Razor buffer, nothing to track.
|
||||
return null;
|
||||
}
|
||||
|
||||
// A little bit of hardening here, to make sure our assumptions are correct.
|
||||
DefaultVisualStudioDocumentTracker tracker;
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out tracker))
|
||||
{
|
||||
Debug.Fail("The document tracker should be initialized");
|
||||
}
|
||||
|
||||
Debug.Assert(tracker.TextViewsInternal.Contains(textView));
|
||||
return tracker;
|
||||
}
|
||||
|
||||
public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(textView));
|
||||
}
|
||||
|
||||
if (subjectBuffers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||
}
|
||||
|
||||
for (var i = 0; i < subjectBuffers.Count; i++)
|
||||
{
|
||||
var textBuffer = subjectBuffers[i];
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DefaultVisualStudioDocumentTracker tracker;
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out tracker))
|
||||
{
|
||||
tracker = new DefaultVisualStudioDocumentTracker(_projectService, _workspace, textBuffer);
|
||||
textBuffer.Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
}
|
||||
|
||||
if (!tracker.TextViewsInternal.Contains(textView))
|
||||
{
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentException(nameof(textView));
|
||||
}
|
||||
|
||||
if (subjectBuffers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||
}
|
||||
|
||||
// This means a Razor buffer has be detached from this ITextView or the ITextView is closing. Since we keep a
|
||||
// list of all of the open text views for each text buffer, we need to update the tracker.
|
||||
//
|
||||
// Notice that this method is called *after* changes are applied to the text buffer(s). We need to check every
|
||||
// one of them for a tracker because the content type could have changed.
|
||||
for (var i = 0; i < subjectBuffers.Count; i++)
|
||||
{
|
||||
var textBuffer = subjectBuffers[i];
|
||||
|
||||
DefaultVisualStudioDocumentTracker tracker;
|
||||
if (textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out tracker))
|
||||
{
|
||||
tracker.TextViewsInternal.Remove(textView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
internal abstract class TextBufferProjectService
|
||||
{
|
||||
public abstract IVsHierarchy GetHierarchy(ITextBuffer textBuffer);
|
||||
|
||||
public abstract bool IsSupportedProject(IVsHierarchy hierarchy);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public abstract class VisualStudioDocumentTracker
|
||||
{
|
||||
public abstract event EventHandler ContextChanged;
|
||||
|
||||
public abstract bool IsSupportedProject { get; }
|
||||
|
||||
public abstract ProjectId ProjectId { get; }
|
||||
|
||||
public abstract Workspace Workspace { get; }
|
||||
|
||||
public abstract ITextBuffer TextBuffer { get; }
|
||||
|
||||
public abstract IReadOnlyList<ITextView> TextViews { 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 Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public abstract class VisualStudioDocumentTrackerFactory
|
||||
{
|
||||
public abstract VisualStudioDocumentTracker GetTracker(ITextView textView);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Razor.Language;
|
|||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
|
|
@ -145,7 +146,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
_foregroundThreadAffinitizedObject.AssertIsBackground();
|
||||
|
||||
var textViews = DefaultTextViewRazorDocumentTrackerService.GetTextViews(_textBuffer);
|
||||
var textViews = Array.Empty<ITextView>();
|
||||
|
||||
foreach (var textView in textViews)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.EditorFeatures.Text" Version="$(RoslynDevVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(VsShellVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.ComponentModelHost" Version="$(VsShellVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.LanguageServices" Version="$(RoslynDevVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient" Version="$(RoslynDevVersion)" />
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
||||
namespace Microsoft.VisualStudio.Text
|
||||
{
|
||||
internal static class TextBufferExtensions
|
||||
{
|
||||
public static bool IsRazorBuffer(this ITextBuffer textBuffer)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
return textBuffer.ContentType.IsOfType(RazorLanguage.ContentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
internal abstract class TextBufferProjectService
|
||||
{
|
||||
public abstract IVsHierarchy GetHierarchy(ITextBuffer textBuffer);
|
||||
|
||||
public abstract bool IsSupportedProject(IVsHierarchy hierarchy);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +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 Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor
|
||||
{
|
||||
public abstract class TextViewRazorDocumentTrackerService
|
||||
{
|
||||
public abstract RazorDocumentTracker CreateTracker(ITextView textView);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
// 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.ObjectModel;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Projection;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public class DefaultVisualStudioDocumentTrackerFactoryTest
|
||||
{
|
||||
private TextBufferProjectService ProjectService { get; } = Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
s.IsSupportedProject(It.IsAny<IVsHierarchy>()) == true);
|
||||
|
||||
private Workspace Workspace { get; } = new AdhocWorkspace();
|
||||
|
||||
private IContentType RazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(RazorLanguage.ContentType) == true);
|
||||
|
||||
private IContentType NonRazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(It.IsAny<string>()) == false);
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
var tracker = buffers[0].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
|
||||
Assert.Equal(buffers[0], tracker.TextBuffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection()),
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
var tracker = buffers[0].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
|
||||
Assert.Equal(buffers[0], tracker.TextBuffer);
|
||||
|
||||
Assert.False(buffers[1].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
|
||||
|
||||
tracker = buffers[2].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
|
||||
Assert.Equal(buffers[2], tracker.TextBuffer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// 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]);
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
Assert.Same(tracker, buffers[0].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker)));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView1 = Mock.Of<IWpfTextView>();
|
||||
var textView2 = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// 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]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView2, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
Assert.Same(tracker, buffers[0].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker)));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1), v => Assert.Same(v, textView2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView1 = Mock.Of<IWpfTextView>();
|
||||
var textView2 = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// 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]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
tracker.TextViewsInternal.Add(textView2);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
tracker = new DefaultVisualStudioDocumentTracker(ProjectService, Workspace, buffers[1]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
tracker.TextViewsInternal.Add(textView2);
|
||||
buffers[1].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersDisconnected(textView2, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
tracker = buffers[0].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1));
|
||||
|
||||
tracker = buffers[1].Properties.GetProperty<DefaultVisualStudioDocumentTracker>(typeof(VisualStudioDocumentTracker));
|
||||
Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersDisconnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTracker_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
var bufferGraph = Mock.Of<IBufferGraph>(g => g.GetTextBuffers(It.IsAny<Predicate<ITextBuffer>>()) == buffers);
|
||||
|
||||
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]);
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
// Act
|
||||
var result = factory.GetTracker(textView);
|
||||
|
||||
// Assert
|
||||
Assert.Same(tracker, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTracker_WithoutRazorBuffer_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(ProjectService, Workspace);
|
||||
|
||||
var buffers = new Collection<ITextBuffer>();
|
||||
|
||||
var bufferGraph = Mock.Of<IBufferGraph>(g => g.GetTextBuffers(It.IsAny<Predicate<ITextBuffer>>()) == buffers);
|
||||
|
||||
var textView = Mock.Of<IWpfTextView>(v => v.BufferGraph == bufferGraph);
|
||||
|
||||
// Act
|
||||
var result = factory.GetTracker(textView);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
|
||||
<!-- The binding redirects are needed due to a conflict between the test host and Roslyn regarding System.Collections.Immutable -->
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -22,6 +26,8 @@
|
|||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="$(RoslynDevVersion)" NoWarn="NU1605" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(RoslynDevVersion)" NoWarn="NU1605" />
|
||||
|
||||
<PackageReference Include="Moq" Version="$(MoqVersion)" />
|
||||
|
||||
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ using System;
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
||||
{
|
||||
public class RazorDocumentInfoViewModel : NotifyPropertyChanged
|
||||
{
|
||||
private readonly RazorDocumentTracker _documentTracker;
|
||||
private readonly VisualStudioDocumentTracker _documentTracker;
|
||||
|
||||
public RazorDocumentInfoViewModel(RazorDocumentTracker documentTracker)
|
||||
public RazorDocumentInfoViewModel(VisualStudioDocumentTracker documentTracker)
|
||||
{
|
||||
if (documentTracker == null)
|
||||
{
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
_documentTracker = documentTracker;
|
||||
}
|
||||
|
||||
public bool IsSupportedDocument => _documentTracker.IsSupportedDocument;
|
||||
public bool IsSupportedDocument => _documentTracker.IsSupportedProject;
|
||||
|
||||
public Project Project
|
||||
{
|
||||
|
|
@ -42,8 +42,6 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
|
||||
public ProjectId ProjectId => _documentTracker.ProjectId;
|
||||
|
||||
public SourceTextContainer TextContainer => _documentTracker.TextContainer;
|
||||
|
||||
public Workspace Workspace => _documentTracker.Workspace;
|
||||
|
||||
public HostLanguageServices RazorLanguageServices => Workspace?.Services.GetLanguageServices(RazorLanguage.Name);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using System.Windows;
|
|||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.ComponentModelHost;
|
||||
using Microsoft.VisualStudio.Editor;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor;
|
||||
using Microsoft.VisualStudio.LanguageServices.Razor.Editor;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
|
@ -21,13 +21,13 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
internal class RazorDocumentInfoWindow : ToolWindowPane
|
||||
{
|
||||
private IVsEditorAdaptersFactoryService _adapterFactory;
|
||||
private TextViewRazorDocumentTrackerService _documentTrackerService;
|
||||
private VisualStudioDocumentTrackerFactory _documentTrackerService;
|
||||
private IVsTextManager _textManager;
|
||||
private IVsRunningDocumentTable _rdt;
|
||||
|
||||
private uint _cookie;
|
||||
private ITextView _textView;
|
||||
private RazorDocumentTracker _documentTracker;
|
||||
private VisualStudioDocumentTracker _documentTracker;
|
||||
|
||||
public RazorDocumentInfoWindow()
|
||||
: base(null)
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
|
||||
var component = (IComponentModel)GetService(typeof(SComponentModel));
|
||||
_adapterFactory = component.GetService<IVsEditorAdaptersFactoryService>();
|
||||
_documentTrackerService = component.GetService<TextViewRazorDocumentTrackerService>();
|
||||
_documentTrackerService = component.GetService<VisualStudioDocumentTrackerFactory>();
|
||||
|
||||
_textManager = (IVsTextManager)GetService(typeof(SVsTextManager));
|
||||
_rdt = (IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable));
|
||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
_documentTracker.ContextChanged -= DocumentTracker_ContextChanged;
|
||||
}
|
||||
|
||||
_documentTracker = _documentTrackerService.CreateTracker(textView);
|
||||
_documentTracker = _documentTrackerService.GetTracker(textView);
|
||||
_documentTracker.ContextChanged += DocumentTracker_ContextChanged;
|
||||
|
||||
((FrameworkElement)Content).DataContext = new RazorDocumentInfoViewModel(_documentTracker);
|
||||
|
|
|
|||
|
|
@ -19,34 +19,13 @@
|
|||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Label Grid.Column="0" Grid.Row="0" Content="Project"/>
|
||||
<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="Text Container"/>
|
||||
<Label Grid.Column="1" Grid.Row="2" Content="{Binding TextContainer}"/>
|
||||
<Label Grid.Column="0" Grid.Row="3" Content="Workspace"/>
|
||||
<Label Grid.Column="1" Grid.Row="3" Content="{Binding Workspace}"/>
|
||||
<Label Grid.Column="0" Grid.Row="4" Content="Razor Lang"/>
|
||||
<Label Grid.Column="1" Grid.Row="4" Content="{Binding RazorLanguageServices}"/>
|
||||
<Label Grid.Column="0" Grid.Row="5" Content="Resolver"/>
|
||||
<Label Grid.Column="1" Grid.Row="5" Content="{Binding TagHelperResolver}"/>
|
||||
<Label Grid.Column="0" Grid.Row="6" Content="Syntax Facts"/>
|
||||
<Label Grid.Column="1" Grid.Row="6" Content="{Binding RazorSyntaxFactsService}"/>
|
||||
<Label Grid.Column="0" Grid.Row="7" Content="Template Eng"/>
|
||||
<Label Grid.Column="1" Grid.Row="7" Content="{Binding RazorTemplateEngineFactoryService}"/>
|
||||
<Label Grid.Column="0" Grid.Row="8" Content="Completion Service"/>
|
||||
<Label Grid.Column="1" Grid.Row="8" Content="{Binding TagHelperCompletionService}"/>
|
||||
<Label Grid.Column="0" Grid.Row="9" Content="TH Facts"/>
|
||||
<Label Grid.Column="1" Grid.Row="9" Content="{Binding TagHelperFactsService}"/>
|
||||
<Label Grid.Column="0" Grid.Row="2" Content="Workspace"/>
|
||||
<Label Grid.Column="1" Grid.Row="2" Content="{Binding Workspace}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<VsixVersion>15.5</VsixVersion>
|
||||
<VsixVersion Condition="'$(BuildNumber)'!=''">$(VsixVersion).$(BuildNumber)</VsixVersion>
|
||||
<VsixVersion Condition="'$(BuildNumber)'==''">$(VsixVersion).999999</VsixVersion>
|
||||
<MinimumVisualStudioVersion>15.0</MinimumVisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<NuGetPackageImportStamp>
|
||||
|
|
|
|||
Loading…
Reference in New Issue