// 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
        }
Ejemplo n.º 2
0
        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;
            };
        }
Ejemplo n.º 3
0
        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);
        }
Ejemplo n.º 5
0
        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
                );
        }
Ejemplo n.º 6
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));
        }
Ejemplo n.º 7
0
        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);
                }
        }
Ejemplo n.º 8
0
        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));
        }
Ejemplo n.º 9
0
        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
        }