未验证 提交 97715524 编写于 作者: H Heejae Chang 提交者: GitHub

fix classification issue on F# (#33576)

* move down support syntaxtree check to where it is actually used.

* bunch of renames, but just renames. no logic changes.

I didn't clean up all mention of parsing though.

* added sanity check on giving wrong document to the service
上级 df860921
......@@ -53,8 +53,8 @@ internal partial class TagComputer
// way, when we call into the actual classification service, it should be very quick for the
// it to get the tree if it needs it.
private readonly object _gate = new object();
private ITextSnapshot _lastParsedSnapshot;
private Document _lastParsedDocument;
private ITextSnapshot _lastProcessedSnapshot;
private Document _lastProcessedDocument;
private Workspace _workspace;
private CancellationTokenSource _reportChangeCancellationSource;
......@@ -134,7 +134,7 @@ private void ResetLastParsedDocument()
{
lock (_gate)
{
_lastParsedDocument = null;
_lastProcessedDocument = null;
}
}
......@@ -155,7 +155,7 @@ private void ConnectToWorkspace(Workspace workspace)
var document = workspace.CurrentSolution.GetDocument(documentId);
if (document != null)
{
EnqueueParseSnapshotTask(document);
EnqueueProcessSnapshotAsync(document);
}
}
}
......@@ -176,31 +176,15 @@ public void DisconnectFromWorkspace()
}
}
private void EnqueueParseSnapshotTask(Document newDocument)
private void EnqueueProcessSnapshotAsync(Document newDocument)
{
// When renaming a file's extension through VS when it's opened in editor,
// the content type might change and the content type changed event can be
// raised before the renaming propagate through VS workspace. As a result,
// the document we got (based on the buffer) could still be the one in the workspace
// before rename happened. This would cause us problem if the document is supported
// by workspace but not a roslyn language (e.g. xaml, F#, etc.), since none of the roslyn
// language services would be available.
//
// If this is the case, we will not parse the snapshot. It's OK to ignore the request
// because when the buffer eventually get associated with the correct document in roslyn
// workspace, we will be invoked again.
//
// For example, if you open a xaml from from a WPF project in designer view,
// and then rename file extension from .xaml to .cs, then the document we received
// here would still belong to the special "-xaml" project.
if (newDocument != null && newDocument.SupportsSyntaxTree)
{
_workQueue.EnqueueBackgroundTask(c => this.EnqueueParseSnapshotWorkerAsync(newDocument, c), GetType() + ".EnqueueParseSnapshotTask.1", CancellationToken.None);
if (newDocument != null)
{
_workQueue.EnqueueBackgroundTask(c => this.EnqueueProcessSnapshotWorkerAsync(newDocument, c), GetType() + ".EnqueueParseSnapshotTask.1", CancellationToken.None);
}
}
private async Task EnqueueParseSnapshotWorkerAsync(Document document, CancellationToken cancellationToken)
private async Task EnqueueProcessSnapshotWorkerAsync(Document document, CancellationToken cancellationToken)
{
// we will enqueue new one soon, cancel pending refresh right away
_reportChangeCancellationSource.Cancel();
......@@ -216,11 +200,13 @@ private async Task EnqueueParseSnapshotWorkerAsync(Document document, Cancellati
}
// preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready.
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
// F#/typescript and other languages that doesn't support syntax tree will return null here.
_ = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
lock (_gate)
{
_lastParsedSnapshot = snapshot;
_lastParsedDocument = document;
_lastProcessedSnapshot = snapshot;
_lastProcessedDocument = document;
}
_reportChangeCancellationSource = new CancellationTokenSource();
......@@ -238,7 +224,7 @@ private void ReportChangedSpan(SnapshotSpan changeSpan)
{
lock (_gate)
{
var snapshot = _lastParsedSnapshot;
var snapshot = _lastProcessedSnapshot;
if (snapshot.Version.ReiteratedVersionNumber != changeSpan.Snapshot.Version.ReiteratedVersionNumber)
{
// wait for next call
......@@ -309,8 +295,8 @@ public IEnumerable<ITagSpan<IClassificationTag>> GetTags(NormalizedSnapshotSpanC
lock (_gate)
{
lastSnapshot = _lastParsedSnapshot;
lastDocument = _lastParsedDocument;
lastSnapshot = _lastProcessedSnapshot;
lastDocument = _lastProcessedDocument;
}
if (lastDocument == null)
......@@ -448,7 +434,7 @@ private void OnDocumentActiveContextChanged(object sender, DocumentActiveContext
{
if (_workspace != null && _workspace == args.Solution.Workspace)
{
ParseIfThisDocument(args.Solution, args.NewActiveContextDocumentId);
ProcessIfThisDocument(args.Solution, args.NewActiveContextDocumentId);
}
}
......@@ -456,7 +442,7 @@ private void OnDocumentOpened(object sender, DocumentEventArgs args)
{
if (_workspace != null)
{
ParseIfThisDocument(args.Document.Project.Solution, args.Document.Id);
ProcessIfThisDocument(args.Document.Project.Solution, args.Document.Id);
}
}
......@@ -485,13 +471,13 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args)
// make sure in case of parse config change, we re-colorize whole document. not just edited section.
var configChanged = !object.Equals(oldProject.ParseOptions, newProject.ParseOptions);
EnqueueParseSnapshotTask(newProject.GetDocument(documentId));
EnqueueProcessSnapshotAsync(newProject.GetDocument(documentId));
break;
}
case WorkspaceChangeKind.DocumentChanged:
{
ParseIfThisDocument(args.NewSolution, args.DocumentId);
ProcessIfThisDocument(args.NewSolution, args.DocumentId);
break;
}
}
......@@ -507,7 +493,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs args)
private async Task UpdateLastParsedDocumentAsync(Solution newSolution, CancellationToken cancellationToken)
{
// lastParsedDocument only updated in the same sequential queue so don't need lock to use it
var lastDocument = Volatile.Read(ref _lastParsedDocument);
var lastDocument = Volatile.Read(ref _lastProcessedDocument);
if (lastDocument == null)
{
return;
......@@ -549,7 +535,7 @@ private async Task UpdateLastParsedDocumentAsync(Solution newSolution, Cancellat
// update document to new snapshot with same content
lock (_gate)
{
_lastParsedDocument = document;
_lastProcessedDocument = document;
}
}
else
......@@ -559,18 +545,18 @@ private async Task UpdateLastParsedDocumentAsync(Solution newSolution, Cancellat
// or some other workspace change (say a SolutionChanged) caused a text edit to happen and we didn't process
// it directly. In that case, requeue a parse. This might be a redundant parse in the linked file case
// since we might also get a DocumentChanged event for our ID. It's fine.
ParseIfThisDocument(newSolution, document.Id);
ProcessIfThisDocument(newSolution, document.Id);
}
}
private void ParseIfThisDocument(Solution newSolution, DocumentId documentId)
private void ProcessIfThisDocument(Solution newSolution, DocumentId documentId)
{
if (_workspace != null)
{
var openDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer());
if (openDocumentId == documentId)
{
EnqueueParseSnapshotTask(newSolution.GetDocument(documentId));
EnqueueProcessSnapshotAsync(newSolution.GetDocument(documentId));
}
}
}
......
......@@ -82,6 +82,37 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification
Assert.NotNull(run)
End Sub
<WpfFact, WorkItem(13753, "https://github.com/dotnet/roslyn/issues/13753")>
Public Async Function TestWrongDocument() As Task
Dim workspaceDefinition =
<Workspace>
<Project Language="NoCompilation" AssemblyName="NoCompilationAssembly" CommonReferencesPortable="true">
<Document>
var x = {}; // e.g., TypeScript code or anything else that doesn't support compilations
</Document>
</Project>
<Project Language="C#" AssemblyName="CSharpAssembly" CommonReferencesPortable="true">
</Project>
</Workspace>
Dim exportProvider = ExportProviderCache _
.GetOrCreateExportProviderFactory(TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic()) _
.CreateExportProvider()
Using workspace = TestWorkspace.Create(workspaceDefinition, exportProvider:=exportProvider)
Dim project = workspace.CurrentSolution.Projects.First(Function(p) p.Language = LanguageNames.CSharp)
Dim classificationService = project.LanguageServices.GetService(Of IClassificationService)()
Dim wrongDocument = workspace.CurrentSolution.Projects.First(Function(p) p.Language = "NoCompilation").Documents.First()
Dim text = Await wrongDocument.GetTextAsync(CancellationToken.None)
' make sure we don't crash with wrong document
Dim result = New List(Of ClassifiedSpan)()
Await classificationService.AddSyntacticClassificationsAsync(wrongDocument, New TextSpan(0, text.Length), result, CancellationToken.None)
Await classificationService.AddSemanticClassificationsAsync(wrongDocument, New TextSpan(0, text.Length), result, CancellationToken.None)
End Using
End Function
#Disable Warning BC40000 ' Type or member is obsolete
<ExportLanguageService(GetType(IEditorClassificationService), "NoCompilation"), [Shared]>
Private Class NoCompilationEditorClassificationService
......
......@@ -19,6 +19,25 @@ internal abstract class AbstractClassificationService : IClassificationService
public async Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, List<ClassifiedSpan> result, CancellationToken cancellationToken)
{
var classificationService = document.GetLanguageService<ISyntaxClassificationService>();
if (classificationService == null)
{
// When renaming a file's extension through VS when it's opened in editor,
// the content type might change and the content type changed event can be
// raised before the renaming propagate through VS workspace. As a result,
// the document we got (based on the buffer) could still be the one in the workspace
// before rename happened. This would cause us problem if the document is supported
// by workspace but not a roslyn language (e.g. xaml, F#, etc.), since none of the roslyn
// language services would be available.
//
// If this is the case, we will simply bail out. It's OK to ignore the request
// because when the buffer eventually get associated with the correct document in roslyn
// workspace, we will be invoked again.
//
// For example, if you open a xaml from from a WPF project in designer view,
// and then rename file extension from .xaml to .cs, then the document we received
// here would still belong to the special "-xaml" project.
return;
}
var extensionManager = document.Project.Solution.Workspace.Services.GetService<IExtensionManager>();
var classifiers = classificationService.GetDefaultSyntaxClassifiers();
......@@ -35,6 +54,26 @@ public async Task AddSemanticClassificationsAsync(Document document, TextSpan te
public async Task AddSyntacticClassificationsAsync(Document document, TextSpan textSpan, List<ClassifiedSpan> result, CancellationToken cancellationToken)
{
var classificationService = document.GetLanguageService<ISyntaxClassificationService>();
if (classificationService == null)
{
// When renaming a file's extension through VS when it's opened in editor,
// the content type might change and the content type changed event can be
// raised before the renaming propagate through VS workspace. As a result,
// the document we got (based on the buffer) could still be the one in the workspace
// before rename happened. This would cause us problem if the document is supported
// by workspace but not a roslyn language (e.g. xaml, F#, etc.), since none of the roslyn
// language services would be available.
//
// If this is the case, we will simply bail out. It's OK to ignore the request
// because when the buffer eventually get associated with the correct document in roslyn
// workspace, we will be invoked again.
//
// For example, if you open a xaml from from a WPF project in designer view,
// and then rename file extension from .xaml to .cs, then the document we received
// here would still belong to the special "-xaml" project.
return;
}
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var temp = ArrayBuilder<ClassifiedSpan>.GetInstance();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册