Manage VisualStudioRazorParser lifetime.
- Exposed `VisualStudioRazorParser`, `DocumentStructureChangedEventArgs` and `ICanHasContextChangedListener` as ways to consume the new parser for a Razor document. - Split the `VisualStudioRazorParser` into an abstract base and an implementation to avoid internal constructors. - Changed the parser and corresponding smart indenter to take in document trackers, template engine factories and parser context change listeners. Of these additions the parser context change listeners will be deprecated once we own the TagHelper discovery mechanisms. - Changed how the parser manages its internal parsing life cycle. It now creates template engines when the document tracker tells it to. So when project changes happen or new documents are opened the parser will re-instantiate its internal parser to ensure that it is parsing against the correct configurations. - Removed all accessor services in favor of a singular RazorEditorFactoryService. This service is responsible for retrieving/creating various Razor components. - Changed the code document provider to now use the parser provider in order to locate code documents associated with buffers. Prior to this that logic was hard coded. - Removed old template engine reconstruction logic in the document tracker now that the parser owns that piece. - Updated tracker to notify listeners when it's unsubscribing. This is how listeners can know when to tear bits down. - Refactored/added pieces to the `DefaultVisualStudioRazorParser` in order to improve its unit/integration testing ability. - Updated existing tests to react to new signatures. - Added new visual studio razor parser tests, uncommented existing ones, and re-enforced ones that were previously flakey. - Added various tests for the new services added, i.e. text buffer factory service tests. #1630
This commit is contained in:
parent
a0733ffa91
commit
212d97e511
|
|
@ -29,15 +29,19 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
private readonly ForegroundDispatcher _dispatcher;
|
||||
private readonly ITextBuffer _textBuffer;
|
||||
private readonly VisualStudioDocumentTrackerFactory _documentTrackerFactory;
|
||||
private readonly VisualStudioDocumentTracker _documentTracker;
|
||||
private readonly IEditorOperationsFactoryService _editorOperationsFactory;
|
||||
private readonly StringBuilder _indentBuilder = new StringBuilder();
|
||||
private BraceIndentationContext _context;
|
||||
|
||||
// Internal for testing
|
||||
internal BraceSmartIndenter()
|
||||
{
|
||||
}
|
||||
|
||||
public BraceSmartIndenter(
|
||||
ForegroundDispatcher dispatcher,
|
||||
ITextBuffer textBuffer,
|
||||
VisualStudioDocumentTrackerFactory documentTrackerFactory,
|
||||
VisualStudioDocumentTracker documentTracker,
|
||||
IEditorOperationsFactoryService editorOperationsFactory)
|
||||
{
|
||||
if (dispatcher == null)
|
||||
|
|
@ -45,14 +49,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
if (textBuffer == null)
|
||||
if (documentTracker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
if (documentTrackerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTrackerFactory));
|
||||
throw new ArgumentNullException(nameof(documentTracker));
|
||||
}
|
||||
|
||||
if (editorOperationsFactory == null)
|
||||
|
|
@ -61,9 +60,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
|
||||
_dispatcher = dispatcher;
|
||||
_textBuffer = textBuffer;
|
||||
_documentTrackerFactory = documentTrackerFactory;
|
||||
_documentTracker = documentTracker;
|
||||
_editorOperationsFactory = editorOperationsFactory;
|
||||
_textBuffer = _documentTracker.TextBuffer;
|
||||
_textBuffer.Changed += TextBuffer_OnChanged;
|
||||
_textBuffer.PostChanged += TextBuffer_OnPostChanged;
|
||||
}
|
||||
|
|
@ -95,16 +94,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return;
|
||||
}
|
||||
|
||||
var documentTracker = _documentTrackerFactory.GetTracker(_textBuffer);
|
||||
|
||||
// Extra hardening, this should never be null.
|
||||
if (documentTracker == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newText = changeInformation.newText;
|
||||
if (TryCreateIndentationContext(changeInformation.firstChange.NewPosition, newText.Length, newText, documentTracker, out var context))
|
||||
if (TryCreateIndentationContext(changeInformation.firstChange.NewPosition, newText.Length, newText, _documentTracker, out var context))
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal abstract class BraceSmartIndenterFactory
|
||||
{
|
||||
public abstract BraceSmartIndenter Create(VisualStudioDocumentTracker documentTracker);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,19 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
[Export(typeof(TextBufferCodeDocumentProvider))]
|
||||
internal class DefaultTextBufferCodeDocumentProvider : TextBufferCodeDocumentProvider
|
||||
{
|
||||
private readonly RazorEditorFactoryService _editorFactoryService;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultTextBufferCodeDocumentProvider(RazorEditorFactoryService editorFactoryService)
|
||||
{
|
||||
if (editorFactoryService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(editorFactoryService));
|
||||
}
|
||||
|
||||
_editorFactoryService = editorFactoryService;
|
||||
}
|
||||
|
||||
public override bool TryGetFromBuffer(ITextBuffer textBuffer, out RazorCodeDocument codeDocument)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
|
|
@ -19,8 +32,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
// Hack until we own the lifetime of the parser.
|
||||
if (textBuffer.Properties.TryGetProperty(typeof(VisualStudioRazorParser), out VisualStudioRazorParser parser) && parser.CodeDocument != null)
|
||||
if (_editorFactoryService.TryGetParser(textBuffer, out var parser) && parser.CodeDocument != null)
|
||||
{
|
||||
codeDocument = parser.CodeDocument;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,423 @@
|
|||
// 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.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal class DefaultVisualStudioRazorParser : VisualStudioRazorParser, IDisposable
|
||||
{
|
||||
public override event EventHandler<DocumentStructureChangedEventArgs> DocumentStructureChanged;
|
||||
|
||||
// Internal for testing.
|
||||
internal TimeSpan IdleDelay = TimeSpan.FromSeconds(3);
|
||||
internal Timer _idleTimer;
|
||||
internal BackgroundParser _parser;
|
||||
|
||||
private readonly object IdleLock = new object();
|
||||
private readonly ICompletionBroker _completionBroker;
|
||||
private readonly IEnumerable<IContextChangedListener> _contextChangedListeners;
|
||||
private readonly VisualStudioDocumentTracker _documentTracker;
|
||||
private readonly ForegroundDispatcher _dispatcher;
|
||||
private readonly RazorTemplateEngineFactoryService _templateEngineFactory;
|
||||
private readonly ErrorReporter _errorReporter;
|
||||
private RazorSyntaxTreePartialParser _partialParser;
|
||||
private RazorTemplateEngine _templateEngine;
|
||||
private RazorCodeDocument _codeDocument;
|
||||
private ITextSnapshot _snapshot;
|
||||
|
||||
// For testing only
|
||||
internal DefaultVisualStudioRazorParser(RazorCodeDocument codeDocument)
|
||||
{
|
||||
_codeDocument = codeDocument;
|
||||
}
|
||||
|
||||
public DefaultVisualStudioRazorParser(
|
||||
ForegroundDispatcher dispatcher,
|
||||
VisualStudioDocumentTracker documentTracker,
|
||||
RazorTemplateEngineFactoryService templateEngineFactory,
|
||||
ErrorReporter errorReporter,
|
||||
ICompletionBroker completionBroker,
|
||||
IEnumerable<IContextChangedListener> contextChangedListeners)
|
||||
{
|
||||
if (dispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
if (documentTracker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTracker));
|
||||
}
|
||||
|
||||
if (templateEngineFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(templateEngineFactory));
|
||||
}
|
||||
|
||||
if (errorReporter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorReporter));
|
||||
}
|
||||
|
||||
if (completionBroker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionBroker));
|
||||
}
|
||||
|
||||
if (contextChangedListeners == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contextChangedListeners));
|
||||
}
|
||||
|
||||
_dispatcher = dispatcher;
|
||||
_templateEngineFactory = templateEngineFactory;
|
||||
_errorReporter = errorReporter;
|
||||
_completionBroker = completionBroker;
|
||||
_contextChangedListeners = contextChangedListeners;
|
||||
_documentTracker = documentTracker;
|
||||
|
||||
_documentTracker.ContextChanged += DocumentTracker_ContextChanged;
|
||||
}
|
||||
|
||||
public override RazorTemplateEngine TemplateEngine => _templateEngine;
|
||||
|
||||
public override string FilePath => _documentTracker.FilePath;
|
||||
|
||||
public override RazorCodeDocument CodeDocument => _codeDocument;
|
||||
|
||||
public override ITextSnapshot Snapshot => _snapshot;
|
||||
|
||||
public override ITextBuffer TextBuffer => _documentTracker.TextBuffer;
|
||||
|
||||
// Used in unit tests to ensure we can be notified when idle starts.
|
||||
internal ManualResetEventSlim NotifyForegroundIdleStart { get; set; }
|
||||
|
||||
// Used in unit tests to ensure we can block background idle work.
|
||||
internal ManualResetEventSlim BlockBackgroundIdleWork { get; set; }
|
||||
|
||||
public override async Task ReparseAsync()
|
||||
{
|
||||
// Can be called from any thread
|
||||
|
||||
if (_dispatcher.IsForegroundThread)
|
||||
{
|
||||
ReparseOnForeground(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Task.Factory.StartNew(ReparseOnForeground, null, CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
StopParser();
|
||||
|
||||
_documentTracker.ContextChanged -= DocumentTracker_ContextChanged;
|
||||
|
||||
StopIdleTimer();
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void DocumentTracker_ContextChanged(object sender, EventArgs args)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
if (!TryReinitializeParser())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NotifyParserContextChanged();
|
||||
|
||||
// We have a new parser, force a reparse to generate new document information. Note that this
|
||||
// only blocks until the reparse change has been queued.
|
||||
ReparseAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal bool TryReinitializeParser()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
StopParser();
|
||||
|
||||
if (!_documentTracker.IsSupportedProject)
|
||||
{
|
||||
// Tracker is either starting up, tearing down or wrongfully instantiated.
|
||||
// Either way, the tracker can't act on its associated project, neither can we.
|
||||
return false;
|
||||
}
|
||||
|
||||
StartParser();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void NotifyParserContextChanged()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
// This is temporary until we own the TagHelper resolution system. At that point the parser will push out updates
|
||||
// via DocumentStructureChangedEvents when contexts change. For now, listeners need to know more information about
|
||||
// the parser. In the case that the tracker does not belong to a supported project the editor will tear down its
|
||||
// attachment to the parser when it recognizes the document closing.
|
||||
foreach (var contextChangeListener in _contextChangedListeners)
|
||||
{
|
||||
contextChangeListener.OnContextChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StartParser()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
var projectDirectory = Path.GetDirectoryName(_documentTracker.ProjectPath);
|
||||
_templateEngine = _templateEngineFactory.Create(projectDirectory, ConfigureTemplateEngine);
|
||||
_parser = new BackgroundParser(TemplateEngine, FilePath);
|
||||
_parser.ResultsReady += OnResultsReady;
|
||||
_parser.Start();
|
||||
|
||||
TextBuffer.Changed += TextBuffer_OnChanged;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StopParser()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
if (_parser != null)
|
||||
{
|
||||
// Detatch from the text buffer until we have a new parser to handle changes.
|
||||
TextBuffer.Changed -= TextBuffer_OnChanged;
|
||||
|
||||
_parser.ResultsReady -= OnResultsReady;
|
||||
_parser.Dispose();
|
||||
_parser = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StartIdleTimer()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
lock (IdleLock)
|
||||
{
|
||||
if (_idleTimer == null)
|
||||
{
|
||||
// Timer will fire after a fixed delay, but only once.
|
||||
_idleTimer = new Timer(Timer_Tick, null, IdleDelay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StopIdleTimer()
|
||||
{
|
||||
// Can be called from any thread.
|
||||
|
||||
lock (IdleLock)
|
||||
{
|
||||
if (_idleTimer != null)
|
||||
{
|
||||
_idleTimer.Dispose();
|
||||
_idleTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs args)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
if (args.Changes.Count > 0)
|
||||
{
|
||||
// Idle timers are used to track provisional changes. Provisional changes only last for a single text change. After that normal
|
||||
// partial parsing rules apply (stop the timer).
|
||||
StopIdleTimer();
|
||||
}
|
||||
|
||||
if (!args.TextChangeOccurred(out var changeInformation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var change = new SourceChange(changeInformation.firstChange.OldPosition, changeInformation.oldText.Length, changeInformation.newText);
|
||||
var snapshot = args.After;
|
||||
var result = PartialParseResultInternal.Rejected;
|
||||
|
||||
using (_parser.SynchronizeMainThreadState())
|
||||
{
|
||||
// Check if we can partial-parse
|
||||
if (_partialParser != null && _parser.IsIdle)
|
||||
{
|
||||
result = _partialParser.Parse(change);
|
||||
}
|
||||
}
|
||||
|
||||
// If partial parsing failed or there were outstanding parser tasks, start a full reparse
|
||||
if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected)
|
||||
{
|
||||
_parser.QueueChange(change, snapshot);
|
||||
}
|
||||
|
||||
if ((result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional)
|
||||
{
|
||||
StartIdleTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIdle(object state)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
OnNotifyForegroundIdle();
|
||||
|
||||
foreach (var textView in _documentTracker.TextViews)
|
||||
{
|
||||
if (_completionBroker.IsCompletionActive(textView))
|
||||
{
|
||||
// Completion list is still active, need to re-start timer.
|
||||
StartIdleTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This only blocks until the reparse change has been queued.
|
||||
ReparseAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private void ReparseOnForeground(object state)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
if (_parser == null)
|
||||
{
|
||||
Debug.Fail("Reparse being attempted after the parser has been disposed.");
|
||||
return;
|
||||
}
|
||||
|
||||
var snapshot = TextBuffer.CurrentSnapshot;
|
||||
_parser.QueueChange(null, snapshot);
|
||||
}
|
||||
|
||||
private void OnNotifyForegroundIdle()
|
||||
{
|
||||
if (NotifyForegroundIdleStart != null)
|
||||
{
|
||||
NotifyForegroundIdleStart.Set();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartingBackgroundIdleWork()
|
||||
{
|
||||
if (BlockBackgroundIdleWork != null)
|
||||
{
|
||||
BlockBackgroundIdleWork.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private async void Timer_Tick(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
_dispatcher.AssertBackgroundThread();
|
||||
|
||||
OnStartingBackgroundIdleWork();
|
||||
|
||||
StopIdleTimer();
|
||||
|
||||
// We need to get back to the UI thread to properly check if a completion is active.
|
||||
await Task.Factory.StartNew(OnIdle, null, CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// This is something totally unexpected, let's just send it over to the workspace.
|
||||
await Task.Factory.StartNew(() => _errorReporter.ReportError(ex), CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args)
|
||||
{
|
||||
_dispatcher.AssertBackgroundThread();
|
||||
|
||||
if (DocumentStructureChanged != null)
|
||||
{
|
||||
if (args.Snapshot != TextBuffer.CurrentSnapshot)
|
||||
{
|
||||
// A different text change is being parsed.
|
||||
return;
|
||||
}
|
||||
|
||||
_codeDocument = args.CodeDocument;
|
||||
_snapshot = args.Snapshot;
|
||||
_partialParser = new RazorSyntaxTreePartialParser(CodeDocument.GetSyntaxTree());
|
||||
DocumentStructureChanged(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureTemplateEngine(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new VisualStudioParserOptionsFeature());
|
||||
builder.Features.Add(new VisualStudioTagHelperFeature(TextBuffer));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class will cease to be useful once we harvest/monitor settings from the editor.
|
||||
/// </summary>
|
||||
private class VisualStudioParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
|
||||
{
|
||||
public int Order { get; set; }
|
||||
|
||||
public void Configure(RazorCodeGenerationOptionsBuilder options)
|
||||
{
|
||||
options.IndentSize = 4;
|
||||
options.IndentWithTabs = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class will cease to be useful once we control TagHelper discovery. For now, it delegates discovery
|
||||
/// to ITagHelperFeature's that exist on the text buffer.
|
||||
/// </summary>
|
||||
private class VisualStudioTagHelperFeature : ITagHelperFeature
|
||||
{
|
||||
private readonly ITextBuffer _textBuffer;
|
||||
|
||||
public VisualStudioTagHelperFeature(ITextBuffer textBuffer)
|
||||
{
|
||||
_textBuffer = textBuffer;
|
||||
}
|
||||
|
||||
public RazorEngine Engine { get; set; }
|
||||
|
||||
public IReadOnlyList<TagHelperDescriptor> GetDescriptors()
|
||||
{
|
||||
if (_textBuffer.Properties.TryGetProperty(typeof(ITagHelperFeature), out ITagHelperFeature feature))
|
||||
{
|
||||
return feature.GetDescriptors();
|
||||
}
|
||||
|
||||
return Array.Empty<TagHelperDescriptor>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ using Microsoft.VisualStudio.Text;
|
|||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal sealed class DocumentStructureChangedEventArgs : EventArgs
|
||||
public sealed class DocumentStructureChangedEventArgs : EventArgs
|
||||
{
|
||||
public DocumentStructureChangedEventArgs(
|
||||
SourceChange change,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.VisualStudio.Editor.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// This class will cease to be useful once the Razor tooling owns TagHelper discovery
|
||||
/// </summary>
|
||||
public interface IContextChangedListener
|
||||
{
|
||||
void OnContextChanged(VisualStudioRazorParser parser);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
public abstract class RazorEditorFactoryService
|
||||
{
|
||||
public abstract bool TryGetDocumentTracker(ITextBuffer textBuffer, out VisualStudioDocumentTracker documentTracker);
|
||||
|
||||
internal abstract bool TryGetParser(ITextBuffer textBuffer, out VisualStudioRazorParser parser);
|
||||
|
||||
internal abstract bool TryGetSmartIndenter(ITextBuffer textBuffer, out BraceSmartIndenter braceSmartIndenter);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
public abstract bool IsSupportedProject { get; }
|
||||
|
||||
public abstract string FilePath { get; }
|
||||
|
||||
public abstract string ProjectPath { get; }
|
||||
|
||||
public abstract Project Project { get; }
|
||||
|
||||
public abstract Workspace Workspace { get; }
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
public abstract class VisualStudioDocumentTrackerFactory
|
||||
internal abstract class VisualStudioDocumentTrackerFactory
|
||||
{
|
||||
public abstract VisualStudioDocumentTracker GetTracker(ITextView textView);
|
||||
|
||||
public abstract VisualStudioDocumentTracker GetTracker(ITextBuffer textBuffer);
|
||||
public abstract VisualStudioDocumentTracker Create(ITextBuffer textBuffer);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,266 +2,26 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Operations;
|
||||
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal class VisualStudioRazorParser : IDisposable
|
||||
public abstract class VisualStudioRazorParser
|
||||
{
|
||||
// Internal for testing.
|
||||
internal readonly ITextBuffer _textBuffer;
|
||||
internal TimeSpan IdleDelay = TimeSpan.FromSeconds(3);
|
||||
internal Timer _idleTimer;
|
||||
public abstract event EventHandler<DocumentStructureChangedEventArgs> DocumentStructureChanged;
|
||||
|
||||
private readonly object IdleLock = new object();
|
||||
private readonly ICompletionBroker _completionBroker;
|
||||
private readonly VisualStudioDocumentTrackerFactory _documentTrackerFactory;
|
||||
private readonly BackgroundParser _parser;
|
||||
private readonly ForegroundDispatcher _dispatcher;
|
||||
private readonly ErrorReporter _errorReporter;
|
||||
private RazorSyntaxTreePartialParser _partialParser;
|
||||
private BraceSmartIndenter _braceSmartIndenter;
|
||||
public abstract RazorTemplateEngine TemplateEngine { get; }
|
||||
|
||||
// For testing only
|
||||
internal VisualStudioRazorParser(RazorCodeDocument codeDocument)
|
||||
{
|
||||
CodeDocument = codeDocument;
|
||||
}
|
||||
public abstract string FilePath { get; }
|
||||
|
||||
public VisualStudioRazorParser(
|
||||
ForegroundDispatcher dispatcher,
|
||||
ITextBuffer buffer,
|
||||
RazorTemplateEngine templateEngine,
|
||||
string filePath,
|
||||
ErrorReporter errorReporter,
|
||||
ICompletionBroker completionBroker,
|
||||
VisualStudioDocumentTrackerFactory documentTrackerFactory,
|
||||
IEditorOperationsFactoryService editorOperationsFactory)
|
||||
{
|
||||
if (dispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
public abstract RazorCodeDocument CodeDocument { get; }
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
}
|
||||
public abstract ITextSnapshot Snapshot { get; }
|
||||
|
||||
if (templateEngine == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(templateEngine));
|
||||
}
|
||||
public abstract ITextBuffer TextBuffer { get; }
|
||||
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath));
|
||||
}
|
||||
|
||||
if (errorReporter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorReporter));
|
||||
}
|
||||
|
||||
if (completionBroker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionBroker));
|
||||
}
|
||||
|
||||
if (documentTrackerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTrackerFactory));
|
||||
}
|
||||
|
||||
if (editorOperationsFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(editorOperationsFactory));
|
||||
}
|
||||
|
||||
_dispatcher = dispatcher;
|
||||
TemplateEngine = templateEngine;
|
||||
FilePath = filePath;
|
||||
_errorReporter = errorReporter;
|
||||
_textBuffer = buffer;
|
||||
_completionBroker = completionBroker;
|
||||
_documentTrackerFactory = documentTrackerFactory;
|
||||
_textBuffer.Changed += TextBuffer_OnChanged;
|
||||
_braceSmartIndenter = new BraceSmartIndenter(_dispatcher, _textBuffer, _documentTrackerFactory, editorOperationsFactory);
|
||||
_parser = new BackgroundParser(templateEngine, filePath);
|
||||
_parser.ResultsReady += OnResultsReady;
|
||||
|
||||
_parser.Start();
|
||||
}
|
||||
|
||||
public event EventHandler<DocumentStructureChangedEventArgs> DocumentStructureChanged;
|
||||
|
||||
public RazorTemplateEngine TemplateEngine { get; }
|
||||
|
||||
public string FilePath { get; }
|
||||
|
||||
public RazorCodeDocument CodeDocument { get; private set; }
|
||||
|
||||
public ITextSnapshot Snapshot { get; private set; }
|
||||
|
||||
public void Reparse()
|
||||
{
|
||||
// Can be called from any thread
|
||||
var snapshot = _textBuffer.CurrentSnapshot;
|
||||
_parser.QueueChange(null, snapshot);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
_textBuffer.Changed -= TextBuffer_OnChanged;
|
||||
_braceSmartIndenter.Dispose();
|
||||
_parser.Dispose();
|
||||
|
||||
StopIdleTimer();
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StartIdleTimer()
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
lock (IdleLock)
|
||||
{
|
||||
if (_idleTimer == null)
|
||||
{
|
||||
// Timer will fire after a fixed delay, but only once.
|
||||
_idleTimer = new Timer(Timer_Tick, null, IdleDelay, Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void StopIdleTimer()
|
||||
{
|
||||
// Can be called from any thread.
|
||||
|
||||
lock (IdleLock)
|
||||
{
|
||||
if (_idleTimer != null)
|
||||
{
|
||||
_idleTimer.Dispose();
|
||||
_idleTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBuffer_OnChanged(object sender, TextContentChangedEventArgs args)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
if (args.Changes.Count > 0)
|
||||
{
|
||||
// Idle timers are used to track provisional changes. Provisional changes only last for a single text change. After that normal
|
||||
// partial parsing rules apply (stop the timer).
|
||||
StopIdleTimer();
|
||||
}
|
||||
|
||||
if (!args.TextChangeOccurred(out var changeInformation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var change = new SourceChange(changeInformation.firstChange.OldPosition, changeInformation.oldText.Length, changeInformation.newText);
|
||||
var snapshot = args.After;
|
||||
var result = PartialParseResultInternal.Rejected;
|
||||
|
||||
using (_parser.SynchronizeMainThreadState())
|
||||
{
|
||||
// Check if we can partial-parse
|
||||
if (_partialParser != null && _parser.IsIdle)
|
||||
{
|
||||
result = _partialParser.Parse(change);
|
||||
}
|
||||
}
|
||||
|
||||
// If partial parsing failed or there were outstanding parser tasks, start a full reparse
|
||||
if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected)
|
||||
{
|
||||
_parser.QueueChange(change, snapshot);
|
||||
}
|
||||
|
||||
if ((result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional)
|
||||
{
|
||||
StartIdleTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIdle(object state)
|
||||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
var documentTracker = _documentTrackerFactory.GetTracker(_textBuffer);
|
||||
|
||||
if (documentTracker == null)
|
||||
{
|
||||
Debug.Fail("Document tracker should never be null when checking idle state.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var textView in documentTracker.TextViews)
|
||||
{
|
||||
if (_completionBroker.IsCompletionActive(textView))
|
||||
{
|
||||
// Completion list is still active, need to re-start timer.
|
||||
StartIdleTimer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Reparse();
|
||||
}
|
||||
|
||||
private async void Timer_Tick(object state)
|
||||
{
|
||||
try
|
||||
{
|
||||
_dispatcher.AssertBackgroundThread();
|
||||
|
||||
StopIdleTimer();
|
||||
|
||||
// We need to get back to the UI thread to properly check if a completion is active.
|
||||
await Task.Factory.StartNew(OnIdle, null, CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// This is something totally unexpected, let's just send it over to the workspace.
|
||||
await Task.Factory.StartNew(() => _errorReporter.ReportError(ex), CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args)
|
||||
{
|
||||
_dispatcher.AssertBackgroundThread();
|
||||
|
||||
if (DocumentStructureChanged != null)
|
||||
{
|
||||
if (args.Snapshot != _textBuffer.CurrentSnapshot)
|
||||
{
|
||||
// A different text change is being parsed.
|
||||
return;
|
||||
}
|
||||
|
||||
CodeDocument = args.CodeDocument;
|
||||
Snapshot = args.Snapshot;
|
||||
_partialParser = new RazorSyntaxTreePartialParser(CodeDocument.GetSyntaxTree());
|
||||
DocumentStructureChanged(this, args);
|
||||
}
|
||||
}
|
||||
public abstract Task ReparseAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal abstract class VisualStudioRazorParserFactory
|
||||
{
|
||||
public abstract VisualStudioRazorParser Create(VisualStudioDocumentTracker documentTracker);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Text.Operations;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[System.Composition.Shared]
|
||||
[Export(typeof(BraceSmartIndenterFactory))]
|
||||
internal class DefaultBraceSmartIndenterFactory : BraceSmartIndenterFactory
|
||||
{
|
||||
private readonly IEditorOperationsFactoryService _editorOperationsFactory;
|
||||
private readonly ForegroundDispatcher _dispatcher;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultBraceSmartIndenterFactory(
|
||||
IEditorOperationsFactoryService editorOperationsFactory,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (editorOperationsFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(editorOperationsFactory));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_editorOperationsFactory = editorOperationsFactory;
|
||||
_dispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
||||
}
|
||||
|
||||
public override BraceSmartIndenter Create(VisualStudioDocumentTracker documentTracker)
|
||||
{
|
||||
if (documentTracker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTracker));
|
||||
}
|
||||
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
var braceSmartIndenter = new BraceSmartIndenter(_dispatcher, documentTracker, _editorOperationsFactory);
|
||||
|
||||
return braceSmartIndenter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
// 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 System.Diagnostics;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[System.Composition.Shared]
|
||||
[Export(typeof(RazorEditorFactoryService))]
|
||||
internal class DefaultRazorEditorFactoryService : RazorEditorFactoryService
|
||||
{
|
||||
private static readonly object RazorTextBufferInitializationKey = new object();
|
||||
|
||||
private readonly VisualStudioDocumentTrackerFactory _documentTrackerFactory;
|
||||
private readonly VisualStudioRazorParserFactory _parserFactory;
|
||||
private readonly BraceSmartIndenterFactory _braceSmartIndenterFactory;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultRazorEditorFactoryService(
|
||||
VisualStudioDocumentTrackerFactory documentTrackerFactory,
|
||||
VisualStudioRazorParserFactory parserFactory,
|
||||
BraceSmartIndenterFactory braceSmartIndenterFactory)
|
||||
{
|
||||
if (documentTrackerFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTrackerFactory));
|
||||
}
|
||||
|
||||
if (parserFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parserFactory));
|
||||
}
|
||||
|
||||
if (braceSmartIndenterFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(braceSmartIndenterFactory));
|
||||
}
|
||||
|
||||
_documentTrackerFactory = documentTrackerFactory;
|
||||
_parserFactory = parserFactory;
|
||||
_braceSmartIndenterFactory = braceSmartIndenterFactory;
|
||||
}
|
||||
|
||||
public override bool TryGetDocumentTracker(ITextBuffer textBuffer, out VisualStudioDocumentTracker documentTracker)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
{
|
||||
documentTracker = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out documentTracker))
|
||||
{
|
||||
Debug.Fail("Document tracker should have been stored on the text buffer during initialization.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal override bool TryGetParser(ITextBuffer textBuffer, out VisualStudioRazorParser parser)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
{
|
||||
parser = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(VisualStudioRazorParser), out parser))
|
||||
{
|
||||
Debug.Fail("Parser should have been stored on the text buffer during initialization.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal override bool TryGetSmartIndenter(ITextBuffer textBuffer, out BraceSmartIndenter braceSmartIndenter)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
{
|
||||
braceSmartIndenter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
if (!textBuffer.Properties.TryGetProperty(typeof(BraceSmartIndenter), out braceSmartIndenter))
|
||||
{
|
||||
Debug.Fail("Brace smart indenter should have been stored on the text buffer during initialization.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal void EnsureTextBufferInitialized(ITextBuffer textBuffer)
|
||||
{
|
||||
if (textBuffer.Properties.ContainsProperty(RazorTextBufferInitializationKey))
|
||||
{
|
||||
// Buffer already initialized.
|
||||
return;
|
||||
}
|
||||
|
||||
var tracker = _documentTrackerFactory.Create(textBuffer);
|
||||
textBuffer.Properties[typeof(VisualStudioDocumentTracker)] = tracker;
|
||||
|
||||
var parser = _parserFactory.Create(tracker);
|
||||
textBuffer.Properties[typeof(VisualStudioRazorParser)] = parser;
|
||||
|
||||
var braceSmartIndenter = _braceSmartIndenterFactory.Create(tracker);
|
||||
textBuffer.Properties[typeof(BraceSmartIndenter)] = braceSmartIndenter;
|
||||
|
||||
textBuffer.Properties.AddProperty(RazorTextBufferInitializationKey, RazorTextBufferInitializationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
|
|
@ -19,12 +15,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
internal class DefaultVisualStudioDocumentTracker : VisualStudioDocumentTracker
|
||||
{
|
||||
private readonly string _filePath;
|
||||
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 ProjectSnapshot _project;
|
||||
private string _projectPath;
|
||||
|
|
@ -32,11 +28,17 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
public override event EventHandler ContextChanged;
|
||||
|
||||
public DefaultVisualStudioDocumentTracker(
|
||||
string filePath,
|
||||
ProjectSnapshotManager projectManager,
|
||||
TextBufferProjectService projectService,
|
||||
Workspace workspace,
|
||||
ITextBuffer textBuffer)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(filePath));
|
||||
}
|
||||
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
|
|
@ -57,6 +59,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
_filePath = filePath;
|
||||
_projectManager = projectManager;
|
||||
_projectService = projectService;
|
||||
_textBuffer = textBuffer;
|
||||
|
|
@ -75,10 +78,48 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
|
||||
public override IReadOnlyList<ITextView> TextViews => _textViews;
|
||||
|
||||
public IList<ITextView> TextViewsInternal => _textViews;
|
||||
|
||||
public override Workspace Workspace => _workspace;
|
||||
|
||||
public override string FilePath => _filePath;
|
||||
|
||||
public override string ProjectPath => _projectPath;
|
||||
|
||||
internal void AddTextView(ITextView textView)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
if (!_textViews.Contains(textView))
|
||||
{
|
||||
_textViews.Add(textView);
|
||||
|
||||
if (_textViews.Count == 1)
|
||||
{
|
||||
Subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void RemoveTextView(ITextView textView)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
if (_textViews.Contains(textView))
|
||||
{
|
||||
_textViews.Remove(textView);
|
||||
|
||||
if (_textViews.Count == 0)
|
||||
{
|
||||
Unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override ITextView GetFocusedTextView()
|
||||
{
|
||||
for (var i = 0; i < TextViews.Count; i++)
|
||||
|
|
@ -92,7 +133,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
return null;
|
||||
}
|
||||
|
||||
public void Subscribe()
|
||||
private void Subscribe()
|
||||
{
|
||||
// Fundamentally we have a Razor half of the world as as soon as the document is open - and then later
|
||||
// the C# half of the world will be initialized. This code is in general pretty tolerant of
|
||||
|
|
@ -126,61 +167,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
OnContextChanged(_project);
|
||||
}
|
||||
|
||||
public void Unsubscribe()
|
||||
private void Unsubscribe()
|
||||
{
|
||||
_projectManager.Changed -= ProjectManager_Changed;
|
||||
|
||||
// Detached from project.
|
||||
_isSupportedProject = false;
|
||||
_project = null;
|
||||
OnContextChanged(project: null);
|
||||
}
|
||||
|
||||
private void OnContextChanged(ProjectSnapshot project)
|
||||
{
|
||||
_project = project;
|
||||
|
||||
// Hack: When the context changes we want to replace the template engine held by the parser.
|
||||
// This code isn't super well factored now - it's intended to be limited to one spot until
|
||||
// we have time to a proper redesign.
|
||||
|
||||
if (TextBuffer.Properties.TryGetProperty(typeof(RazorEditorParser), out RazorEditorParser legacyParser) &&
|
||||
legacyParser.TemplateEngine != null &&
|
||||
_projectPath != null)
|
||||
{
|
||||
var factory = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<CodeAnalysis.Razor.RazorTemplateEngineFactoryService>();
|
||||
|
||||
var existingEngine = legacyParser.TemplateEngine;
|
||||
var projectDirectory = Path.GetDirectoryName(_projectPath);
|
||||
var templateEngine = factory.Create(projectDirectory, builder =>
|
||||
{
|
||||
var existingVSParserOptions = existingEngine.Engine.Features.FirstOrDefault(
|
||||
feature => string.Equals(
|
||||
feature.GetType().Name,
|
||||
"VisualStudioParserOptionsFeature",
|
||||
StringComparison.Ordinal));
|
||||
|
||||
if (existingVSParserOptions == null)
|
||||
{
|
||||
Debug.Fail("The VS Parser options should have been set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Features.Add(existingVSParserOptions);
|
||||
}
|
||||
|
||||
var existingTagHelperFeature = existingEngine.Engine.Features
|
||||
.OfType<ITagHelperFeature>()
|
||||
.FirstOrDefault();
|
||||
|
||||
if (existingTagHelperFeature == null)
|
||||
{
|
||||
Debug.Fail("The VS TagHelperFeature should have been set.");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Features.Add(existingTagHelperFeature);
|
||||
}
|
||||
});
|
||||
|
||||
legacyParser.TemplateEngine = templateEngine;
|
||||
}
|
||||
|
||||
var handler = ContextChanged;
|
||||
if (handler != null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,34 +2,30 @@
|
|||
// 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.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[ContentType(RazorLanguage.ContentType)]
|
||||
[TextViewRole(PredefinedTextViewRoles.Document)]
|
||||
[Export(typeof(IWpfTextViewConnectionListener))]
|
||||
[System.Composition.Shared]
|
||||
[Export(typeof(VisualStudioDocumentTrackerFactory))]
|
||||
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory, IWpfTextViewConnectionListener
|
||||
internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory
|
||||
{
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly ITextDocumentFactoryService _textDocumentFactory;
|
||||
private readonly Workspace _workspace;
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly ProjectSnapshotManager _projectManager;
|
||||
private readonly TextBufferProjectService _projectService;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultVisualStudioDocumentTrackerFactory(
|
||||
TextBufferProjectService projectService,
|
||||
ITextDocumentFactoryService textDocumentFactory,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (projectService == null)
|
||||
|
|
@ -37,181 +33,41 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (textDocumentFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textDocumentFactory));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
|
||||
_projectService = projectService;
|
||||
_textDocumentFactory = textDocumentFactory;
|
||||
_workspace = workspace;
|
||||
|
||||
_foregroundDispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
||||
_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(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
ProjectSnapshotManager projectManager,
|
||||
TextBufferProjectService projectService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (projectManager == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectManager));
|
||||
}
|
||||
|
||||
if (projectService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(projectService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_projectManager = projectManager;
|
||||
_projectService = projectService;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
public Workspace Workspace => _workspace;
|
||||
|
||||
public override VisualStudioDocumentTracker GetTracker(ITextView textView)
|
||||
{
|
||||
if (textView == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textView));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
// 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 override VisualStudioDocumentTracker GetTracker(ITextBuffer textBuffer)
|
||||
public override VisualStudioDocumentTracker Create(ITextBuffer textBuffer)
|
||||
{
|
||||
if (textBuffer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textBuffer));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
if (!_textDocumentFactory.TryGetTextDocument(textBuffer, out var textDocument))
|
||||
{
|
||||
// Not a Razor buffer.
|
||||
Debug.Fail("Text document should be available from the text buffer.");
|
||||
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");
|
||||
}
|
||||
var filePath = textDocument.FilePath;
|
||||
var tracker = new DefaultVisualStudioDocumentTracker(filePath, _projectManager, _projectService, _workspace, textBuffer);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
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(_projectManager, _projectService, _workspace, textBuffer);
|
||||
textBuffer.Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
}
|
||||
|
||||
if (!tracker.TextViewsInternal.Contains(textView))
|
||||
{
|
||||
tracker.TextViewsInternal.Add(textView);
|
||||
if (tracker.TextViewsInternal.Count == 1)
|
||||
{
|
||||
tracker.Subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
// 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);
|
||||
if (tracker.TextViewsInternal.Count == 0)
|
||||
{
|
||||
tracker.Unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.ComponentModel.Composition;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using TemplateEngineFactoryService = Microsoft.CodeAnalysis.Razor.RazorTemplateEngineFactoryService;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[System.Composition.Shared]
|
||||
[Export(typeof(VisualStudioRazorParserFactory))]
|
||||
internal class DefaultVisualStudioRazorParserFactory : VisualStudioRazorParserFactory
|
||||
{
|
||||
private readonly ForegroundDispatcher _dispatcher;
|
||||
private readonly TemplateEngineFactoryService _templateEngineFactoryService;
|
||||
private readonly ICompletionBroker _completionBroker;
|
||||
private readonly IEnumerable<IContextChangedListener> _parserContextChangedListeners;
|
||||
private readonly ErrorReporter _errorReporter;
|
||||
|
||||
[ImportingConstructor]
|
||||
public DefaultVisualStudioRazorParserFactory(
|
||||
ICompletionBroker completionBroker,
|
||||
[ImportMany(typeof(IContextChangedListener))] IEnumerable<IContextChangedListener> parserContextChangedListeners,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (completionBroker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(completionBroker));
|
||||
}
|
||||
|
||||
if (parserContextChangedListeners == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parserContextChangedListeners));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_completionBroker = completionBroker;
|
||||
_parserContextChangedListeners = parserContextChangedListeners;
|
||||
_dispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
||||
_errorReporter = workspace.Services.GetRequiredService<ErrorReporter>();
|
||||
var razorLanguageServices = workspace.Services.GetLanguageServices(RazorLanguage.Name);
|
||||
_templateEngineFactoryService = razorLanguageServices.GetRequiredService<TemplateEngineFactoryService>();
|
||||
}
|
||||
|
||||
public override VisualStudioRazorParser Create(VisualStudioDocumentTracker documentTracker)
|
||||
{
|
||||
if (documentTracker == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(documentTracker));
|
||||
}
|
||||
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
var parser = new DefaultVisualStudioRazorParser(
|
||||
_dispatcher,
|
||||
documentTracker,
|
||||
_templateEngineFactoryService,
|
||||
_errorReporter,
|
||||
_completionBroker,
|
||||
_parserContextChangedListeners);
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
[ContentType(RazorLanguage.ContentType)]
|
||||
[TextViewRole(PredefinedTextViewRoles.Document)]
|
||||
[Export(typeof(IWpfTextViewConnectionListener))]
|
||||
internal class RazorTextViewConnectionListener : IWpfTextViewConnectionListener
|
||||
{
|
||||
private readonly ForegroundDispatcher _foregroundDispatcher;
|
||||
private readonly RazorEditorFactoryService _editorFactoryService;
|
||||
private readonly Workspace _workspace;
|
||||
|
||||
[ImportingConstructor]
|
||||
public RazorTextViewConnectionListener(
|
||||
RazorEditorFactoryService editorFactoryService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (editorFactoryService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(editorFactoryService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_editorFactoryService = editorFactoryService;
|
||||
_workspace = workspace;
|
||||
|
||||
_foregroundDispatcher = workspace.Services.GetRequiredService<ForegroundDispatcher>();
|
||||
}
|
||||
|
||||
// This is only for testing. We want to avoid using the actual Roslyn GetService methods in unit tests.
|
||||
internal RazorTextViewConnectionListener(
|
||||
ForegroundDispatcher foregroundDispatcher,
|
||||
RazorEditorFactoryService editorFactoryService,
|
||||
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||
{
|
||||
if (foregroundDispatcher == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(foregroundDispatcher));
|
||||
}
|
||||
|
||||
if (editorFactoryService == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(editorFactoryService));
|
||||
}
|
||||
|
||||
if (workspace == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(workspace));
|
||||
}
|
||||
|
||||
_foregroundDispatcher = foregroundDispatcher;
|
||||
_editorFactoryService = editorFactoryService;
|
||||
_workspace = workspace;
|
||||
}
|
||||
|
||||
public Workspace Workspace => _workspace;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
for (var i = 0; i < subjectBuffers.Count; i++)
|
||||
{
|
||||
var textBuffer = subjectBuffers[i];
|
||||
if (!textBuffer.IsRazorBuffer())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_editorFactoryService.TryGetDocumentTracker(textBuffer, out var documentTracker) ||
|
||||
!(documentTracker is DefaultVisualStudioDocumentTracker tracker))
|
||||
{
|
||||
Debug.Fail("Tracker should always be available given our expectations of the VS workflow.");
|
||||
return;
|
||||
}
|
||||
|
||||
tracker.AddTextView(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));
|
||||
}
|
||||
|
||||
_foregroundDispatcher.AssertForegroundThread();
|
||||
|
||||
// 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 documentTracker;
|
||||
if (textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out documentTracker))
|
||||
{
|
||||
documentTracker.RemoveTextView(textView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var documentTracker = CreateDocumentTracker(() => textBuffer, focusedTextView);
|
||||
textBuffer = CreateTextBuffer(initialSnapshot, documentTracker);
|
||||
var editorOperationsFactory = CreateOperationsFactoryService();
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, textBuffer, CreateDocumentTrackerFactory(() => textBuffer, documentTracker), editorOperationsFactory);
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory);
|
||||
|
||||
// Act
|
||||
textBuffer.ApplyEdit(edit);
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var documentTracker = CreateDocumentTracker(() => textBuffer, focusedTextView);
|
||||
textBuffer = CreateTextBuffer(initialSnapshot, documentTracker);
|
||||
var editorOperationsFactory = CreateOperationsFactoryService();
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, textBuffer, CreateDocumentTrackerFactory(() => textBuffer, documentTracker), editorOperationsFactory);
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory);
|
||||
|
||||
// Act
|
||||
textBuffer.ApplyEdit(edit);
|
||||
|
|
@ -76,7 +76,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var documentTracker = CreateDocumentTracker(() => textBuffer, focusedTextView);
|
||||
textBuffer = CreateTextBuffer(initialSnapshot, documentTracker);
|
||||
var editorOperationsFactory = CreateOperationsFactoryService();
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, textBuffer, CreateDocumentTrackerFactory(() => textBuffer, documentTracker), editorOperationsFactory);
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory);
|
||||
|
||||
// Act
|
||||
textBuffer.ApplyEdit(edit);
|
||||
|
|
|
|||
|
|
@ -65,9 +65,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var editorOperations = new Mock<IEditorOperations>();
|
||||
editorOperations.Setup(operations => operations.MoveToEndOfLine(false));
|
||||
var editorOperationsFactory = new Mock<IEditorOperationsFactoryService>();
|
||||
var documentTracker = CreateDocumentTracker(() => Mock.Of<ITextBuffer>(), textView);
|
||||
editorOperationsFactory.Setup(factory => factory.GetEditorOperations(textView))
|
||||
.Returns(editorOperations.Object);
|
||||
var smartIndenter = new BraceSmartIndenter(Dispatcher, new Mock<ITextBuffer>().Object, new Mock<VisualStudioDocumentTrackerFactory>().Object, editorOperationsFactory.Object);
|
||||
var smartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory.Object);
|
||||
|
||||
// Act
|
||||
smartIndenter.TriggerSmartIndent(textView);
|
||||
|
|
@ -136,12 +137,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
public void TextBuffer_OnChanged_NoopsIfNoChanges()
|
||||
{
|
||||
// Arrange
|
||||
var textBuffer = new Mock<ITextBuffer>();
|
||||
var editorOperationsFactory = new Mock<IEditorOperationsFactoryService>();
|
||||
var changeCollection = new TestTextChangeCollection();
|
||||
var textContentChangeArgs = new TestTextContentChangedEventArgs(changeCollection);
|
||||
var documentTrackerFactory = new Mock<VisualStudioDocumentTrackerFactory>();
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, textBuffer.Object, documentTrackerFactory.Object, editorOperationsFactory.Object);
|
||||
var documentTracker = CreateDocumentTracker(() => Mock.Of<ITextBuffer>(), Mock.Of<ITextView>());
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory.Object);
|
||||
|
||||
// Act & Assert
|
||||
braceSmartIndenter.TextBuffer_OnChanged(null, textContentChangeArgs);
|
||||
|
|
@ -155,8 +155,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
var textBuffer = new TestTextBuffer(initialSnapshot);
|
||||
var edit = new TestEdit(0, 0, initialSnapshot, 0, initialSnapshot, string.Empty);
|
||||
var editorOperationsFactory = new Mock<IEditorOperationsFactoryService>();
|
||||
var documentTrackerFactory = new Mock<VisualStudioDocumentTrackerFactory>();
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, textBuffer, documentTrackerFactory.Object, editorOperationsFactory.Object);
|
||||
var documentTracker = CreateDocumentTracker(() => textBuffer, Mock.Of<ITextView>());
|
||||
var braceSmartIndenter = new BraceSmartIndenter(Dispatcher, documentTracker, editorOperationsFactory.Object);
|
||||
|
||||
// Act & Assert
|
||||
textBuffer.ApplyEdits(edit, edit);
|
||||
|
|
|
|||
|
|
@ -24,15 +24,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return tracker.Object;
|
||||
}
|
||||
|
||||
protected static VisualStudioDocumentTrackerFactory CreateDocumentTrackerFactory(Func<ITextBuffer> bufferAccessor, VisualStudioDocumentTracker documentTracker)
|
||||
{
|
||||
var trackerFactory = new Mock<VisualStudioDocumentTrackerFactory>();
|
||||
trackerFactory.Setup(factory => factory.GetTracker(It.IsAny<ITextBuffer>()))
|
||||
.Returns(documentTracker);
|
||||
|
||||
return trackerFactory.Object;
|
||||
}
|
||||
|
||||
protected static ITextView CreateFocusedTextView(Func<ITextBuffer> textBufferAccessor = null, ITextCaret caret = null)
|
||||
{
|
||||
var focusedTextView = new Mock<ITextView>();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using Mvc1_X = Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
|
||||
using MvcLatest = Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
|
|
@ -197,12 +194,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
|
||||
{
|
||||
public TestProjectSnapshotManager(Workspace workspace)
|
||||
public TestProjectSnapshotManager(Workspace workspace)
|
||||
: base(
|
||||
Mock.Of<ForegroundDispatcher>(),
|
||||
Mock.Of<ErrorReporter>(),
|
||||
Mock.Of<ProjectSnapshotWorker>(),
|
||||
Enumerable.Empty<ProjectSnapshotChangeTrigger>(),
|
||||
Mock.Of<ForegroundDispatcher>(),
|
||||
Mock.Of<ErrorReporter>(),
|
||||
Mock.Of<ProjectSnapshotWorker>(),
|
||||
Enumerable.Empty<ProjectSnapshotChangeTrigger>(),
|
||||
workspace)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -12,38 +11,51 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
public class DefaultTextBufferCodeDocumentProviderTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryGetFromBuffer_UsesVisualStudioRazorParserIfAvailable()
|
||||
public void TryGetFromBuffer_SucceedsIfParserFromProviderHasCodeDocument()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCodeDocument = TestRazorCodeDocument.Create("Hello World");
|
||||
var parser = new VisualStudioRazorParser(expectedCodeDocument);
|
||||
var properties = new PropertyCollection();
|
||||
properties.AddProperty(typeof(VisualStudioRazorParser), parser);
|
||||
var textBuffer = new Mock<ITextBuffer>();
|
||||
textBuffer.Setup(buffer => buffer.Properties)
|
||||
.Returns(properties);
|
||||
var provider = new DefaultTextBufferCodeDocumentProvider();
|
||||
VisualStudioRazorParser parser = new DefaultVisualStudioRazorParser(expectedCodeDocument);
|
||||
var parserProvider = Mock.Of<RazorEditorFactoryService>(p => p.TryGetParser(It.IsAny<ITextBuffer>(), out parser) == true);
|
||||
var textBuffer = Mock.Of<ITextBuffer>();
|
||||
var provider = new DefaultTextBufferCodeDocumentProvider(parserProvider);
|
||||
|
||||
// Act
|
||||
var result = provider.TryGetFromBuffer(textBuffer.Object, out var codeDocument);
|
||||
var result = provider.TryGetFromBuffer(textBuffer, out var codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Same(expectedCodeDocument, codeDocument);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetFromBuffer_FailsIfParserFromProviderMissingCodeDocument()
|
||||
{
|
||||
// Arrange
|
||||
VisualStudioRazorParser parser = new DefaultVisualStudioRazorParser(codeDocument: null);
|
||||
var parserProvider = Mock.Of<RazorEditorFactoryService>(p => p.TryGetParser(It.IsAny<ITextBuffer>(), out parser) == true);
|
||||
var textBuffer = Mock.Of<ITextBuffer>();
|
||||
var provider = new DefaultTextBufferCodeDocumentProvider(parserProvider);
|
||||
|
||||
// Act
|
||||
var result = provider.TryGetFromBuffer(textBuffer, out var codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(codeDocument);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetFromBuffer_FailsIfNoParserIsAvailable()
|
||||
{
|
||||
// Arrange
|
||||
var properties = new PropertyCollection();
|
||||
var textBuffer = new Mock<ITextBuffer>();
|
||||
textBuffer.Setup(buffer => buffer.Properties)
|
||||
.Returns(properties);
|
||||
var provider = new DefaultTextBufferCodeDocumentProvider();
|
||||
VisualStudioRazorParser parser = null;
|
||||
var parserProvider = Mock.Of<RazorEditorFactoryService>(p => p.TryGetParser(It.IsAny<ITextBuffer>(), out parser) == false);
|
||||
var textBuffer = Mock.Of<ITextBuffer>();
|
||||
var provider = new DefaultTextBufferCodeDocumentProvider(parserProvider);
|
||||
|
||||
// Act
|
||||
var result = provider.TryGetFromBuffer(textBuffer.Object, out var codeDocument);
|
||||
var result = provider.TryGetFromBuffer(textBuffer, out var codeDocument);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
|
@ -14,63 +16,32 @@ using Microsoft.VisualStudio.Language.Intellisense;
|
|||
using Microsoft.VisualStudio.Test;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Text.Operations;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
public class VisualStudioRazorParserTest : ForegroundDispatcherTestBase
|
||||
public class DefaultVisualStudioRazorParserIntegrationTest : ForegroundDispatcherTestBase
|
||||
{
|
||||
private const string TestLinePragmaFileName = "C:\\This\\Path\\Is\\Just\\For\\Line\\Pragmas.cshtml";
|
||||
private const string TestProjectPath = "C:\\This\\Path\\Is\\Just\\For\\Project.csproj";
|
||||
|
||||
[Fact]
|
||||
public void ConstructorRequiresNonNullPhysicalPath()
|
||||
{
|
||||
Assert.Throws<ArgumentException>("filePath",
|
||||
() => new VisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
new TestTextBuffer(null),
|
||||
CreateTemplateEngine(),
|
||||
null,
|
||||
new DefaultErrorReporter(),
|
||||
new TestCompletionBroker(),
|
||||
new Mock<VisualStudioDocumentTrackerFactory>().Object,
|
||||
new Mock<IEditorOperationsFactoryService>().Object));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConstructorRequiresNonEmptyPhysicalPath()
|
||||
{
|
||||
Assert.Throws<ArgumentException>("filePath",
|
||||
() => new VisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
new TestTextBuffer(null),
|
||||
CreateTemplateEngine(),
|
||||
string.Empty,
|
||||
new DefaultErrorReporter(),
|
||||
new TestCompletionBroker(),
|
||||
new Mock<VisualStudioDocumentTrackerFactory>().Object,
|
||||
new Mock<IEditorOperationsFactoryService>().Object));
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void BufferChangeStartsFullReparseIfChangeOverlapsMultipleSpans()
|
||||
[ForegroundFact]
|
||||
public void BufferChangeStartsFullReparseIfChangeOverlapsMultipleSpans()
|
||||
{
|
||||
// Arrange
|
||||
var original = new StringTextSnapshot("Foo @bar Baz");
|
||||
var testBuffer = new TestTextBuffer(original);
|
||||
using (var parser = new VisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
testBuffer,
|
||||
CreateTemplateEngine(),
|
||||
TestLinePragmaFileName,
|
||||
var documentTracker = CreateDocumentTracker(testBuffer);
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
documentTracker,
|
||||
CreateTemplateEngineFactory(),
|
||||
new DefaultErrorReporter(),
|
||||
new TestCompletionBroker(),
|
||||
new Mock<VisualStudioDocumentTrackerFactory>().Object,
|
||||
new Mock<IEditorOperationsFactoryService>().Object))
|
||||
Enumerable.Empty<IContextChangedListener>()))
|
||||
{
|
||||
parser.IdleDelay = TimeSpan.FromMilliseconds(100);
|
||||
parser.DocumentTracker_ContextChanged(null, null);
|
||||
var changed = new StringTextSnapshot("Foo @bap Daz");
|
||||
var edit = new TestEdit(7, 3, original, 3, changed, "p D");
|
||||
var parseComplete = new ManualResetEventSlim();
|
||||
|
|
@ -98,8 +69,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void AwaitPeriodInsertionAcceptedProvisionally()
|
||||
[ForegroundFact]
|
||||
public async Task AwaitPeriodInsertionAcceptedProvisionally()
|
||||
{
|
||||
// Arrange
|
||||
var original = new StringTextSnapshot("foo @await Html baz");
|
||||
|
|
@ -111,7 +82,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
manager.InitializeWithDocument(edit.OldSnapshot);
|
||||
|
||||
// Act
|
||||
manager.ApplyEditAndWaitForReparse(edit);
|
||||
await manager.ApplyEditAndWaitForReparseAsync(edit);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, manager.ParseCount);
|
||||
|
|
@ -124,8 +95,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
|
||||
{
|
||||
var factory = new SpanFactory();
|
||||
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
||||
|
|
@ -183,8 +154,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
|
||||
{
|
||||
var factory = new SpanFactory();
|
||||
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
||||
|
|
@ -234,8 +205,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertions()
|
||||
[ForegroundFact]
|
||||
public async Task ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertions()
|
||||
{
|
||||
var factory = new SpanFactory();
|
||||
var changed = new StringTextSnapshot("foo @DateT. baz");
|
||||
|
|
@ -268,7 +239,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
ApplyAndVerifyPartialChange(edit, "DateTime.");
|
||||
|
||||
// Verify the reparse finally comes
|
||||
manager.WaitForReparse();
|
||||
await manager.WaitForReparseAsync();
|
||||
|
||||
Assert.Equal(2, manager.ParseCount);
|
||||
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
|
||||
|
|
@ -280,8 +251,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertionsAfterIdentifiers()
|
||||
[ForegroundFact]
|
||||
public async Task ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertionsAfterIdentifiers()
|
||||
{
|
||||
var factory = new SpanFactory();
|
||||
var changed = new StringTextSnapshot("foo @DateTime. baz");
|
||||
|
|
@ -320,7 +291,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
ApplyAndVerifyPartialChange(edit, "DateTime.Now.");
|
||||
|
||||
// Verify the reparse eventually happens
|
||||
manager.WaitForReparse();
|
||||
await manager.WaitForReparseAsync();
|
||||
|
||||
Assert.Equal(2, manager.ParseCount);
|
||||
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
|
||||
|
|
@ -332,8 +303,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionProvisionallyAcceptsCaseInsensitiveDotlessCommitInsertions_NewRoslynIntegration()
|
||||
[ForegroundFact]
|
||||
public async Task ImplicitExpressionProvisionallyAcceptsCaseInsensitiveDotlessCommitInsertions_NewRoslynIntegration()
|
||||
{
|
||||
var factory = new SpanFactory();
|
||||
var original = new StringTextSnapshot("foo @date baz");
|
||||
|
|
@ -383,7 +354,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
ApplyAndVerifyPartialChange(() => manager.ApplyEdit(edit), "DateTime.");
|
||||
|
||||
// Verify the reparse eventually happens
|
||||
manager.WaitForReparse();
|
||||
await manager.WaitForReparseAsync();
|
||||
|
||||
Assert.Equal(2, manager.ParseCount);
|
||||
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
|
||||
|
|
@ -395,8 +366,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
|
||||
[ForegroundFact]
|
||||
public async Task ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new SpanFactory();
|
||||
|
|
@ -407,7 +378,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
manager.InitializeWithDocument(dotTyped.OldSnapshot);
|
||||
|
||||
// Apply the dot change
|
||||
manager.ApplyEditAndWaitForReparse(dotTyped);
|
||||
await manager.ApplyEditAndWaitForReparseAsync(dotTyped);
|
||||
|
||||
// Act (apply the identifier start char change)
|
||||
manager.ApplyEditAndWaitForParse(charTyped);
|
||||
|
|
@ -432,8 +403,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new SpanFactory();
|
||||
|
|
@ -463,106 +434,115 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("if");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("do");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("try");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("for");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("foreach");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("while");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("switch");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("lock");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("using");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("section");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("inherits");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("functions");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("namespace");
|
||||
}
|
||||
|
||||
// [Fact] Silent skip to avoid warnings. Skipping until we can control the parser more directly.
|
||||
private void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
|
||||
[ForegroundFact]
|
||||
public void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
|
||||
{
|
||||
RunTypeKeywordTest("class");
|
||||
}
|
||||
|
||||
private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot, int idleDelay = 50)
|
||||
private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot)
|
||||
{
|
||||
var parser = new VisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
new TestTextBuffer(originalSnapshot),
|
||||
CreateTemplateEngine(),
|
||||
TestLinePragmaFileName,
|
||||
var textBuffer = new TestTextBuffer(originalSnapshot);
|
||||
var documentTracker = CreateDocumentTracker(textBuffer);
|
||||
var templateEngineFactory = CreateTemplateEngineFactory();
|
||||
var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
documentTracker,
|
||||
templateEngineFactory,
|
||||
new DefaultErrorReporter(),
|
||||
new TestCompletionBroker(),
|
||||
new Mock<VisualStudioDocumentTrackerFactory>().Object,
|
||||
new Mock<IEditorOperationsFactoryService>().Object);
|
||||
new TestCompletionBroker(),
|
||||
Enumerable.Empty<IContextChangedListener>())
|
||||
{
|
||||
// We block idle work with the below reset events. Therefore, make tests fast and have the idle timer fire as soon as possible.
|
||||
IdleDelay = TimeSpan.FromMilliseconds(1),
|
||||
NotifyForegroundIdleStart = new ManualResetEventSlim(),
|
||||
BlockBackgroundIdleWork = new ManualResetEventSlim(),
|
||||
};
|
||||
|
||||
parser.StartParser();
|
||||
|
||||
return new TestParserManager(parser);
|
||||
}
|
||||
|
||||
private static RazorTemplateEngine CreateTemplateEngine(
|
||||
private static RazorTemplateEngineFactoryService CreateTemplateEngineFactory(
|
||||
string path = TestLinePragmaFileName,
|
||||
IEnumerable<TagHelperDescriptor> tagHelpers = null)
|
||||
{
|
||||
|
|
@ -585,7 +565,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
var templateEngine = new RazorTemplateEngine(engine, project);
|
||||
templateEngine.Options.DefaultImports = RazorSourceDocument.Create("@addTagHelper *, Test", "_TestImports.cshtml");
|
||||
return templateEngine;
|
||||
|
||||
var templateEngineFactory = Mock.Of<RazorTemplateEngineFactoryService>(
|
||||
service => service.Create(It.IsAny<string>(), It.IsAny<Action<IRazorEngineBuilder>>()) == templateEngine);
|
||||
|
||||
return templateEngineFactory;
|
||||
}
|
||||
|
||||
private void RunTypeKeywordTest(string keyword)
|
||||
|
|
@ -619,12 +603,26 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
else
|
||||
{
|
||||
#endif
|
||||
Assert.True(withTimeout((int)TimeSpan.FromSeconds(1).TotalMilliseconds), "Timeout expired!");
|
||||
Assert.True(withTimeout((int)TimeSpan.FromSeconds(5).TotalMilliseconds), "Timeout expired!");
|
||||
#if DEBUG
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static VisualStudioDocumentTracker CreateDocumentTracker(Text.ITextBuffer textBuffer)
|
||||
{
|
||||
var focusedTextView = Mock.Of<ITextView>(textView => textView.HasAggregateFocus == true);
|
||||
var documentTracker = Mock.Of<VisualStudioDocumentTracker>(tracker =>
|
||||
tracker.TextBuffer == textBuffer &&
|
||||
tracker.TextViews == new[] { focusedTextView } &&
|
||||
tracker.FilePath == TestLinePragmaFileName &&
|
||||
tracker.ProjectPath == TestProjectPath &&
|
||||
tracker.IsSupportedProject == true);
|
||||
textBuffer.Properties.AddProperty(typeof(VisualStudioDocumentTracker), documentTracker);
|
||||
|
||||
return documentTracker;
|
||||
}
|
||||
|
||||
private class TestParserManager : IDisposable
|
||||
{
|
||||
public int ParseCount;
|
||||
|
|
@ -632,22 +630,22 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
private readonly ManualResetEventSlim _parserComplete;
|
||||
private readonly ManualResetEventSlim _reparseComplete;
|
||||
private readonly TestTextBuffer _testBuffer;
|
||||
private readonly VisualStudioRazorParser _parser;
|
||||
private readonly DefaultVisualStudioRazorParser _parser;
|
||||
|
||||
public TestParserManager(VisualStudioRazorParser parser)
|
||||
public TestParserManager(DefaultVisualStudioRazorParser parser)
|
||||
{
|
||||
_parserComplete = new ManualResetEventSlim();
|
||||
_reparseComplete = new ManualResetEventSlim();
|
||||
_testBuffer = (TestTextBuffer)parser._textBuffer;
|
||||
|
||||
_testBuffer = (TestTextBuffer)parser.TextBuffer;
|
||||
ParseCount = 0;
|
||||
|
||||
// Change idle delay to be huge in order to enable us to take control of when idle methods fire.
|
||||
parser.IdleDelay = TimeSpan.FromMinutes(2);
|
||||
_parser = parser;
|
||||
parser.DocumentStructureChanged += (sender, args) =>
|
||||
{
|
||||
CurrentSyntaxTree = args.CodeDocument.GetSyntaxTree();
|
||||
|
||||
Interlocked.Increment(ref ParseCount);
|
||||
_parserComplete.Set();
|
||||
|
||||
if (args.SourceChange == null)
|
||||
{
|
||||
|
|
@ -655,7 +653,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_reparseComplete.Set();
|
||||
}
|
||||
|
||||
CurrentSyntaxTree = args.CodeDocument.GetSyntaxTree();
|
||||
_parserComplete.Set();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -680,10 +678,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
WaitForParse();
|
||||
}
|
||||
|
||||
public void ApplyEditAndWaitForReparse(TestEdit edit)
|
||||
public async Task ApplyEditAndWaitForReparseAsync(TestEdit edit)
|
||||
{
|
||||
ApplyEdit(edit);
|
||||
WaitForReparse();
|
||||
await WaitForReparseAsync();
|
||||
}
|
||||
|
||||
public void WaitForParse()
|
||||
|
|
@ -692,17 +690,26 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_parserComplete.Reset();
|
||||
}
|
||||
|
||||
public void WaitForReparse()
|
||||
public async Task WaitForReparseAsync()
|
||||
{
|
||||
Assert.True(_parser._idleTimer != null, "Expected the parser to be waiting for an idle invocation but it was not.");
|
||||
Assert.True(_parser._idleTimer != null);
|
||||
|
||||
// Allow background idle work to continue
|
||||
_parser.BlockBackgroundIdleWork.Set();
|
||||
|
||||
// Get off of the foreground thread so we can wait for the idle timer to fire
|
||||
await Task.Run(() =>
|
||||
{
|
||||
DoWithTimeoutIfNotDebugging(_parser.NotifyForegroundIdleStart.Wait);
|
||||
});
|
||||
|
||||
_parser.StopIdleTimer();
|
||||
_parser.IdleDelay = TimeSpan.FromMilliseconds(50);
|
||||
_parser.StartIdleTimer();
|
||||
DoWithTimeoutIfNotDebugging(_reparseComplete.Wait);
|
||||
_reparseComplete.Reset();
|
||||
Assert.Null(_parser._idleTimer);
|
||||
_parser.IdleDelay = TimeSpan.FromMinutes(2);
|
||||
|
||||
DoWithTimeoutIfNotDebugging(_reparseComplete.Wait);
|
||||
|
||||
_reparseComplete.Reset();
|
||||
_parser.BlockBackgroundIdleWork.Reset();
|
||||
_parser.NotifyForegroundIdleStart.Reset();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.VisualStudio.Language.Intellisense;
|
||||
using Microsoft.VisualStudio.Test;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
public class DefaultVisualStudioRazorParserTest : ForegroundDispatcherTestBase
|
||||
{
|
||||
private static VisualStudioDocumentTracker CreateDocumentTracker(bool isSupportedProject = true)
|
||||
{
|
||||
var documentTracker = Mock.Of<VisualStudioDocumentTracker>(tracker =>
|
||||
tracker.TextBuffer == new TestTextBuffer(new StringTextSnapshot(string.Empty)) &&
|
||||
tracker.ProjectPath == "SomeProject.csproj" &&
|
||||
tracker.FilePath == "SomeFilePath.cshtml" &&
|
||||
tracker.IsSupportedProject == isSupportedProject);
|
||||
|
||||
return documentTracker;
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void StartIdleTimer_DoesNotRestartTimerWhenAlreadyRunning()
|
||||
{
|
||||
// Arrange
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>())
|
||||
{
|
||||
BlockBackgroundIdleWork = new ManualResetEventSlim(),
|
||||
IdleDelay = TimeSpan.FromSeconds(5)
|
||||
})
|
||||
{
|
||||
parser.StartIdleTimer();
|
||||
using (var currentTimer = parser._idleTimer)
|
||||
{
|
||||
|
||||
// Act
|
||||
parser.StartIdleTimer();
|
||||
var afterTimer = parser._idleTimer;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(currentTimer);
|
||||
Assert.Same(currentTimer, afterTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void StopIdleTimer_StopsTimer()
|
||||
{
|
||||
// Arrange
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>())
|
||||
{
|
||||
BlockBackgroundIdleWork = new ManualResetEventSlim(),
|
||||
IdleDelay = TimeSpan.FromSeconds(5)
|
||||
})
|
||||
{
|
||||
parser.StartIdleTimer();
|
||||
var currentTimer = parser._idleTimer;
|
||||
|
||||
// Act
|
||||
parser.StopIdleTimer();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(currentTimer);
|
||||
Assert.Null(parser._idleTimer);
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void StopParser_DetachesFromTextBufferChangeLoop()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = CreateDocumentTracker();
|
||||
var textBuffer = (TestTextBuffer)documentTracker.TextBuffer;
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>()))
|
||||
{
|
||||
parser.StartParser();
|
||||
|
||||
// Act
|
||||
parser.StopParser();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(textBuffer.AttachedChangedEvents);
|
||||
Assert.Null(parser._parser);
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void StartParser_AttachesToTextBufferChangeLoop()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = CreateDocumentTracker();
|
||||
var textBuffer = (TestTextBuffer)documentTracker.TextBuffer;
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
documentTracker,
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>()))
|
||||
{
|
||||
// Act
|
||||
parser.StartParser();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, textBuffer.AttachedChangedEvents.Count);
|
||||
Assert.NotNull(parser._parser);
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void NotifyParserContextChanged_NotifiesListeners()
|
||||
{
|
||||
// Arrange
|
||||
var listener1 = new Mock<IContextChangedListener>();
|
||||
listener1.Setup(l => l.OnContextChanged(It.IsAny<VisualStudioRazorParser>()));
|
||||
var listener2 = new Mock<IContextChangedListener>();
|
||||
listener2.Setup(l => l.OnContextChanged(It.IsAny<VisualStudioRazorParser>()));
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
new[] { listener1.Object, listener2.Object }))
|
||||
{
|
||||
// Act
|
||||
parser.NotifyParserContextChanged();
|
||||
|
||||
// Assert
|
||||
listener1.Verify();
|
||||
listener2.Verify();
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void TryReinitializeParser_ReturnsTrue_IfProjectIsSupported()
|
||||
{
|
||||
// Arrange
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(isSupportedProject: true),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>()))
|
||||
{
|
||||
// Act
|
||||
var result = parser.TryReinitializeParser();
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void TryReinitializeParser_ReturnsFalse_IfProjectIsNotSupported()
|
||||
{
|
||||
// Arrange
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
CreateDocumentTracker(isSupportedProject: false),
|
||||
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<ICompletionBroker>(),
|
||||
Enumerable.Empty<IContextChangedListener>()))
|
||||
{
|
||||
// Act
|
||||
var result = parser.TryReinitializeParser();
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,13 @@ namespace Microsoft.VisualStudio.Test
|
|||
public class TestTextBuffer : ITextBuffer
|
||||
{
|
||||
private ITextSnapshot _currentSnapshot;
|
||||
private List<EventHandler<TextContentChangedEventArgs>> _attachedChangedEvents;
|
||||
|
||||
public TestTextBuffer(ITextSnapshot initialSnapshot)
|
||||
{
|
||||
_currentSnapshot = initialSnapshot;
|
||||
_attachedChangedEvents = new List<EventHandler<TextContentChangedEventArgs>>();
|
||||
|
||||
ReadOnlyRegionsChanged += (sender, args) => { };
|
||||
ChangedLowPriority += (sender, args) => { };
|
||||
ChangedHighPriority += (sender, args) => { };
|
||||
|
|
@ -39,7 +42,11 @@ namespace Microsoft.VisualStudio.Test
|
|||
|
||||
_currentSnapshot = edits[edits.Length - 1].NewSnapshot;
|
||||
|
||||
Changed?.Invoke(this, args);
|
||||
foreach (var changedEvent in AttachedChangedEvents)
|
||||
{
|
||||
changedEvent.Invoke(this, args);
|
||||
}
|
||||
|
||||
PostChanged?.Invoke(null, null);
|
||||
|
||||
ReadOnlyRegionsChanged?.Invoke(null, null);
|
||||
|
|
@ -49,12 +56,27 @@ namespace Microsoft.VisualStudio.Test
|
|||
ContentTypeChanged?.Invoke(null, null);
|
||||
}
|
||||
|
||||
public IReadOnlyList<EventHandler<TextContentChangedEventArgs>> AttachedChangedEvents => _attachedChangedEvents;
|
||||
|
||||
public ITextSnapshot CurrentSnapshot => _currentSnapshot;
|
||||
|
||||
public PropertyCollection Properties { get; }
|
||||
|
||||
public event EventHandler<SnapshotSpanEventArgs> ReadOnlyRegionsChanged;
|
||||
public event EventHandler<TextContentChangedEventArgs> Changed;
|
||||
|
||||
public event EventHandler<TextContentChangedEventArgs> Changed
|
||||
{
|
||||
add
|
||||
{
|
||||
_attachedChangedEvents.Add(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
_attachedChangedEvents.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<TextContentChangedEventArgs> ChangedLowPriority;
|
||||
public event EventHandler<TextContentChangedEventArgs> ChangedHighPriority;
|
||||
public event EventHandler<TextContentChangingEventArgs> Changing;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
// 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.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public class DefaultRazorEditorFactoryServiceTest
|
||||
{
|
||||
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 TryGetDocumentTracker_ForRazorTextBuffer_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var expectedDocumentTracker = Mock.Of<VisualStudioDocumentTracker>();
|
||||
var factoryService = CreateFactoryService(expectedDocumentTracker);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetDocumentTracker(textBuffer, out var documentTracker);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Same(expectedDocumentTracker, documentTracker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetDocumentTracker_NonRazorBuffer_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetDocumentTracker(textBuffer, out var documentTracker);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(documentTracker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_StoresTracker()
|
||||
{
|
||||
// Arrange
|
||||
var expectedDocumentTracker = Mock.Of<VisualStudioDocumentTracker>();
|
||||
var factoryService = CreateFactoryService(expectedDocumentTracker);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out VisualStudioDocumentTracker documentTracker));
|
||||
Assert.Same(expectedDocumentTracker, documentTracker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_OnlyStoresTrackerOnTextBufferOnce()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
var expectedDocumentTracker = textBuffer.Properties[typeof(VisualStudioDocumentTracker)];
|
||||
|
||||
// Create a second factory service so it generates a different tracker
|
||||
factoryService = CreateFactoryService();
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(VisualStudioDocumentTracker), out VisualStudioDocumentTracker documentTracker));
|
||||
Assert.Same(expectedDocumentTracker, documentTracker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetParser_ForRazorTextBuffer_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var expectedParser = Mock.Of<VisualStudioRazorParser>();
|
||||
var factoryService = CreateFactoryService(parser: expectedParser);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetParser(textBuffer, out var parser);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Same(expectedParser, parser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetParser_NonRazorBuffer_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetParser(textBuffer, out var parser);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(parser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_StoresParser()
|
||||
{
|
||||
// Arrange
|
||||
var expectedParser = Mock.Of<VisualStudioRazorParser>();
|
||||
var factoryService = CreateFactoryService(parser: expectedParser);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(VisualStudioRazorParser), out VisualStudioRazorParser parser));
|
||||
Assert.Same(expectedParser, parser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_OnlyStoresParserOnTextBufferOnce()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
var expectedParser = textBuffer.Properties[typeof(VisualStudioRazorParser)];
|
||||
|
||||
// Create a second factory service so it generates a different parser
|
||||
factoryService = CreateFactoryService();
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(VisualStudioRazorParser), out VisualStudioRazorParser parser));
|
||||
Assert.Same(expectedParser, parser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetSmartIndenter_ForRazorTextBuffer_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var expectedSmartIndenter = Mock.Of<BraceSmartIndenter>();
|
||||
var factoryService = CreateFactoryService(smartIndenter: expectedSmartIndenter);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetSmartIndenter(textBuffer, out var smartIndenter);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
Assert.Same(expectedSmartIndenter, smartIndenter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetSmartIndenter_NonRazorBuffer_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factoryService.TryGetSmartIndenter(textBuffer, out var smartIndenter);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
Assert.Null(smartIndenter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_StoresSmartIndenter()
|
||||
{
|
||||
// Arrange
|
||||
var expectedSmartIndenter = Mock.Of<BraceSmartIndenter>();
|
||||
var factoryService = CreateFactoryService(smartIndenter: expectedSmartIndenter);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(BraceSmartIndenter), out BraceSmartIndenter smartIndenter));
|
||||
Assert.Same(expectedSmartIndenter, smartIndenter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnsureTextBufferInitialized_OnlyStoresSmartIndenterOnTextBufferOnce()
|
||||
{
|
||||
// Arrange
|
||||
var factoryService = CreateFactoryService();
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection());
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
var expectedSmartIndenter = textBuffer.Properties[typeof(BraceSmartIndenter)];
|
||||
|
||||
// Create a second factory service so it generates a different smart indenter
|
||||
factoryService = CreateFactoryService();
|
||||
|
||||
// Act
|
||||
factoryService.EnsureTextBufferInitialized(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.True(textBuffer.Properties.TryGetProperty(typeof(BraceSmartIndenter), out BraceSmartIndenter smartIndenter));
|
||||
Assert.Same(expectedSmartIndenter, smartIndenter);
|
||||
}
|
||||
|
||||
private static DefaultRazorEditorFactoryService CreateFactoryService(
|
||||
VisualStudioDocumentTracker documentTracker = null,
|
||||
VisualStudioRazorParser parser = null,
|
||||
BraceSmartIndenter smartIndenter = null)
|
||||
{
|
||||
documentTracker = documentTracker ?? Mock.Of<VisualStudioDocumentTracker>();
|
||||
parser = parser ?? Mock.Of<VisualStudioRazorParser>();
|
||||
smartIndenter = smartIndenter ?? Mock.Of<BraceSmartIndenter>();
|
||||
|
||||
var documentTrackerFactory = Mock.Of<VisualStudioDocumentTrackerFactory>(f => f.Create(It.IsAny<ITextBuffer>()) == documentTracker);
|
||||
var parserFactory = Mock.Of<VisualStudioRazorParserFactory>(f => f.Create(It.IsAny<VisualStudioDocumentTracker>()) == parser);
|
||||
var smartIndenterFactory = Mock.Of<BraceSmartIndenterFactory>(f => f.Create(It.IsAny<VisualStudioDocumentTracker>()) == smartIndenter);
|
||||
var factoryService = new DefaultRazorEditorFactoryService(documentTrackerFactory, parserFactory, smartIndenterFactory);
|
||||
|
||||
return factoryService;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,297 +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 Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Editor.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 : ForegroundDispatcherTestBase
|
||||
{
|
||||
private static IReadOnlyList<ProjectSnapshot> Projects = new List<ProjectSnapshot>();
|
||||
|
||||
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(p => p.Projects == Projects);
|
||||
|
||||
private TextBufferProjectService ProjectService { get; } = Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
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);
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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)));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithoutTracker_CreatesTrackerAndTracksTextView_ForMultipleBuffers()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_DoesNotAddDuplicateTextViewEntry()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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(ProjectManager, 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));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBufferWithTracker_AddsEntryForADifferentTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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(ProjectManager, 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));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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(ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.TextViewsInternal.Add(textView1);
|
||||
tracker.TextViewsInternal.Add(textView2);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
tracker = new DefaultVisualStudioDocumentTracker(ProjectManager, 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));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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)));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void GetTracker_ITextBuffer_ForRazorTextBufferWithTracker_ReturnsTracker()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
|
||||
var textBuffer = 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(ProjectManager, ProjectService, Workspace, textBuffer);
|
||||
textBuffer.Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
// Act
|
||||
var result = factory.GetTracker(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.Same(tracker, result);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void GetTracker_ITextBuffer_NonRazorBuffer_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, ProjectService, Workspace);
|
||||
var textBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection());
|
||||
|
||||
// Act
|
||||
var result = factory.GetTracker(textBuffer);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void GetTracker_ITextView_ForRazorTextBufferWithTracker_ReturnsTheFirstTracker()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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(ProjectManager, 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);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void GetTracker_ITextView_WithoutRazorBuffer_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new DefaultVisualStudioDocumentTrackerFactory(Dispatcher, ProjectManager, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
// 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;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public class DefaultVisualStudioDocumentTrackerTest
|
||||
{
|
||||
private IContentType RazorContentType { get; } = Mock.Of<IContentType>(c => c.IsOfType(RazorLanguage.ContentType) == true);
|
||||
|
||||
private ITextBuffer TextBuffer => Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType);
|
||||
|
||||
private string FilePath => "C:/Some/Path/TestDocumentTracker.cshtml";
|
||||
|
||||
private ProjectSnapshotManager ProjectManager => Mock.Of<ProjectSnapshotManager>(p => p.Projects == new List<ProjectSnapshot>());
|
||||
|
||||
private TextBufferProjectService ProjectService => Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
s.IsSupportedProject(It.IsAny<IVsHierarchy>()) == true &&
|
||||
s.GetProjectPath(It.IsAny<IVsHierarchy>()) == "C:/Some/Path/TestProject.csproj");
|
||||
|
||||
private Workspace Workspace => new AdhocWorkspace();
|
||||
|
||||
[Fact]
|
||||
public void AddTextView_AddsToTextViewCollection()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView = Mock.Of<ITextView>();
|
||||
|
||||
// Act
|
||||
documentTracker.AddTextView(textView);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(documentTracker.TextViews, v => Assert.Same(v, textView));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTextView_SubscribesAfterFirstTextViewAdded()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView = Mock.Of<ITextView>();
|
||||
|
||||
// Assert - 1
|
||||
Assert.False(documentTracker.IsSupportedProject);
|
||||
|
||||
// Act
|
||||
documentTracker.AddTextView(textView);
|
||||
|
||||
// Assert - 2
|
||||
Assert.True(documentTracker.IsSupportedProject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTextView_DoesNotAddDuplicateTextViews()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView = Mock.Of<ITextView>();
|
||||
|
||||
// Act
|
||||
documentTracker.AddTextView(textView);
|
||||
documentTracker.AddTextView(textView);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(documentTracker.TextViews, v => Assert.Same(v, textView));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddTextView_AddsMultipleTextViewsToCollection()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView1 = Mock.Of<ITextView>();
|
||||
var textView2 = Mock.Of<ITextView>();
|
||||
|
||||
// Act
|
||||
documentTracker.AddTextView(textView1);
|
||||
documentTracker.AddTextView(textView2);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
documentTracker.TextViews,
|
||||
v => Assert.Same(v, textView1),
|
||||
v => Assert.Same(v, textView2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTextView_RemovesTextViewFromCollection_SingleItem()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView = Mock.Of<ITextView>();
|
||||
documentTracker.AddTextView(textView);
|
||||
|
||||
// Act
|
||||
documentTracker.RemoveTextView(textView);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(documentTracker.TextViews);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTextView_RemovesTextViewFromCollection_MultipleItems()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView1 = Mock.Of<ITextView>();
|
||||
var textView2 = Mock.Of<ITextView>();
|
||||
var textView3 = Mock.Of<ITextView>();
|
||||
documentTracker.AddTextView(textView1);
|
||||
documentTracker.AddTextView(textView2);
|
||||
documentTracker.AddTextView(textView3);
|
||||
|
||||
// Act
|
||||
documentTracker.RemoveTextView(textView2);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
documentTracker.TextViews,
|
||||
v => Assert.Same(v, textView1),
|
||||
v => Assert.Same(v, textView3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTextView_NoopsWhenRemovingTextViewNotInCollection()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView1 = Mock.Of<ITextView>();
|
||||
documentTracker.AddTextView(textView1);
|
||||
var textView2 = Mock.Of<ITextView>();
|
||||
|
||||
// Act
|
||||
documentTracker.RemoveTextView(textView2);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(documentTracker.TextViews, v => Assert.Same(v, textView1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveTextView_UnsubscribesAfterLastTextViewRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = new DefaultVisualStudioDocumentTracker(FilePath, ProjectManager, ProjectService, Workspace, TextBuffer);
|
||||
var textView1 = Mock.Of<ITextView>();
|
||||
var textView2 = Mock.Of<ITextView>();
|
||||
documentTracker.AddTextView(textView1);
|
||||
documentTracker.AddTextView(textView2);
|
||||
|
||||
// Act - 1
|
||||
documentTracker.RemoveTextView(textView1);
|
||||
|
||||
// Assert - 1
|
||||
Assert.True(documentTracker.IsSupportedProject);
|
||||
|
||||
// Act - 2
|
||||
documentTracker.RemoveTextView(textView2);
|
||||
|
||||
// Assert - 2
|
||||
Assert.False(documentTracker.IsSupportedProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
{
|
||||
// Arrange
|
||||
var expectedCodeDocument = TestRazorCodeDocument.Create("Hello World");
|
||||
var parser = new VisualStudioRazorParser(expectedCodeDocument);
|
||||
var parser = new DefaultVisualStudioRazorParser(expectedCodeDocument);
|
||||
var properties = new PropertyCollection();
|
||||
properties.AddProperty(typeof(VisualStudioRazorParser), parser);
|
||||
var textBuffer = new Mock<ITextBuffer>();
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
|||
// Arrange
|
||||
var properties = new PropertyCollection();
|
||||
var expectedCodeDocument = TestRazorCodeDocument.Create("Hello World");
|
||||
var parser = new VisualStudioRazorParser(expectedCodeDocument);
|
||||
var parser = new DefaultVisualStudioRazorParser(expectedCodeDocument);
|
||||
properties.AddProperty(typeof(VisualStudioRazorParser), parser);
|
||||
var unexpectedCodeDocument = TestRazorCodeDocument.Create("Unexpected");
|
||||
var legacyParser = new RazorEditorParser(unexpectedCodeDocument);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
|
||||
{
|
||||
public class RazorTextViewConnectionListenerTest : ForegroundDispatcherTestBase
|
||||
{
|
||||
private ProjectSnapshotManager ProjectManager { get; } = Mock.Of<ProjectSnapshotManager>(p => p.Projects == new List<ProjectSnapshot>());
|
||||
|
||||
private TextBufferProjectService ProjectService { get; } = Mock.Of<TextBufferProjectService>(
|
||||
s => s.GetHierarchy(It.IsAny<ITextBuffer>()) == Mock.Of<IVsHierarchy>() &&
|
||||
s.IsSupportedProject(It.IsAny<IVsHierarchy>()) == true &&
|
||||
s.GetProjectPath(It.IsAny<IVsHierarchy>()) == "C:/Some/Path/TestProject.csproj");
|
||||
|
||||
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);
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var editorFactoryService = new Mock<RazorEditorFactoryService>(MockBehavior.Strict);
|
||||
var factory = new RazorTextViewConnectionListener(Dispatcher, editorFactoryService.Object, Workspace);
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == NonRazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersConnected_ForRazorTextBuffer_AddsTextViewToTracker()
|
||||
{
|
||||
// Arrange
|
||||
var textView = Mock.Of<IWpfTextView>();
|
||||
var buffers = new Collection<ITextBuffer>()
|
||||
{
|
||||
Mock.Of<ITextBuffer>(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()),
|
||||
};
|
||||
VisualStudioDocumentTracker documentTracker = new DefaultVisualStudioDocumentTracker("AFile", ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
var editorFactoryService = Mock.Of<RazorEditorFactoryService>(factoryService => factoryService.TryGetDocumentTracker(It.IsAny<ITextBuffer>(), out documentTracker) == true);
|
||||
var factory = new RazorTextViewConnectionListener(Dispatcher, editorFactoryService, Workspace);
|
||||
|
||||
// Act
|
||||
factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(documentTracker.TextViews, v => Assert.Same(v, textView));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithTracker_RemovesTextView()
|
||||
{
|
||||
// Arrange
|
||||
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("C:/File/Path/To/Tracker1.cshtml", ProjectManager, ProjectService, Workspace, buffers[0]);
|
||||
tracker.AddTextView(textView1);
|
||||
tracker.AddTextView(textView2);
|
||||
buffers[0].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
|
||||
tracker = new DefaultVisualStudioDocumentTracker("C:/File/Path/To/Tracker1.cshtml", ProjectManager, ProjectService, Workspace, buffers[1]);
|
||||
tracker.AddTextView(textView1);
|
||||
tracker.AddTextView(textView2);
|
||||
buffers[1].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker);
|
||||
var factory = new RazorTextViewConnectionListener(Dispatcher, Mock.Of<RazorEditorFactoryService>(), Workspace);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new RazorTextViewConnectionListener(Dispatcher, Mock.Of<RazorEditorFactoryService>(), 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
#if RAZOR_EXTENSION_DEVELOPER_MODE
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using Microsoft.VisualStudio.ComponentModelHost;
|
||||
|
|
@ -11,6 +13,7 @@ using Microsoft.VisualStudio.Editor;
|
|||
using Microsoft.VisualStudio.Editor.Razor;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
using Microsoft.VisualStudio.Shell.Interop;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Text.Editor;
|
||||
using Microsoft.VisualStudio.TextManager.Interop;
|
||||
|
||||
|
|
@ -20,15 +23,15 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
internal class RazorDocumentInfoWindow : ToolWindowPane
|
||||
{
|
||||
private IVsEditorAdaptersFactoryService _adapterFactory;
|
||||
private VisualStudioDocumentTrackerFactory _documentTrackerService;
|
||||
private RazorEditorFactoryService _editorFactoryService;
|
||||
private IVsTextManager _textManager;
|
||||
private IVsRunningDocumentTable _rdt;
|
||||
|
||||
|
||||
private uint _cookie;
|
||||
private ITextView _textView;
|
||||
private VisualStudioDocumentTracker _documentTracker;
|
||||
|
||||
public RazorDocumentInfoWindow()
|
||||
public RazorDocumentInfoWindow()
|
||||
: base(null)
|
||||
{
|
||||
Caption = "Razor Document Info";
|
||||
|
|
@ -42,11 +45,11 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
|
||||
var component = (IComponentModel)GetService(typeof(SComponentModel));
|
||||
_adapterFactory = component.GetService<IVsEditorAdaptersFactoryService>();
|
||||
_documentTrackerService = component.GetService<VisualStudioDocumentTrackerFactory>();
|
||||
|
||||
_editorFactoryService = component.GetService<RazorEditorFactoryService>();
|
||||
|
||||
_textManager = (IVsTextManager)GetService(typeof(SVsTextManager));
|
||||
_rdt = (IVsRunningDocumentTable)GetService(typeof(SVsRunningDocumentTable));
|
||||
|
||||
|
||||
var hr = _rdt.AdviseRunningDocTableEvents(new RdtEvents(this), out uint _cookie);
|
||||
ErrorHandler.ThrowOnFailure(hr);
|
||||
}
|
||||
|
|
@ -77,7 +80,13 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
|
|||
_documentTracker.ContextChanged -= DocumentTracker_ContextChanged;
|
||||
}
|
||||
|
||||
_documentTracker = _documentTrackerService.GetTracker(textView);
|
||||
var textBuffer = textView.BufferGraph.GetRazorBuffers().FirstOrDefault();
|
||||
|
||||
if (!_editorFactoryService.TryGetDocumentTracker(textBuffer, out _documentTracker))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_documentTracker.ContextChanged += DocumentTracker_ContextChanged;
|
||||
|
||||
((FrameworkElement)Content).DataContext = new RazorDocumentInfoViewModel(_documentTracker);
|
||||
|
|
|
|||
Loading…
Reference in New Issue