Made the Visual Studio parser smarter about overlapping reparse requests.
- Prior to this the parser would think that a non-latest reparse request was the latest because the only way we would check to see if a change reference was "latest" would be to do an equality check on the snapshot and `SourceChange`; the issue here was that `SourceChange` was null but the snapshots were the same. Problems could arise with this due to project context changes. - Added tests to validate the new reparse behavior. - Renamed `Edit` in the `BackgroundParser` to `ChangeReference` also refactored all the "edit" text in `BackgroundParser` to be `ChangerReference` like. - Added a new event args to be the DTO between the internal and external parser implementations. This is how we could pas additional information in order to determine "latest" change reference. #2336
This commit is contained in:
parent
a412299881
commit
7c70207594
|
|
@ -30,7 +30,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
/// <summary>
|
||||
/// Fired on the main thread.
|
||||
/// </summary>
|
||||
public event EventHandler<DocumentStructureChangedEventArgs> ResultsReady;
|
||||
public event EventHandler<BackgroundParserResultsReadyEventArgs> ResultsReady;
|
||||
|
||||
public bool IsIdle
|
||||
{
|
||||
|
|
@ -47,10 +47,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_main.Cancel();
|
||||
}
|
||||
|
||||
public void QueueChange(SourceChange change, ITextSnapshot snapshot)
|
||||
public ChangeReference QueueChange(SourceChange change, ITextSnapshot snapshot)
|
||||
{
|
||||
var edit = new Edit(change, snapshot);
|
||||
_main.QueueChange(edit);
|
||||
var changeReference = new ChangeReference(change, snapshot);
|
||||
_main.QueueChange(changeReference);
|
||||
return changeReference;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return _main.Lock();
|
||||
}
|
||||
|
||||
protected virtual void OnResultsReady(DocumentStructureChangedEventArgs args)
|
||||
protected virtual void OnResultsReady(BackgroundParserResultsReadyEventArgs args)
|
||||
{
|
||||
using (SynchronizeMainThreadState())
|
||||
{
|
||||
|
|
@ -115,7 +116,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
private string _fileName;
|
||||
private readonly object _stateLock = new object();
|
||||
private IList<Edit> _changes = new List<Edit>();
|
||||
private IList<ChangeReference> _changes = new List<ChangeReference>();
|
||||
|
||||
public MainThreadState(string fileName)
|
||||
{
|
||||
|
|
@ -124,7 +125,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
SetThreadId(Thread.CurrentThread.ManagedThreadId);
|
||||
}
|
||||
|
||||
public event EventHandler<DocumentStructureChangedEventArgs> ResultsReady;
|
||||
public event EventHandler<BackgroundParserResultsReadyEventArgs> ResultsReady;
|
||||
|
||||
public CancellationToken CancelToken
|
||||
{
|
||||
|
|
@ -154,7 +155,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return new DisposableAction(() => Monitor.Exit(_stateLock));
|
||||
}
|
||||
|
||||
public void QueueChange(Edit edit)
|
||||
public void QueueChange(ChangeReference change)
|
||||
{
|
||||
// Any thread can queue a change.
|
||||
|
||||
|
|
@ -166,7 +167,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_currentParcelCancelSource.Cancel();
|
||||
}
|
||||
|
||||
_changes.Add(edit);
|
||||
_changes.Add(change);
|
||||
_hasParcel.Set();
|
||||
}
|
||||
}
|
||||
|
|
@ -182,12 +183,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_currentParcelCancelSource = new CancellationTokenSource();
|
||||
|
||||
var changes = _changes;
|
||||
_changes = new List<Edit>();
|
||||
_changes = new List<ChangeReference>();
|
||||
return new WorkParcel(changes, _currentParcelCancelSource.Token);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReturnParcel(DocumentStructureChangedEventArgs args)
|
||||
public void ReturnParcel(BackgroundParserResultsReadyEventArgs args)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
|
|
@ -242,7 +243,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
private CancellationToken _shutdownToken;
|
||||
private RazorProjectEngine _projectEngine;
|
||||
private RazorSyntaxTree _currentSyntaxTree;
|
||||
private IList<Edit> _previouslyDiscarded = new List<Edit>();
|
||||
private IList<ChangeReference> _previouslyDiscarded = new List<ChangeReference>();
|
||||
|
||||
public BackgroundThread(MainThreadState main, RazorProjectEngine projectEngine, string filePath, string projectDirectory)
|
||||
{
|
||||
|
|
@ -274,30 +275,30 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
// Grab the parcel of work to do
|
||||
var parcel = _main.GetParcel();
|
||||
if (parcel.Edits.Any())
|
||||
if (parcel.Changes.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
DocumentStructureChangedEventArgs args = null;
|
||||
BackgroundParserResultsReadyEventArgs args = null;
|
||||
using (var linkedCancel = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken, parcel.CancelToken))
|
||||
{
|
||||
if (!linkedCancel.IsCancellationRequested)
|
||||
{
|
||||
// Collect ALL changes
|
||||
List<Edit> allEdits;
|
||||
List<ChangeReference> allChanges;
|
||||
|
||||
if (_previouslyDiscarded != null)
|
||||
{
|
||||
allEdits = Enumerable.Concat(_previouslyDiscarded, parcel.Edits).ToList();
|
||||
allChanges = Enumerable.Concat(_previouslyDiscarded, parcel.Changes).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
allEdits = parcel.Edits.ToList();
|
||||
allChanges = parcel.Changes.ToList();
|
||||
}
|
||||
|
||||
var finalEdit = allEdits.Last();
|
||||
var finalChange = allChanges.Last();
|
||||
|
||||
var results = ParseChange(finalEdit.Snapshot, linkedCancel.Token);
|
||||
var results = ParseChange(finalChange.Snapshot, linkedCancel.Token);
|
||||
|
||||
if (results != null && !linkedCancel.IsCancellationRequested)
|
||||
{
|
||||
|
|
@ -307,15 +308,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
_currentSyntaxTree = results.GetSyntaxTree();
|
||||
|
||||
// Build Arguments
|
||||
args = new DocumentStructureChangedEventArgs(
|
||||
finalEdit.Change,
|
||||
finalEdit.Snapshot,
|
||||
results);
|
||||
args = new BackgroundParserResultsReadyEventArgs(finalChange, results);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Parse completed but we were cancelled in the mean time. Add these to the discarded changes set
|
||||
_previouslyDiscarded = allEdits;
|
||||
_previouslyDiscarded = allChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -378,20 +376,20 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
private class WorkParcel
|
||||
{
|
||||
public WorkParcel(IList<Edit> changes, CancellationToken cancelToken)
|
||||
public WorkParcel(IList<ChangeReference> changes, CancellationToken cancelToken)
|
||||
{
|
||||
Edits = changes;
|
||||
Changes = changes;
|
||||
CancelToken = cancelToken;
|
||||
}
|
||||
|
||||
public CancellationToken CancelToken { get; }
|
||||
|
||||
public IList<Edit> Edits { get; }
|
||||
public IList<ChangeReference> Changes { get; }
|
||||
}
|
||||
|
||||
private class Edit
|
||||
internal class ChangeReference
|
||||
{
|
||||
public Edit(SourceChange change, ITextSnapshot snapshot)
|
||||
public ChangeReference(SourceChange change, ITextSnapshot snapshot)
|
||||
{
|
||||
Change = change;
|
||||
Snapshot = snapshot;
|
||||
|
|
@ -399,7 +397,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
|
||||
public SourceChange Change { get; }
|
||||
|
||||
public ITextSnapshot Snapshot { get; set; }
|
||||
public ITextSnapshot Snapshot { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using static Microsoft.VisualStudio.Editor.Razor.BackgroundParser;
|
||||
|
||||
namespace Microsoft.VisualStudio.Editor.Razor
|
||||
{
|
||||
internal class BackgroundParserResultsReadyEventArgs : EventArgs
|
||||
{
|
||||
public BackgroundParserResultsReadyEventArgs(ChangeReference edit, RazorCodeDocument codeDocument)
|
||||
{
|
||||
ChangeReference = edit;
|
||||
CodeDocument = codeDocument;
|
||||
}
|
||||
|
||||
public ChangeReference ChangeReference { get; }
|
||||
|
||||
public RazorCodeDocument CodeDocument { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Razor.Language.Legacy;
|
|||
using Microsoft.CodeAnalysis.Razor;
|
||||
using Microsoft.CodeAnalysis.Razor.Editor;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using static Microsoft.VisualStudio.Editor.Razor.BackgroundParser;
|
||||
using ITextBuffer = Microsoft.VisualStudio.Text.ITextBuffer;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
|
|
@ -317,8 +318,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
_dispatcher.AssertForegroundThread();
|
||||
|
||||
_latestChangeReference = new ChangeReference(change, snapshot);
|
||||
_parser.QueueChange(change, snapshot);
|
||||
_latestChangeReference = _parser.QueueChange(change, snapshot);
|
||||
}
|
||||
|
||||
private void OnNotifyForegroundIdle()
|
||||
|
|
@ -357,7 +357,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
private void OnResultsReady(object sender, DocumentStructureChangedEventArgs args)
|
||||
private void OnResultsReady(object sender, BackgroundParserResultsReadyEventArgs args)
|
||||
{
|
||||
_dispatcher.AssertBackgroundThread();
|
||||
|
||||
|
|
@ -375,21 +375,25 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return;
|
||||
}
|
||||
|
||||
var args = (DocumentStructureChangedEventArgs)state;
|
||||
var backgroundParserArgs = (BackgroundParserResultsReadyEventArgs)state;
|
||||
if (_latestChangeReference == null || // extra hardening
|
||||
!_latestChangeReference.IsAssociatedWith(args) ||
|
||||
args.Snapshot != TextBuffer.CurrentSnapshot)
|
||||
_latestChangeReference != backgroundParserArgs.ChangeReference ||
|
||||
backgroundParserArgs.ChangeReference.Snapshot != TextBuffer.CurrentSnapshot)
|
||||
{
|
||||
// In the middle of parsing a newer change or about to parse a newer change.
|
||||
return;
|
||||
}
|
||||
|
||||
_latestChangeReference = null;
|
||||
_codeDocument = args.CodeDocument;
|
||||
_snapshot = args.Snapshot;
|
||||
_codeDocument = backgroundParserArgs.CodeDocument;
|
||||
_snapshot = backgroundParserArgs.ChangeReference.Snapshot;
|
||||
_partialParser = new RazorSyntaxTreePartialParser(CodeDocument.GetSyntaxTree());
|
||||
|
||||
DocumentStructureChanged?.Invoke(this, args);
|
||||
var documentStructureChangedArgs = new DocumentStructureChangedEventArgs(
|
||||
backgroundParserArgs.ChangeReference.Change,
|
||||
backgroundParserArgs.ChangeReference.Snapshot,
|
||||
backgroundParserArgs.CodeDocument);
|
||||
DocumentStructureChanged?.Invoke(this, documentStructureChangedArgs);
|
||||
}
|
||||
|
||||
private void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
|
||||
|
|
@ -432,25 +436,5 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
return _tagHelpers;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,10 +108,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
{
|
||||
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),
|
||||
parser._latestChangeReference = new BackgroundParser.ChangeReference(null, new StringTextSnapshot(string.Empty));
|
||||
var args = new BackgroundParserResultsReadyEventArgs(
|
||||
new BackgroundParser.ChangeReference(new SourceChange(0, 0, string.Empty), new StringTextSnapshot(string.Empty)),
|
||||
TestRazorCodeDocument.CreateEmpty());
|
||||
|
||||
// Act
|
||||
|
|
@ -138,12 +137,11 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
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);
|
||||
parser._latestChangeReference = new BackgroundParser.ChangeReference(latestChange, latestSnapshot);
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()));
|
||||
var args = new DocumentStructureChangedEventArgs(
|
||||
latestChange,
|
||||
latestSnapshot,
|
||||
var args = new BackgroundParserResultsReadyEventArgs(
|
||||
parser._latestChangeReference,
|
||||
codeDocument);
|
||||
|
||||
// Act
|
||||
|
|
@ -154,6 +152,46 @@ namespace Microsoft.VisualStudio.Editor.Razor
|
|||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void OnDocumentStructureChanged_FiresForOnlyLatestTextBufferReparseEdit()
|
||||
{
|
||||
// Arrange
|
||||
var documentTracker = CreateDocumentTracker();
|
||||
using (var parser = new DefaultVisualStudioRazorParser(
|
||||
Dispatcher,
|
||||
documentTracker,
|
||||
ProjectEngineFactory,
|
||||
new DefaultErrorReporter(),
|
||||
Mock.Of<VisualStudioCompletionBroker>()))
|
||||
{
|
||||
var called = false;
|
||||
parser.DocumentStructureChanged += (sender, e) => called = true;
|
||||
var latestSnapshot = documentTracker.TextBuffer.CurrentSnapshot;
|
||||
parser._latestChangeReference = new BackgroundParser.ChangeReference(null, latestSnapshot);
|
||||
var codeDocument = TestRazorCodeDocument.CreateEmpty();
|
||||
codeDocument.SetSyntaxTree(RazorSyntaxTree.Parse(TestRazorSourceDocument.Create()));
|
||||
var badArgs = new BackgroundParserResultsReadyEventArgs(
|
||||
// This is a different reparse edit, shouldn't be fired for this call
|
||||
new BackgroundParser.ChangeReference(null, latestSnapshot),
|
||||
codeDocument);
|
||||
var goodArgs = new BackgroundParserResultsReadyEventArgs(
|
||||
parser._latestChangeReference,
|
||||
codeDocument);
|
||||
|
||||
// Act - 1
|
||||
parser.OnDocumentStructureChanged(badArgs);
|
||||
|
||||
// Assert - 1
|
||||
Assert.False(called);
|
||||
|
||||
// Act - 2
|
||||
parser.OnDocumentStructureChanged(goodArgs);
|
||||
|
||||
// Assert - 2
|
||||
Assert.True(called);
|
||||
}
|
||||
}
|
||||
|
||||
[ForegroundFact]
|
||||
public void StartIdleTimer_DoesNotRestartTimerWhenAlreadyRunning()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue