Dispatch DocumentStructureChanged event on foreground thread.
- Had to add extra logic to track document structure changes so listeners could know if an event was on its way or not. - Added and fixed some tests. #1748
This commit is contained in:
parent
7002dbf20a
commit
a2972ebf1c
|
|
@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
using Microsoft.CodeAnalysis.Razor;
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
using Microsoft.CodeAnalysis.Razor.Editor;
|
using Microsoft.CodeAnalysis.Razor.Editor;
|
||||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
|
||||||
using Microsoft.VisualStudio.Language.Intellisense;
|
using Microsoft.VisualStudio.Language.Intellisense;
|
||||||
using Microsoft.VisualStudio.Text;
|
using Microsoft.VisualStudio.Text;
|
||||||
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
||||||
|
|
@ -27,6 +26,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
internal TimeSpan IdleDelay = TimeSpan.FromSeconds(3);
|
internal TimeSpan IdleDelay = TimeSpan.FromSeconds(3);
|
||||||
internal Timer _idleTimer;
|
internal Timer _idleTimer;
|
||||||
internal BackgroundParser _parser;
|
internal BackgroundParser _parser;
|
||||||
|
internal ChangeReference _latestChangeReference;
|
||||||
|
|
||||||
private readonly object IdleLock = new object();
|
private readonly object IdleLock = new object();
|
||||||
private readonly ICompletionBroker _completionBroker;
|
private readonly ICompletionBroker _completionBroker;
|
||||||
|
|
@ -96,6 +96,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
|
||||||
public override ITextBuffer TextBuffer => _documentTracker.TextBuffer;
|
public override ITextBuffer TextBuffer => _documentTracker.TextBuffer;
|
||||||
|
|
||||||
|
public override bool HasPendingChanges => _latestChangeReference != null;
|
||||||
|
|
||||||
// Used in unit tests to ensure we can be notified when idle starts.
|
// Used in unit tests to ensure we can be notified when idle starts.
|
||||||
internal ManualResetEventSlim NotifyForegroundIdleStart { get; set; }
|
internal ManualResetEventSlim NotifyForegroundIdleStart { get; set; }
|
||||||
|
|
||||||
|
|
@ -253,7 +255,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
// If partial parsing failed or there were outstanding parser tasks, start a full reparse
|
// If partial parsing failed or there were outstanding parser tasks, start a full reparse
|
||||||
if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected)
|
if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected)
|
||||||
{
|
{
|
||||||
_parser.QueueChange(change, snapshot);
|
QueueChange(change, snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional)
|
if ((result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional)
|
||||||
|
|
@ -292,7 +294,15 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = TextBuffer.CurrentSnapshot;
|
var snapshot = TextBuffer.CurrentSnapshot;
|
||||||
_parser.QueueChange(null, snapshot);
|
QueueChange(null, snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void QueueChange(SourceChange change, ITextSnapshot snapshot)
|
||||||
|
{
|
||||||
|
_dispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
_latestChangeReference = new ChangeReference(change, snapshot);
|
||||||
|
_parser.QueueChange(change, snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNotifyForegroundIdle()
|
private void OnNotifyForegroundIdle()
|
||||||
|
|
@ -311,7 +321,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Timer_Tick(object state)
|
private void Timer_Tick(object state)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -322,12 +332,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
StopIdleTimer();
|
StopIdleTimer();
|
||||||
|
|
||||||
// We need to get back to the UI thread to properly check if a completion is active.
|
// 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);
|
Task.Factory.StartNew(OnIdle, null, CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// This is something totally unexpected, let's just send it over to the workspace.
|
// 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);
|
Task.Factory.StartNew(() => _errorReporter.ReportError(ex), CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,19 +345,29 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
{
|
{
|
||||||
_dispatcher.AssertBackgroundThread();
|
_dispatcher.AssertBackgroundThread();
|
||||||
|
|
||||||
if (DocumentStructureChanged != null)
|
// Jump back to UI thread to notify structure changes.
|
||||||
{
|
Task.Factory.StartNew(OnDocumentStructureChanged, args, CancellationToken.None, TaskCreationOptions.None, _dispatcher.ForegroundScheduler);
|
||||||
if (args.Snapshot != TextBuffer.CurrentSnapshot)
|
}
|
||||||
{
|
|
||||||
// A different text change is being parsed.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_codeDocument = args.CodeDocument;
|
// Internal for testing
|
||||||
_snapshot = args.Snapshot;
|
internal void OnDocumentStructureChanged(object state)
|
||||||
_partialParser = new RazorSyntaxTreePartialParser(CodeDocument.GetSyntaxTree());
|
{
|
||||||
DocumentStructureChanged(this, args);
|
_dispatcher.AssertForegroundThread();
|
||||||
|
|
||||||
|
var args = (DocumentStructureChangedEventArgs)state;
|
||||||
|
if (_latestChangeReference == null || // extra hardening
|
||||||
|
!_latestChangeReference.IsAssociatedWith(args))
|
||||||
|
{
|
||||||
|
// In the middle of parsing a newer change.
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_latestChangeReference = null;
|
||||||
|
_codeDocument = args.CodeDocument;
|
||||||
|
_snapshot = args.Snapshot;
|
||||||
|
_partialParser = new RazorSyntaxTreePartialParser(CodeDocument.GetSyntaxTree());
|
||||||
|
|
||||||
|
DocumentStructureChanged?.Invoke(this, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfigureTemplateEngine(IRazorEngineBuilder builder)
|
private void ConfigureTemplateEngine(IRazorEngineBuilder builder)
|
||||||
|
|
@ -399,5 +419,25 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
return Array.Empty<TagHelperDescriptor>();
|
return Array.Empty<TagHelperDescriptor>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal for testing
|
||||||
|
internal class ChangeReference
|
||||||
|
{
|
||||||
|
public ChangeReference(SourceChange change, ITextSnapshot snapshot)
|
||||||
|
{
|
||||||
|
Change = change;
|
||||||
|
Snapshot = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SourceChange Change { get; }
|
||||||
|
|
||||||
|
public ITextSnapshot Snapshot { get; }
|
||||||
|
|
||||||
|
public bool IsAssociatedWith(DocumentStructureChangedEventArgs other)
|
||||||
|
{
|
||||||
|
return Change == other.SourceChange &&
|
||||||
|
Snapshot == other.Snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
|
||||||
public abstract ITextBuffer TextBuffer { get; }
|
public abstract ITextBuffer TextBuffer { get; }
|
||||||
|
|
||||||
|
public abstract bool HasPendingChanges { get; }
|
||||||
|
|
||||||
public abstract void QueueReparse();
|
public abstract void QueueReparse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,44 +27,30 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
private const string TestProjectPath = "C:\\This\\Path\\Is\\Just\\For\\Project.csproj";
|
private const string TestProjectPath = "C:\\This\\Path\\Is\\Just\\For\\Project.csproj";
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void BufferChangeStartsFullReparseIfChangeOverlapsMultipleSpans()
|
public async Task BufferChangeStartsFullReparseIfChangeOverlapsMultipleSpans()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var original = new StringTextSnapshot("Foo @bar Baz");
|
var original = new StringTextSnapshot("Foo @bar Baz");
|
||||||
var testBuffer = new TestTextBuffer(original);
|
var testBuffer = new TestTextBuffer(original);
|
||||||
var documentTracker = CreateDocumentTracker(testBuffer);
|
var documentTracker = CreateDocumentTracker(testBuffer);
|
||||||
using (var parser = new DefaultVisualStudioRazorParser(
|
using (var manager = CreateParserManager(original))
|
||||||
Dispatcher,
|
|
||||||
documentTracker,
|
|
||||||
CreateTemplateEngineFactory(),
|
|
||||||
new DefaultErrorReporter(),
|
|
||||||
new TestCompletionBroker()))
|
|
||||||
{
|
{
|
||||||
parser.DocumentTracker_ContextChanged(null, null);
|
|
||||||
var changed = new StringTextSnapshot("Foo @bap Daz");
|
var changed = new StringTextSnapshot("Foo @bap Daz");
|
||||||
var edit = new TestEdit(7, 3, original, 3, changed, "p D");
|
var edit = new TestEdit(7, 3, original, 3, changed, "p D");
|
||||||
var parseComplete = new ManualResetEventSlim();
|
|
||||||
var parseCount = 0;
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
parser.DocumentStructureChanged += (s, a) =>
|
|
||||||
{
|
|
||||||
Interlocked.Increment(ref parseCount);
|
|
||||||
parseComplete.Set();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act - 1
|
// Act - 1
|
||||||
testBuffer.ApplyEdit(edit);
|
await manager.ApplyEditAndWaitForParseAsync(edit);
|
||||||
DoWithTimeoutIfNotDebugging(parseComplete.Wait); // Wait for the parse to finish
|
|
||||||
|
|
||||||
// Assert - 1
|
// Assert - 1
|
||||||
Assert.Equal(1, parseCount);
|
Assert.Equal(2, manager.ParseCount);
|
||||||
parseComplete.Reset();
|
|
||||||
|
|
||||||
// Act - 2
|
// Act - 2
|
||||||
testBuffer.ApplyEdit(edit);
|
await manager.ApplyEditAndWaitForParseAsync(edit);
|
||||||
|
|
||||||
// Assert - 2
|
// Assert - 2
|
||||||
DoWithTimeoutIfNotDebugging(parseComplete.Wait);
|
Assert.Equal(3, manager.ParseCount);
|
||||||
Assert.Equal(2, parseCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +64,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
var factory = new SpanFactory();
|
var factory = new SpanFactory();
|
||||||
var changed = new StringTextSnapshot("foo @await Html. baz");
|
var changed = new StringTextSnapshot("foo @await Html. baz");
|
||||||
var edit = new TestEdit(15, 0, original, 1, changed, ".");
|
var edit = new TestEdit(15, 0, original, 1, changed, ".");
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await manager.ApplyEditAndWaitForReparseAsync(edit);
|
await manager.ApplyEditAndWaitForReparseAsync(edit);
|
||||||
|
|
@ -95,7 +81,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
|
public async Task ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
|
||||||
{
|
{
|
||||||
var factory = new SpanFactory();
|
var factory = new SpanFactory();
|
||||||
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
||||||
|
|
@ -130,7 +116,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
factory.EmptyHtml()));
|
factory.EmptyHtml()));
|
||||||
};
|
};
|
||||||
|
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
||||||
ApplyAndVerifyPartialChange(edit, "DateTime.");
|
ApplyAndVerifyPartialChange(edit, "DateTime.");
|
||||||
|
|
@ -154,7 +140,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
|
public async Task ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
|
||||||
{
|
{
|
||||||
var factory = new SpanFactory();
|
var factory = new SpanFactory();
|
||||||
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
var changed = new StringTextSnapshot("@{" + Environment.NewLine
|
||||||
|
|
@ -189,7 +175,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
factory.EmptyHtml()));
|
factory.EmptyHtml()));
|
||||||
};
|
};
|
||||||
|
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
||||||
ApplyAndVerifyPartialChange(edit, "DateT.");
|
ApplyAndVerifyPartialChange(edit, "DateT.");
|
||||||
|
|
@ -226,7 +212,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
factory.Markup(" baz")));
|
factory.Markup(" baz")));
|
||||||
};
|
};
|
||||||
|
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
||||||
ApplyAndVerifyPartialChange(edit, "DateT.");
|
ApplyAndVerifyPartialChange(edit, "DateT.");
|
||||||
|
|
@ -272,7 +258,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
factory.Markup(" baz")));
|
factory.Markup(" baz")));
|
||||||
};
|
};
|
||||||
|
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
||||||
ApplyAndVerifyPartialChange(edit, "DateTime.");
|
ApplyAndVerifyPartialChange(edit, "DateTime.");
|
||||||
|
|
@ -324,7 +310,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
factory.Markup(" baz")));
|
factory.Markup(" baz")));
|
||||||
};
|
};
|
||||||
|
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
|
||||||
|
|
||||||
|
|
@ -374,13 +360,13 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
var charTyped = new TestEdit(14, 0, new StringTextSnapshot("foo @foo. @bar"), 1, new StringTextSnapshot("foo @foo. @barb"), "b");
|
var charTyped = new TestEdit(14, 0, new StringTextSnapshot("foo @foo. @bar"), 1, new StringTextSnapshot("foo @foo. @barb"), "b");
|
||||||
using (var manager = CreateParserManager(dotTyped.OldSnapshot))
|
using (var manager = CreateParserManager(dotTyped.OldSnapshot))
|
||||||
{
|
{
|
||||||
manager.InitializeWithDocument(dotTyped.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(dotTyped.OldSnapshot);
|
||||||
|
|
||||||
// Apply the dot change
|
// Apply the dot change
|
||||||
await manager.ApplyEditAndWaitForReparseAsync(dotTyped);
|
await manager.ApplyEditAndWaitForReparseAsync(dotTyped);
|
||||||
|
|
||||||
// Act (apply the identifier start char change)
|
// Act (apply the identifier start char change)
|
||||||
manager.ApplyEditAndWaitForParse(charTyped);
|
await manager.ApplyEditAndWaitForParseAsync(charTyped);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(2, manager.ParseCount);
|
Assert.Equal(2, manager.ParseCount);
|
||||||
|
|
@ -403,7 +389,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
|
public async Task ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var factory = new SpanFactory();
|
var factory = new SpanFactory();
|
||||||
|
|
@ -411,7 +397,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
var charTyped = new TestEdit(9, 0, new StringTextSnapshot("foo @foo. bar"), 1, new StringTextSnapshot("foo @foo.b bar"), "b");
|
var charTyped = new TestEdit(9, 0, new StringTextSnapshot("foo @foo. bar"), 1, new StringTextSnapshot("foo @foo.b bar"), "b");
|
||||||
using (var manager = CreateParserManager(dotTyped.OldSnapshot))
|
using (var manager = CreateParserManager(dotTyped.OldSnapshot))
|
||||||
{
|
{
|
||||||
manager.InitializeWithDocument(dotTyped.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(dotTyped.OldSnapshot);
|
||||||
|
|
||||||
// Apply the dot change
|
// Apply the dot change
|
||||||
manager.ApplyEdit(dotTyped);
|
manager.ApplyEdit(dotTyped);
|
||||||
|
|
@ -434,87 +420,87 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("if");
|
await RunTypeKeywordTestAsync("if");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("do");
|
await RunTypeKeywordTestAsync("do");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("try");
|
await RunTypeKeywordTestAsync("try");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("for");
|
await RunTypeKeywordTestAsync("for");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("foreach");
|
await RunTypeKeywordTestAsync("foreach");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("while");
|
await RunTypeKeywordTestAsync("while");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("switch");
|
await RunTypeKeywordTestAsync("switch");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("lock");
|
await RunTypeKeywordTestAsync("lock");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("using");
|
await RunTypeKeywordTestAsync("using");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("section");
|
await RunTypeKeywordTestAsync("section");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("inherits");
|
await RunTypeKeywordTestAsync("inherits");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("functions");
|
await RunTypeKeywordTestAsync("functions");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("namespace");
|
await RunTypeKeywordTestAsync("namespace");
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
|
public async Task ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
|
||||||
{
|
{
|
||||||
RunTypeKeywordTest("class");
|
await RunTypeKeywordTestAsync("class");
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot)
|
private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot)
|
||||||
|
|
@ -570,7 +556,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
return templateEngineFactory;
|
return templateEngineFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunTypeKeywordTest(string keyword)
|
private async Task RunTypeKeywordTestAsync(string keyword)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var before = "@" + keyword.Substring(0, keyword.Length - 1);
|
var before = "@" + keyword.Substring(0, keyword.Length - 1);
|
||||||
|
|
@ -581,10 +567,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
var edit = new TestEdit(change, old, changed);
|
var edit = new TestEdit(change, old, changed);
|
||||||
using (var manager = CreateParserManager(old))
|
using (var manager = CreateParserManager(old))
|
||||||
{
|
{
|
||||||
manager.InitializeWithDocument(edit.OldSnapshot);
|
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
manager.ApplyEditAndWaitForParse(edit);
|
await manager.ApplyEditAndWaitForParseAsync(edit);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(2, manager.ParseCount);
|
Assert.Equal(2, manager.ParseCount);
|
||||||
|
|
@ -657,12 +643,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
|
||||||
public RazorSyntaxTree CurrentSyntaxTree { get; private set; }
|
public RazorSyntaxTree CurrentSyntaxTree { get; private set; }
|
||||||
|
|
||||||
public void InitializeWithDocument(ITextSnapshot snapshot)
|
public async Task InitializeWithDocumentAsync(ITextSnapshot snapshot)
|
||||||
{
|
{
|
||||||
var old = new StringTextSnapshot(string.Empty);
|
var old = new StringTextSnapshot(string.Empty);
|
||||||
var initialChange = new SourceChange(0, 0, snapshot.GetText());
|
var initialChange = new SourceChange(0, 0, snapshot.GetText());
|
||||||
var edit = new TestEdit(initialChange, old, snapshot);
|
var edit = new TestEdit(initialChange, old, snapshot);
|
||||||
ApplyEditAndWaitForParse(edit);
|
await ApplyEditAndWaitForParseAsync(edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyEdit(TestEdit edit)
|
public void ApplyEdit(TestEdit edit)
|
||||||
|
|
@ -670,10 +656,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
_testBuffer.ApplyEdit(edit);
|
_testBuffer.ApplyEdit(edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyEditAndWaitForParse(TestEdit edit)
|
public async Task ApplyEditAndWaitForParseAsync(TestEdit edit)
|
||||||
{
|
{
|
||||||
ApplyEdit(edit);
|
ApplyEdit(edit);
|
||||||
WaitForParse();
|
await WaitForParseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApplyEditAndWaitForReparseAsync(TestEdit edit)
|
public async Task ApplyEditAndWaitForReparseAsync(TestEdit edit)
|
||||||
|
|
@ -682,9 +668,14 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
await WaitForReparseAsync();
|
await WaitForReparseAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WaitForParse()
|
public async Task WaitForParseAsync()
|
||||||
{
|
{
|
||||||
DoWithTimeoutIfNotDebugging(_parserComplete.Wait); // Wait for the parse to finish
|
// Get off of the foreground thread so we can wait for the document structure changed event to fire
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
DoWithTimeoutIfNotDebugging(_parserComplete.Wait);
|
||||||
|
});
|
||||||
|
|
||||||
_parserComplete.Reset();
|
_parserComplete.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -703,7 +694,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
|
|
||||||
Assert.Null(_parser._idleTimer);
|
Assert.Null(_parser._idleTimer);
|
||||||
|
|
||||||
DoWithTimeoutIfNotDebugging(_reparseComplete.Wait);
|
// Get off of the foreground thread so we can wait for the document structure changed event to fire for reparse
|
||||||
|
await Task.Run(() =>
|
||||||
|
{
|
||||||
|
DoWithTimeoutIfNotDebugging(_reparseComplete.Wait);
|
||||||
|
});
|
||||||
|
|
||||||
_reparseComplete.Reset();
|
_reparseComplete.Reset();
|
||||||
_parser.BlockBackgroundIdleWork.Reset();
|
_parser.BlockBackgroundIdleWork.Reset();
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Microsoft.CodeAnalysis.Razor;
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
using Microsoft.VisualStudio.Language.Intellisense;
|
using Microsoft.VisualStudio.Language.Intellisense;
|
||||||
using Microsoft.VisualStudio.Test;
|
using Microsoft.VisualStudio.Test;
|
||||||
|
|
@ -26,6 +27,63 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
||||||
return documentTracker;
|
return documentTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnDocumentStructureChanged_IgnoresEditsThatAreOld()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
using (var parser = new DefaultVisualStudioRazorParser(
|
||||||
|
Dispatcher,
|
||||||
|
CreateDocumentTracker(),
|
||||||
|
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||||
|
new DefaultErrorReporter(),
|
||||||
|
Mock.Of<ICompletionBroker>()))
|
||||||
|
{
|
||||||
|
var called = false;
|
||||||
|
parser.DocumentStructureChanged += (sender, e) => called = true;
|
||||||
|
parser._latestChangeReference = new DefaultVisualStudioRazorParser.ChangeReference(null, new StringTextSnapshot(string.Empty));
|
||||||
|
var args = new DocumentStructureChangedEventArgs(
|
||||||
|
new SourceChange(0, 0, string.Empty),
|
||||||
|
new StringTextSnapshot(string.Empty),
|
||||||
|
TestRazorCodeDocument.CreateEmpty());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parser.OnDocumentStructureChanged(args);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(called);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ForegroundFact]
|
||||||
|
public void OnDocumentStructureChanged_FiresForLatestTextBufferEdit()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var documentTracker = CreateDocumentTracker();
|
||||||
|
using (var parser = new DefaultVisualStudioRazorParser(
|
||||||
|
Dispatcher,
|
||||||
|
documentTracker,
|
||||||
|
Mock.Of<RazorTemplateEngineFactoryService>(),
|
||||||
|
new DefaultErrorReporter(),
|
||||||
|
Mock.Of<ICompletionBroker>()))
|
||||||
|
{
|
||||||
|
var called = false;
|
||||||
|
parser.DocumentStructureChanged += (sender, e) => called = true;
|
||||||
|
var latestChange = new SourceChange(0, 0, string.Empty);
|
||||||
|
var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot;
|
||||||
|
parser._latestChangeReference = new DefaultVisualStudioRazorParser.ChangeReference(latestChange, latestSnapshot);
|
||||||
|
var args = new DocumentStructureChangedEventArgs(
|
||||||
|
latestChange,
|
||||||
|
latestSnapshot,
|
||||||
|
TestRazorCodeDocument.CreateEmpty());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
parser.OnDocumentStructureChanged(args);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(called);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[ForegroundFact]
|
[ForegroundFact]
|
||||||
public void StartIdleTimer_DoesNotRestartTimerWhenAlreadyRunning()
|
public void StartIdleTimer_DoesNotRestartTimerWhenAlreadyRunning()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue