aspnetcore/test/Microsoft.VisualStudio.Edit.../DefaultVisualStudioRazorPar...

720 lines
31 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.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;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Test;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.Editor.Razor
{
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";
[ForegroundFact]
public async Task BufferChangeStartsFullReparseIfChangeOverlapsMultipleSpans()
{
// Arrange
var original = new StringTextSnapshot("Foo @bar Baz");
var testBuffer = new TestTextBuffer(original);
var documentTracker = CreateDocumentTracker(testBuffer);
using (var manager = CreateParserManager(original))
{
var changed = new StringTextSnapshot("Foo @bap Daz");
var edit = new TestEdit(7, 3, original, 3, changed, "p D");
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// Act - 1
await manager.ApplyEditAndWaitForParseAsync(edit);
// Assert - 1
Assert.Equal(2, manager.ParseCount);
// Act - 2
await manager.ApplyEditAndWaitForParseAsync(edit);
// Assert - 2
Assert.Equal(3, manager.ParseCount);
}
}
[ForegroundFact]
public async Task AwaitPeriodInsertionAcceptedProvisionally()
{
// Arrange
var original = new StringTextSnapshot("foo @await Html baz");
using (var manager = CreateParserManager(original))
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @await Html. baz");
var edit = new TestEdit(15, 0, original, 1, changed, ".");
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// Act
await manager.ApplyEditAndWaitForReparseAsync(edit);
// Assert
Assert.Equal(2, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("await Html").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.WhiteSpace | AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". baz")));
}
}
[ForegroundFact]
public async Task ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlockAfterIdentifiers()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime." + Environment.NewLine
+ "}");
var original = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime" + Environment.NewLine
+ "}");
var edit = new TestEdit(15 + Environment.NewLine.Length, 0, original, 1, changed, ".");
using (var manager = CreateParserManager(original))
{
void ApplyAndVerifyPartialChange(TestEdit testEdit, string expectedCode)
{
manager.ApplyEdit(testEdit);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot, new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
};
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
ApplyAndVerifyPartialChange(edit, "DateTime.");
original = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime.." + Environment.NewLine
+ "}");
edit = new TestEdit(16 + Environment.NewLine.Length, 0, original, 1, changed, ".");
ApplyAndVerifyPartialChange(edit, "DateTime..");
original = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime.Now." + Environment.NewLine
+ "}");
edit = new TestEdit(16 + Environment.NewLine.Length, 0, original, 3, changed, "Now");
ApplyAndVerifyPartialChange(edit, "DateTime.Now.");
}
}
[ForegroundFact]
public async Task ImplicitExpressionAcceptsDotlessCommitInsertionsInStatementBlock()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateT." + Environment.NewLine
+ "}");
var original = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateT" + Environment.NewLine
+ "}");
var edit = new TestEdit(12 + Environment.NewLine.Length, 0, original, 1, changed, ".");
using (var manager = CreateParserManager(original))
{
void ApplyAndVerifyPartialChange(TestEdit testEdit, string expectedCode)
{
manager.ApplyEdit(testEdit);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot, new MarkupBlock(
factory.EmptyHtml(),
new StatementBlock(
factory.CodeTransition(),
factory.MetaCode("{").Accepts(AcceptedCharactersInternal.None),
factory.Code(Environment.NewLine + " ")
.AsStatement()
.AutoCompleteWith(autoCompleteString: null),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode)
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords, acceptTrailingDot: true)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Code(Environment.NewLine).AsStatement(),
factory.MetaCode("}").Accepts(AcceptedCharactersInternal.None)),
factory.EmptyHtml()));
};
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
ApplyAndVerifyPartialChange(edit, "DateT.");
original = changed;
changed = new StringTextSnapshot("@{" + Environment.NewLine
+ " @DateTime." + Environment.NewLine
+ "}");
edit = new TestEdit(12 + Environment.NewLine.Length, 0, original, 3, changed, "ime");
ApplyAndVerifyPartialChange(edit, "DateTime.");
}
}
[ForegroundFact]
public async Task ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertions()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @DateT. baz");
var original = new StringTextSnapshot("foo @DateT baz");
var edit = new TestEdit(10, 0, original, 1, changed, ".");
using (var manager = CreateParserManager(original))
{
void ApplyAndVerifyPartialChange(TestEdit testEdit, string expectedCode)
{
manager.ApplyEdit(testEdit);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
ApplyAndVerifyPartialChange(edit, "DateT.");
original = changed;
changed = new StringTextSnapshot("foo @DateTime. baz");
edit = new TestEdit(10, 0, original, 3, changed, "ime");
ApplyAndVerifyPartialChange(edit, "DateTime.");
// Verify the reparse finally comes
await manager.WaitForReparseAsync();
Assert.Equal(2, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". baz")));
}
}
[ForegroundFact]
public async Task ImplicitExpressionProvisionallyAcceptsDotlessCommitInsertionsAfterIdentifiers()
{
var factory = new SpanFactory();
var changed = new StringTextSnapshot("foo @DateTime. baz");
var original = new StringTextSnapshot("foo @DateTime baz");
var edit = new TestEdit(13, 0, original, 1, changed, ".");
using (var manager = CreateParserManager(original))
{
void ApplyAndVerifyPartialChange(TestEdit testEdit, string expectedCode)
{
manager.ApplyEdit(testEdit);
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
ApplyAndVerifyPartialChange(edit, "DateTime.");
original = changed;
changed = new StringTextSnapshot("foo @DateTime.. baz");
edit = new TestEdit(14, 0, original, 1, changed, ".");
ApplyAndVerifyPartialChange(edit, "DateTime..");
original = changed;
changed = new StringTextSnapshot("foo @DateTime.Now. baz");
edit = new TestEdit(14, 0, original, 3, changed, "Now");
ApplyAndVerifyPartialChange(edit, "DateTime.Now.");
// Verify the reparse eventually happens
await manager.WaitForReparseAsync();
Assert.Equal(2, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime.Now").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". baz")));
}
}
[ForegroundFact]
public async Task ImplicitExpressionProvisionallyAcceptsCaseInsensitiveDotlessCommitInsertions_NewRoslynIntegration()
{
var factory = new SpanFactory();
var original = new StringTextSnapshot("foo @date baz");
var changed = new StringTextSnapshot("foo @date. baz");
var edit = new TestEdit(9, 0, original, 1, changed, ".");
using (var manager = CreateParserManager(original))
{
void ApplyAndVerifyPartialChange(Action applyEdit, string expectedCode)
{
applyEdit();
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code(expectedCode).AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" baz")));
};
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// This is the process of a dotless commit when doing "." insertions to commit intellisense changes.
// @date => @date.
ApplyAndVerifyPartialChange(() => manager.ApplyEdit(edit), "date.");
original = changed;
changed = new StringTextSnapshot("foo @date baz");
edit = new TestEdit(9, 1, original, 0, changed, "");
// @date. => @date
ApplyAndVerifyPartialChange(() => manager.ApplyEdit(edit), "date");
original = changed;
changed = new StringTextSnapshot("foo @DateTime baz");
edit = new TestEdit(5, 4, original, 8, changed, "DateTime");
// @date => @DateTime
ApplyAndVerifyPartialChange(() => manager.ApplyEdit(edit), "DateTime");
original = changed;
changed = new StringTextSnapshot("foo @DateTime. baz");
edit = new TestEdit(13, 0, original, 1, changed, ".");
// @DateTime => @DateTime.
ApplyAndVerifyPartialChange(() => manager.ApplyEdit(edit), "DateTime.");
// Verify the reparse eventually happens
await manager.WaitForReparseAsync();
Assert.Equal(2, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.CurrentSyntaxTree.Root, new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("DateTime").AsImplicitExpression(CSharpCodeParser.DefaultKeywords).Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". baz")));
}
}
[ForegroundFact]
public async Task ImplicitExpressionRejectsChangeWhichWouldHaveBeenAcceptedIfLastChangeWasProvisionallyAcceptedOnDifferentSpan()
{
// Arrange
var factory = new SpanFactory();
var dotTyped = new TestEdit(8, 0, new StringTextSnapshot("foo @foo @bar"), 1, new StringTextSnapshot("foo @foo. @bar"), ".");
var charTyped = new TestEdit(14, 0, new StringTextSnapshot("foo @foo. @bar"), 1, new StringTextSnapshot("foo @foo. @barb"), "b");
using (var manager = CreateParserManager(dotTyped.OldSnapshot))
{
await manager.InitializeWithDocumentAsync(dotTyped.OldSnapshot);
// Apply the dot change
await manager.ApplyEditAndWaitForReparseAsync(dotTyped);
// Act (apply the identifier start char change)
await manager.ApplyEditAndWaitForParseAsync(charTyped);
// Assert
Assert.Equal(2, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot,
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(". "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("barb")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.EmptyHtml()));
}
}
[ForegroundFact]
public async Task ImplicitExpressionAcceptsIdentifierTypedAfterDotIfLastChangeWasProvisionalAcceptanceOfDot()
{
// Arrange
var factory = new SpanFactory();
var dotTyped = new TestEdit(8, 0, new StringTextSnapshot("foo @foo bar"), 1, new StringTextSnapshot("foo @foo. bar"), ".");
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))
{
await manager.InitializeWithDocumentAsync(dotTyped.OldSnapshot);
// Apply the dot change
manager.ApplyEdit(dotTyped);
// Act (apply the identifier start char change)
manager.ApplyEdit(charTyped);
// Assert
Assert.Equal(1, manager.ParseCount);
ParserTestBase.EvaluateParseTree(manager.PartialParsingSyntaxTreeRoot,
new MarkupBlock(
factory.Markup("foo "),
new ExpressionBlock(
factory.CodeTransition(),
factory.Code("foo.b")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharactersInternal.NonWhiteSpace)),
factory.Markup(" bar")));
}
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfIfKeywordTyped()
{
await RunTypeKeywordTestAsync("if");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfDoKeywordTyped()
{
await RunTypeKeywordTestAsync("do");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfTryKeywordTyped()
{
await RunTypeKeywordTestAsync("try");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfForKeywordTyped()
{
await RunTypeKeywordTestAsync("for");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfForEachKeywordTyped()
{
await RunTypeKeywordTestAsync("foreach");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfWhileKeywordTyped()
{
await RunTypeKeywordTestAsync("while");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfSwitchKeywordTyped()
{
await RunTypeKeywordTestAsync("switch");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfLockKeywordTyped()
{
await RunTypeKeywordTestAsync("lock");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfUsingKeywordTyped()
{
await RunTypeKeywordTestAsync("using");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfSectionKeywordTyped()
{
await RunTypeKeywordTestAsync("section");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfInheritsKeywordTyped()
{
await RunTypeKeywordTestAsync("inherits");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfFunctionsKeywordTyped()
{
await RunTypeKeywordTestAsync("functions");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfNamespaceKeywordTyped()
{
await RunTypeKeywordTestAsync("namespace");
}
[ForegroundFact]
public async Task ImplicitExpressionCorrectlyTriggersReparseIfClassKeywordTyped()
{
await RunTypeKeywordTestAsync("class");
}
private TestParserManager CreateParserManager(ITextSnapshot originalSnapshot)
{
var textBuffer = new TestTextBuffer(originalSnapshot);
var documentTracker = CreateDocumentTracker(textBuffer);
var templateEngineFactory = CreateTemplateEngineFactory();
var parser = new DefaultVisualStudioRazorParser(
Dispatcher,
documentTracker,
templateEngineFactory,
new DefaultErrorReporter(),
new TestCompletionBroker())
{
// 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 RazorTemplateEngineFactoryService CreateTemplateEngineFactory(
string path = TestLinePragmaFileName,
IEnumerable<TagHelperDescriptor> tagHelpers = null)
{
var engine = RazorEngine.CreateDesignTime(builder =>
{
RazorExtensions.Register(builder);
if (tagHelpers != null)
{
builder.AddTagHelpers(tagHelpers);
}
});
// GetImports on RazorTemplateEngine will at least check that the item exists, so we need to pretend
// that it does.
var items = new List<RazorProjectItem>();
items.Add(new TestRazorProjectItem(path));
var project = new TestRazorProject(items);
var templateEngine = new RazorTemplateEngine(engine, project);
templateEngine.Options.DefaultImports = RazorSourceDocument.Create("@addTagHelper *, Test", "_TestImports.cshtml");
var templateEngineFactory = Mock.Of<RazorTemplateEngineFactoryService>(
service => service.Create(It.IsAny<string>(), It.IsAny<Action<IRazorEngineBuilder>>()) == templateEngine);
return templateEngineFactory;
}
private async Task RunTypeKeywordTestAsync(string keyword)
{
// Arrange
var before = "@" + keyword.Substring(0, keyword.Length - 1);
var after = "@" + keyword;
var changed = new StringTextSnapshot(after);
var old = new StringTextSnapshot(before);
var change = new SourceChange(keyword.Length, 0, keyword[keyword.Length - 1].ToString());
var edit = new TestEdit(change, old, changed);
using (var manager = CreateParserManager(old))
{
await manager.InitializeWithDocumentAsync(edit.OldSnapshot);
// Act
await manager.ApplyEditAndWaitForParseAsync(edit);
// Assert
Assert.Equal(2, manager.ParseCount);
}
}
private static void DoWithTimeoutIfNotDebugging(Func<int, bool> withTimeout)
{
#if DEBUG
if (Debugger.IsAttached)
{
withTimeout(Timeout.Infinite);
}
else
{
#endif
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;
private readonly ManualResetEventSlim _parserComplete;
private readonly ManualResetEventSlim _reparseComplete;
private readonly TestTextBuffer _testBuffer;
private readonly DefaultVisualStudioRazorParser _parser;
public TestParserManager(DefaultVisualStudioRazorParser parser)
{
_parserComplete = new ManualResetEventSlim();
_reparseComplete = new ManualResetEventSlim();
_testBuffer = (TestTextBuffer)parser.TextBuffer;
ParseCount = 0;
_parser = parser;
parser.DocumentStructureChanged += (sender, args) =>
{
CurrentSyntaxTree = args.CodeDocument.GetSyntaxTree();
Interlocked.Increment(ref ParseCount);
if (args.SourceChange == null)
{
// Reparse occurred
_reparseComplete.Set();
}
_parserComplete.Set();
};
}
public RazorSyntaxTree CurrentSyntaxTree { get; private set; }
public Block PartialParsingSyntaxTreeRoot => _parser._partialParser.SyntaxTreeRoot;
public async Task InitializeWithDocumentAsync(ITextSnapshot snapshot)
{
var old = new StringTextSnapshot(string.Empty);
var initialChange = new SourceChange(0, 0, snapshot.GetText());
var edit = new TestEdit(initialChange, old, snapshot);
await ApplyEditAndWaitForParseAsync(edit);
}
public void ApplyEdit(TestEdit edit)
{
_testBuffer.ApplyEdit(edit);
}
public async Task ApplyEditAndWaitForParseAsync(TestEdit edit)
{
ApplyEdit(edit);
await WaitForParseAsync();
}
public async Task ApplyEditAndWaitForReparseAsync(TestEdit edit)
{
ApplyEdit(edit);
await WaitForReparseAsync();
}
public async Task WaitForParseAsync()
{
// 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();
}
public async Task WaitForReparseAsync()
{
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);
});
Assert.Null(_parser._idleTimer);
// 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();
_parser.BlockBackgroundIdleWork.Reset();
_parser.NotifyForegroundIdleStart.Reset();
}
public void Dispose()
{
_parser.Dispose();
}
}
private class TestCompletionBroker : VisualStudioCompletionBroker
{
public override bool IsCompletionActive(ITextView textView) => false;
}
}
}