// NOTE: We are only sharing this code between additional documents and analyzer config documents, // which are essentially plain text documents. Regular source documents need special handling // and hence have a different implementation. private async Task <DifferenceViewerPreview?> CreateChangedAdditionalOrAnalyzerConfigDocumentPreviewViewAsync( TextDocument oldDocument, TextDocument newDocument, double zoomLevel, CancellationToken cancellationToken) { Debug.Assert(oldDocument.Kind == TextDocumentKind.AdditionalDocument || oldDocument.Kind == TextDocumentKind.AnalyzerConfigDocument); // openTextDocument must be called from the main thread await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Note: We don't use the original buffer that is associated with oldDocument // (and currently open in the editor) for oldBuffer below. This is because oldBuffer // will be used inside a projection buffer inside our inline diff preview below // and platform's implementation currently has a bug where projection buffers // are being leaked. This leak means that if we use the original buffer that is // currently visible in the editor here, the projection buffer span calculation // would be triggered every time user changes some code in this buffer (even though // the diff view would long have been dismissed by the time user edits the code) // resulting in crashes. Instead we create a new buffer from the same content. // TODO: We could use ITextBufferCloneService instead here to clone the original buffer. #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) var oldBuffer = await CreateNewPlainTextBufferAsync(oldDocument, cancellationToken); var newBuffer = await CreateNewPlainTextBufferAsync(newDocument, cancellationToken); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task // Convert the diffs to be line based. // Compute the diffs between the old text and the new. var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken); // Need to show the spans in the right that are different. var originalSpans = GetOriginalSpans(diffResult, cancellationToken); var changedSpans = GetChangedSpans(diffResult, cancellationToken); var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken); var changedLineSpans = CreateLineSpans(newBuffer.CurrentSnapshot, changedSpans, cancellationToken); // TODO: Why aren't we attaching conflict / warning annotations here like we do for regular documents above? // Create PreviewWorkspaces around the buffers to be displayed on the left and right // so that all IDE services (colorizer, squiggles etc.) light up in these buffers. var leftWorkspace = new PreviewWorkspace(oldDocument.Project.Solution); leftWorkspace.OpenDocument(oldDocument.Id, oldBuffer.AsTextContainer()); var rightWorkspace = new PreviewWorkspace(newDocument.Project.Solution); rightWorkspace.OpenDocument(newDocument.Id, newBuffer.AsTextContainer()); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return(await CreateChangedDocumentViewAsync( oldBuffer, newBuffer, description : null, originalLineSpans, changedLineSpans, leftWorkspace, rightWorkspace, zoomLevel, cancellationToken)); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task }
public void UpdatePreview(string text) { var service = VisualStudioMefHostServices.Create(_componentModel.GetService <ExportProvider>()); var workspace = new PreviewWorkspace(service); var fileName = "project." + (Language == "C#" ? "csproj" : "vbproj"); var project = workspace.CurrentSolution.AddProject(fileName, "assembly.dll", Language); // use the mscorlib, system, and system.core that are loaded in the current process. string[] references = { "mscorlib", "System", "System.Core" }; var metadataService = workspace.Services.GetService <IMetadataService>(); var referenceAssemblies = Thread.GetDomain().GetAssemblies() .Where(x => references.Contains(x.GetName(true).Name, StringComparer.OrdinalIgnoreCase)) .Select(a => metadataService.GetReference(a.Location, MetadataReferenceProperties.Assembly)); project = project.WithMetadataReferences(referenceAssemblies); var document = project.AddDocument("document", SourceText.From(text, Encoding.UTF8)); var fallbackFormattingOptions = _globalOptions.GetSyntaxFormattingOptions(document.Project.LanguageServices); var optionService = workspace.Services.GetRequiredService <IOptionService>(); var configOptions = OptionStore.GetOptions().AsAnalyzerConfigOptions(optionService, document.Project.Language); var formattingService = document.GetRequiredLanguageService <ISyntaxFormattingService>(); var formattingOptions = formattingService.GetFormattingOptions(configOptions, fallbackFormattingOptions); var formatted = Formatter.FormatAsync(document, formattingOptions, CancellationToken.None).WaitAndGetResult(CancellationToken.None); var textBuffer = _textBufferFactoryService.CreateTextBuffer(formatted.GetTextSynchronously(CancellationToken.None).ToString(), _contentType); var container = textBuffer.AsTextContainer(); var projection = _projectionBufferFactory.CreateProjectionBufferWithoutIndentation(_contentTypeRegistryService, _editorOptions.CreateOptions(), textBuffer.CurrentSnapshot, separator: "", exposedLineSpans: GetExposedLineSpans(textBuffer.CurrentSnapshot).ToArray()); var textView = _textEditorFactoryService.CreateTextView(projection, _textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive)); this.TextViewHost = _textEditorFactoryService.CreateTextViewHost(textView, setFocus: false); workspace.TryApplyChanges(document.Project.Solution); workspace.OpenDocument(document.Id, container); this.TextViewHost.Closed += (s, a) => { workspace.Dispose(); workspace = null; }; }
public IWpfDifferenceViewer CreateAddedDocumentPreviewView(Document document, double zoomLevel, CancellationToken cancellationToken) { var newBuffer = CreateNewBuffer(document, cancellationToken); // Create PreviewWorkspace around the buffer to be displayed in the diff preview // so that all IDE services (colorizer, squiggles etc.) light up in this buffer. var rightWorkspace = new PreviewWorkspace( document.WithText(newBuffer.AsTextContainer().CurrentText).Project.Solution); rightWorkspace.OpenDocument(document.Id); return(CreateAddedDocumentPreviewViewCore(newBuffer, rightWorkspace, document, zoomLevel, cancellationToken)); }
public void UpdatePreview(string text) { const string start = "//["; const string end = "//]"; var service = MefV1HostServices.Create(_componentModel.DefaultExportProvider); var workspace = new PreviewWorkspace(service); var document = workspace.OpenDocument(DocumentId.CreateNewId("document"), SourceText.From(text), Language); var formatted = Formatter.FormatAsync(document, this.Options).WaitAndGetResult(CancellationToken.None); var textBuffer = _textBufferFactoryService.CreateTextBuffer(formatted.SourceText.ToString(), _contentType); var container = textBuffer.AsTextContainer(); var documentBackedByTextBuffer = document.WithText(container.CurrentText); var bufferText = textBuffer.CurrentSnapshot.GetText().ToString(); var startIndex = bufferText.IndexOf(start, StringComparison.Ordinal); var endIndex = bufferText.IndexOf(end, StringComparison.Ordinal); var startLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(startIndex) + 1; var endLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(endIndex); var projection = _projectionBufferFactory.CreateProjectionBufferWithoutIndentation(_contentTypeRegistryService, _editorOptions.CreateOptions(), textBuffer.CurrentSnapshot, "", LineSpan.FromBounds(startLine, endLine)); var textView = _textEditorFactoryService.CreateTextView(projection, _textEditorFactoryService.CreateTextViewRoleSet()); this.TextViewHost = _textEditorFactoryService.CreateTextViewHost(textView, setFocus: false); workspace.CloseDocument(document.Id); workspace.OpenDocument(document.Id, documentBackedByTextBuffer.SourceText, Language); //workspace.UpdateDocument(documentBackedByTextBuffer.Id, documentBackedByTextBuffer.SourceText); }
public async Task TestPreviewDiagnostic() { var hostServices = EditorTestCompositions.EditorFeatures.GetHostServices(); var diagnosticService = (IDiagnosticUpdateSource)( (IMefHostExportProvider)hostServices ).GetExportedValue <IDiagnosticAnalyzerService>(); RoslynDebug.AssertNotNull(diagnosticService); var taskSource = new TaskCompletionSource <DiagnosticsUpdatedArgs>(); diagnosticService.DiagnosticsUpdated += (s, a) => taskSource.TrySetResult(a); using var previewWorkspace = new PreviewWorkspace(hostServices); var solution = previewWorkspace.CurrentSolution .WithAnalyzerReferences( new[] { DiagnosticExtensions.GetCompilerDiagnosticAnalyzerReference( LanguageNames.CSharp ) } ) .AddProject("project", "project.dll", LanguageNames.CSharp) .AddDocument("document", "class { }").Project.Solution; Assert.True(previewWorkspace.TryApplyChanges(solution)); var document = previewWorkspace.CurrentSolution.Projects.First().Documents.Single(); previewWorkspace.OpenDocument(document.Id, (await document.GetTextAsync()).Container); previewWorkspace.EnableDiagnostic(); // wait 20 seconds taskSource.Task.Wait(20000); Assert.True(taskSource.Task.IsCompleted); var args = taskSource.Task.Result; Assert.True( args.GetPushDiagnostics( previewWorkspace, InternalDiagnosticsOptions.NormalDiagnosticMode ).Length > 0 ); }
public void TestPreviewOpenCloseFile() { using var previewWorkspace = new PreviewWorkspace(); var solution = previewWorkspace.CurrentSolution; var project = solution.AddProject("project", "project.dll", LanguageNames.CSharp); var document = project.AddDocument("document", ""); Assert.True(previewWorkspace.TryApplyChanges(document.Project.Solution)); previewWorkspace.OpenDocument(document.Id); Assert.Equal(1, previewWorkspace.GetOpenDocumentIds().Count()); Assert.True(previewWorkspace.IsDocumentOpen(document.Id)); previewWorkspace.CloseDocument(document.Id); Assert.Equal(0, previewWorkspace.GetOpenDocumentIds().Count()); Assert.False(previewWorkspace.IsDocumentOpen(document.Id)); }
public void TestPreviewDiagnosticTagger() { using (var workspace = CSharpWorkspaceFactory.CreateWorkspaceFromLines("class { }")) using (var previewWorkspace = new PreviewWorkspace(workspace.CurrentSolution)) { // set up to listen diagnostic changes so that we can wait until it happens var diagnosticService = workspace.ExportProvider.GetExportedValue <IDiagnosticService>() as DiagnosticService; var taskSource = new TaskCompletionSource <DiagnosticsUpdatedArgs>(); diagnosticService.DiagnosticsUpdated += (s, a) => taskSource.TrySetResult(a); // preview workspace and owner of the solution now share solution and its underlying text buffer var hostDocument = workspace.Projects.First().Documents.First(); var buffer = hostDocument.GetTextBuffer(); // enable preview diagnostics previewWorkspace.OpenDocument(hostDocument.Id); previewWorkspace.EnableDiagnostic(); var foregroundService = new TestForegroundNotificationService(); var optionsService = workspace.Services.GetService <IOptionService>(); var squiggleWaiter = new ErrorSquiggleWaiter(); // create a tagger for preview workspace var taggerSource = new DiagnosticsSquiggleTaggerProvider.TagSource(buffer, foregroundService, diagnosticService, optionsService, squiggleWaiter); // wait up to 20 seconds for diagnostic service taskSource.Task.Wait(20000); if (!taskSource.Task.IsCompleted) { // something is wrong FatalError.Report(new System.Exception("not finished after 20 seconds")); } // wait for tagger squiggleWaiter.CreateWaitTask().PumpingWait(); var snapshot = buffer.CurrentSnapshot; var intervalTree = taggerSource.GetTagIntervalTreeForBuffer(buffer); var spans = intervalTree.GetIntersectingSpans(new SnapshotSpan(snapshot, 0, snapshot.Length)); taggerSource.TestOnly_Dispose(); Assert.Equal(1, spans.Count); } }
public DisposableToolTip CreateDisposableToolTip(Document document, ITextBuffer textBuffer, Span contentSpan, object backgroundResourceKey) { var control = CreateViewHostingControl(textBuffer, contentSpan); // Create the actual tooltip around the region of that text buffer we want to show. var toolTip = new ToolTip { Content = control, Background = (Brush)Application.Current.Resources[backgroundResourceKey] }; // Create a preview workspace for this text buffer and open it's corresponding // document. // // our underlying preview tagger and mechanism to attach tagger to associated buffer of // opened document will light up automatically var workspace = new PreviewWorkspace(document.Project.Solution); workspace.OpenDocument(document.Id, textBuffer.AsTextContainer()); return(new DisposableToolTip(toolTip, workspace)); }
public Task <object> CreateChangedDocumentPreviewViewAsync(Document oldDocument, Document newDocument, double zoomLevel, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // Note: We don't use the original buffer that is associated with oldDocument // (and currently open in the editor) for oldBuffer below. This is because oldBuffer // will be used inside a projection buffer inside our inline diff preview below // and platform's implementation currently has a bug where projection buffers // are being leaked. This leak means that if we use the original buffer that is // currently visible in the editor here, the projection buffer span calculation // would be triggered every time user changes some code in this buffer (even though // the diff view would long have been dismissed by the time user edits the code) // resulting in crashes. Instead we create a new buffer from the same content. // TODO: We could use ITextBufferCloneService instead here to clone the original buffer. var oldBuffer = CreateNewBuffer(oldDocument, cancellationToken); var newBuffer = CreateNewBuffer(newDocument, cancellationToken); // Convert the diffs to be line based. // Compute the diffs between the old text and the new. var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken); // Need to show the spans in the right that are different. // We also need to show the spans that are in conflict. var originalSpans = GetOriginalSpans(diffResult, cancellationToken); var changedSpans = GetChangedSpans(diffResult, cancellationToken); var newRoot = newDocument.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken); var conflictNodes = newRoot.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind); var conflictSpans = conflictNodes.Select(n => n.Span.ToSpan()).ToList(); var conflictDescriptions = conflictNodes.SelectMany(n => n.GetAnnotations(ConflictAnnotation.Kind)) .Select(a => ConflictAnnotation.GetDescription(a)) .Distinct(); var warningNodes = newRoot.GetAnnotatedNodesAndTokens(WarningAnnotation.Kind); var warningSpans = warningNodes.Select(n => n.Span.ToSpan()).ToList(); var warningDescriptions = warningNodes.SelectMany(n => n.GetAnnotations(WarningAnnotation.Kind)) .Select(a => WarningAnnotation.GetDescription(a)) .Distinct(); AttachConflictAndWarningAnnotationToBuffer(newBuffer, conflictSpans, warningSpans); var description = conflictSpans.Count == 0 && warningSpans.Count == 0 ? null : string.Join(Environment.NewLine, conflictDescriptions.Concat(warningDescriptions)); var allSpans = new NormalizedSpanCollection(conflictSpans.Concat(warningSpans).Concat(changedSpans)); var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken); var changedLineSpans = CreateLineSpans(newBuffer.CurrentSnapshot, allSpans, cancellationToken); if (!originalLineSpans.Any()) { // This means that we have no differences (likely because of conflicts). // In such cases, use the same spans for the left (old) buffer as the right (new) buffer. originalLineSpans = changedLineSpans; } // Create PreviewWorkspaces around the buffers to be displayed on the left and right // so that all IDE services (colorizer, squiggles etc.) light up in these buffers. var leftDocument = oldDocument.Project .RemoveDocument(oldDocument.Id) .AddDocument(oldDocument.Name, oldBuffer.AsTextContainer().CurrentText); var leftWorkspace = new PreviewWorkspace(leftDocument.Project.Solution); leftWorkspace.OpenDocument(leftDocument.Id); var rightWorkspace = new PreviewWorkspace( oldDocument.WithText(newBuffer.AsTextContainer().CurrentText).Project.Solution); rightWorkspace.OpenDocument(newDocument.Id); return(CreateChangedDocumentViewAsync( oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans, leftWorkspace, rightWorkspace, zoomLevel, cancellationToken)); }
public async Task <DifferenceViewerPreview?> CreateChangedDocumentPreviewViewAsync(Document oldDocument, Document newDocument, double zoomLevel, CancellationToken cancellationToken) { // CreateNewBufferAsync must be called from the main thread await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // Note: We don't use the original buffer that is associated with oldDocument // (and currently open in the editor) for oldBuffer below. This is because oldBuffer // will be used inside a projection buffer inside our inline diff preview below // and platform's implementation currently has a bug where projection buffers // are being leaked. This leak means that if we use the original buffer that is // currently visible in the editor here, the projection buffer span calculation // would be triggered every time user changes some code in this buffer (even though // the diff view would long have been dismissed by the time user edits the code) // resulting in crashes. Instead we create a new buffer from the same content. // TODO: We could use ITextBufferCloneService instead here to clone the original buffer. #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) var oldBuffer = await CreateNewBufferAsync(oldDocument, cancellationToken); var newBuffer = await CreateNewBufferAsync(newDocument, cancellationToken); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task // Convert the diffs to be line based. // Compute the diffs between the old text and the new. var diffResult = ComputeEditDifferences(oldDocument, newDocument, cancellationToken); // Need to show the spans in the right that are different. // We also need to show the spans that are in conflict. var originalSpans = GetOriginalSpans(diffResult, cancellationToken); var changedSpans = GetChangedSpans(diffResult, cancellationToken); string?description = null; NormalizedSpanCollection allSpans; if (newDocument.SupportsSyntaxTree) { #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) var newRoot = await newDocument.GetRequiredSyntaxRootAsync(cancellationToken); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task var conflictNodes = newRoot.GetAnnotatedNodesAndTokens(ConflictAnnotation.Kind); var conflictSpans = conflictNodes.Select(n => n.Span.ToSpan()).ToList(); var conflictDescriptions = conflictNodes.SelectMany(n => n.GetAnnotations(ConflictAnnotation.Kind)) .Select(a => $"❌ {ConflictAnnotation.GetDescription(a)}") .Distinct(); var warningNodes = newRoot.GetAnnotatedNodesAndTokens(WarningAnnotation.Kind); var warningSpans = warningNodes.Select(n => n.Span.ToSpan()).ToList(); var warningDescriptions = warningNodes.SelectMany(n => n.GetAnnotations(WarningAnnotation.Kind)) .Select(a => $"⚠ {WarningAnnotation.GetDescription(a)}") .Distinct(); var suppressDiagnosticsNodes = newRoot.GetAnnotatedNodesAndTokens(SuppressDiagnosticsAnnotation.Kind); var suppressDiagnosticsSpans = suppressDiagnosticsNodes.Select(n => n.Span.ToSpan()).ToList(); AttachAnnotationsToBuffer(newBuffer, conflictSpans, warningSpans, suppressDiagnosticsSpans); description = conflictSpans.Count == 0 && warningSpans.Count == 0 ? null : string.Join(Environment.NewLine, conflictDescriptions.Concat(warningDescriptions)); allSpans = new NormalizedSpanCollection(conflictSpans.Concat(warningSpans).Concat(changedSpans)); } else { allSpans = new NormalizedSpanCollection(changedSpans); } var originalLineSpans = CreateLineSpans(oldBuffer.CurrentSnapshot, originalSpans, cancellationToken); var changedLineSpans = CreateLineSpans(newBuffer.CurrentSnapshot, allSpans, cancellationToken); if (!originalLineSpans.Any()) { // This means that we have no differences (likely because of conflicts). // In such cases, use the same spans for the left (old) buffer as the right (new) buffer. originalLineSpans = changedLineSpans; } // Create PreviewWorkspaces around the buffers to be displayed on the left and right // so that all IDE services (colorizer, squiggles etc.) light up in these buffers. var leftWorkspace = new PreviewWorkspace(oldDocument.Project.Solution); leftWorkspace.OpenDocument(oldDocument.Id, oldBuffer.AsTextContainer()); var rightWorkspace = new PreviewWorkspace(newDocument.Project.Solution); rightWorkspace.OpenDocument(newDocument.Id, newBuffer.AsTextContainer()); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return(await CreateChangedDocumentViewAsync( oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans, leftWorkspace, rightWorkspace, zoomLevel, cancellationToken)); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task }