private static void ExecuteAnalyzers(PreviewWorkspace previewWorkspace, ImmutableArray <DiagnosticAnalyzer> analyzers) { var analyzerOptions = new AnalyzerOptions(additionalFiles: ImmutableArray <AdditionalText> .Empty); var project = previewWorkspace.CurrentSolution.Projects.Single(); var workspaceAnalyzerOptions = new WorkspaceAnalyzerOptions(analyzerOptions, project.Solution, IdeAnalyzerOptions.Default); var compilationWithAnalyzersOptions = new CompilationWithAnalyzersOptions(workspaceAnalyzerOptions, onAnalyzerException: null, concurrentAnalysis: false, logAnalyzerExecutionTime: false); var compilation = project.GetRequiredCompilationAsync(CancellationToken.None).Result; var compilationWithAnalyzers = new CompilationWithAnalyzers(compilation, analyzers, compilationWithAnalyzersOptions); var result = compilationWithAnalyzers.GetAnalysisResultAsync(CancellationToken.None).Result; Assert.Equal(1, result.CompilationDiagnostics.Count); }
public void TestPreviewServices() { using var previewWorkspace = new PreviewWorkspace(EditorTestCompositions.EditorFeatures.GetHostServices()); var service = previewWorkspace.Services.GetService <ISolutionCrawlerRegistrationService>(); Assert.IsType <PreviewSolutionCrawlerRegistrationServiceFactory.Service>(service); var persistentService = previewWorkspace.Services.GetRequiredService <IPersistentStorageService>(); using var storage = persistentService.GetStorage(previewWorkspace.CurrentSolution); Assert.IsType <NoOpPersistentStorage>(storage); }
public void TestPreviewServices() { using var previewWorkspace = new PreviewWorkspace(VisualStudioMefHostServices.Create(EditorServicesUtil.ExportProvider)); var service = previewWorkspace.Services.GetService <ISolutionCrawlerRegistrationService>(); Assert.True(service is PreviewSolutionCrawlerRegistrationServiceFactory.Service); var persistentService = previewWorkspace.Services.GetRequiredService <IPersistentStorageService>(); using var storage = persistentService.GetStorage(previewWorkspace.CurrentSolution); Assert.True(storage is NoOpPersistentStorage); }
public void TestPreviewAddRemoveProject() { using var previewWorkspace = new PreviewWorkspace(); var solution = previewWorkspace.CurrentSolution; var project = solution.AddProject("project", "project.dll", LanguageNames.CSharp); Assert.True(previewWorkspace.TryApplyChanges(project.Solution)); var newSolution = previewWorkspace.CurrentSolution.RemoveProject(project.Id); Assert.True(previewWorkspace.TryApplyChanges(newSolution)); Assert.Equal(0, previewWorkspace.CurrentSolution.ProjectIds.Count); }
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 formattingOptions = SyntaxFormattingOptions.Create(OptionStore.GetOptions(), fallbackFormattingOptions, document.Project.LanguageServices); 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 async Task TestPreviewServices() { using var previewWorkspace = new PreviewWorkspace(EditorTestCompositions.EditorFeatures.GetHostServices()); var service = previewWorkspace.Services.GetService <ISolutionCrawlerRegistrationService>(); Assert.IsType <PreviewSolutionCrawlerRegistrationServiceFactory.Service>(service); var persistentService = previewWorkspace.Services.GetPersistentStorageService(previewWorkspace.CurrentSolution.Options); await using var storage = await persistentService.GetStorageAsync(SolutionKey.ToSolutionKey(previewWorkspace.CurrentSolution), CancellationToken.None); Assert.IsType <NoOpPersistentStorage>(storage); }
//private static List<LineSpan> GetExposedLineSpans (ITextSnapshot textSnapshot) //{ // const string start = "//["; // const string end = "//]"; // var bufferText = textSnapshot.GetText ().ToString (); // var lineSpans = new List<LineSpan> (); // var lastEndIndex = 0; // while (true) { // var startIndex = bufferText.IndexOf (start, lastEndIndex, StringComparison.Ordinal); // if (startIndex == -1) { // break; // } // var endIndex = bufferText.IndexOf (end, lastEndIndex, StringComparison.Ordinal); // var startLine = textSnapshot.GetLineNumberFromPosition (startIndex) + 1; // var endLine = textSnapshot.GetLineNumberFromPosition (endIndex); // lineSpans.Add (LineSpan.FromBounds (startLine, endLine)); // lastEndIndex = endIndex + end.Length; // } // return lineSpans; //} public void Dispose() { if (_textViewHost != null) { _textViewHost.Dispose(); _textViewHost = null; } if (curWorkspace != null) { curWorkspace.Dispose(); curWorkspace = null; } }
public IWpfDifferenceViewer CreateAddedAdditionalDocumentPreviewView(TextDocument document, double zoomLevel, CancellationToken cancellationToken) { var newBuffer = CreateNewPlainTextBuffer(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.Project.Solution.WithAdditionalDocumentText(document.Id, newBuffer.AsTextContainer().CurrentText)); rightWorkspace.OpenAdditionalDocument(document.Id); return(CreateAddedDocumentPreviewViewCore(newBuffer, rightWorkspace, document, zoomLevel, cancellationToken)); }
public async void UpdatePreview(string text) { var workspace = new PreviewWorkspace(Ide.Composition.CompositionManager.Instance.HostServices); var fileName = string.Format("project.{0}", Language == "C#" ? "csproj" : "vbproj"); 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.cs", SourceText.From(text, Encoding.UTF8)); var formatted = Formatter.FormatAsync(document, this.Options).WaitAndGetResult(CancellationToken.None); workspace.TryApplyChanges(project.Solution); TextViewHost.MimeType = "text/x-csharp"; TextViewHost.Text = (await document.GetTextAsync()).ToString(); TextViewHost.DocumentContext = new MyDocumentContext(workspace, document); TextViewHost.IsReadOnly = false; for (int i = 1; i <= TextViewHost.LineCount; i++) { var txt = TextViewHost.GetLineText(i); if (txt == "//[" || txt == "//]") { var line = TextViewHost.GetLine(i); TextViewHost.RemoveText(line.Offset, line.LengthIncludingDelimiter); i--; } } TextViewHost.IsReadOnly = true; if (curWorkspace != null) { curWorkspace.Dispose(); } this.curWorkspace = workspace; }
public Task <object> CreateAddedDocumentPreviewViewAsync(Document document, double zoomLevel, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); 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(CreateAddedDocumentPreviewViewCoreAsync(newBuffer, rightWorkspace, document, zoomLevel, cancellationToken)); }
public Task <object> CreateChangedAdditionalDocumentPreviewViewAsync(TextDocument oldDocument, TextDocument 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 = CreateNewPlainTextBuffer(oldDocument, cancellationToken); var newBuffer = CreateNewPlainTextBuffer(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. var originalSpans = GetOriginalSpans(diffResult, cancellationToken); var changedSpans = GetChangedSpans(diffResult, cancellationToken); string description = null; 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 leftDocumentId = DocumentId.CreateNewId(oldDocument.Project.Id); var leftSolution = oldDocument.Project.Solution .RemoveAdditionalDocument(oldDocument.Id) .AddAdditionalDocument(leftDocumentId, oldDocument.Name, oldBuffer.AsTextContainer().CurrentText); var leftWorkspace = new PreviewWorkspace(leftSolution); leftWorkspace.OpenAdditionalDocument(leftDocumentId); var rightWorkSpace = new PreviewWorkspace( oldDocument.Project.Solution.WithAdditionalDocumentText(oldDocument.Id, newBuffer.AsTextContainer().CurrentText)); rightWorkSpace.OpenAdditionalDocument(newDocument.Id); return(CreateChangedDocumentViewAsync( oldBuffer, newBuffer, description, originalLineSpans, changedLineSpans, leftWorkspace, rightWorkSpace, zoomLevel, cancellationToken)); }
public void TestPreviewServices() { using (var previewWorkspace = new PreviewWorkspace(MefV1HostServices.Create(EditorServicesUtil.ExportProvider.AsExportProvider()))) { var service = previewWorkspace.Services.GetService <ISolutionCrawlerRegistrationService>(); Assert.True(service is PreviewSolutionCrawlerRegistrationServiceFactory.Service); var persistentService = previewWorkspace.Services.GetService <IPersistentStorageService>(); Assert.NotNull(persistentService); var storage = persistentService.GetStorage(previewWorkspace.CurrentSolution); Assert.True(storage is NoOpPersistentStorage); } }
public void TestPreviewServices() { using (var previewWorkspace = new PreviewWorkspace(MefV1HostServices.Create(TestExportProvider.ExportProviderWithCSharpAndVisualBasic.AsExportProvider()))) { var workcoordinatorService = previewWorkspace.Services.GetService <IWorkCoordinatorRegistrationService>(); Assert.True(workcoordinatorService is PreviewWorkCoordinatorRegistrationService); var persistentService = previewWorkspace.Services.GetService <IPersistentStorageService>(); Assert.NotNull(persistentService); var storage = persistentService.GetStorage(previewWorkspace.CurrentSolution); Assert.True(storage is NoOpPersistentStorage); } }
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 TestPreviewDiagnosticTagger() { using (var workspace = CSharpWorkspaceFactory.CreateWorkspaceFromLines("class { }")) using (var previewWorkspace = new PreviewWorkspace(workspace.CurrentSolution)) { //// preview workspace and owner of the solution now share solution and its underlying text buffer var hostDocument = workspace.Projects.First().Documents.First(); //// enable preview diagnostics previewWorkspace.EnableDiagnostic(); var spans = SquiggleUtilities.GetErrorSpans(workspace); Assert.Equal(1, spans.Count); } }
public void TestPreviewWorkspaceDoesNotLeakSolution() { // Verify that analyzer execution doesn't leak solution instances from the preview workspace. var previewWorkspace = new PreviewWorkspace(); Assert.NotNull(previewWorkspace.CurrentSolution); var project = previewWorkspace.CurrentSolution.AddProject("project", "project.dll", LanguageNames.CSharp); Assert.True(previewWorkspace.TryApplyChanges(project.Solution)); var solutionObjectReference = ObjectReference.Create(previewWorkspace.CurrentSolution); var analyzers = ImmutableArray.Create<DiagnosticAnalyzer>(new CommonDiagnosticAnalyzers.NotConfigurableDiagnosticAnalyzer()); ExecuteAnalyzers(previewWorkspace, analyzers); previewWorkspace.Dispose(); solutionObjectReference.AssertReleased(); }
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); } }
private Task <object> CreateAddedTextDocumentPreviewViewAsync( TextDocument document, double zoomLevel, Func <TextDocument, CancellationToken, ITextBuffer> createBuffer, Action <Workspace, DocumentId> openTextDocument, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var newBuffer = createBuffer(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.Project.Solution.WithTextDocumentText(document.Id, newBuffer.AsTextContainer().CurrentText)); openTextDocument(rightWorkspace, document.Id); return(CreateAddedDocumentPreviewViewCoreAsync(newBuffer, rightWorkspace, document, zoomLevel, cancellationToken)); }
public async Task TestPreviewDiagnosticTagger() { using (var workspace = TestWorkspace.CreateCSharp("class { }", exportProvider: EditorServicesUtil.ExportProvider)) using (var previewWorkspace = new PreviewWorkspace(workspace.CurrentSolution)) { //// preview workspace and owner of the solution now share solution and its underlying text buffer var hostDocument = workspace.Projects.First().Documents.First(); //// enable preview diagnostics previewWorkspace.EnableDiagnostic(); var diagnosticsAndErrorsSpans = await SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync <DiagnosticsSquiggleTaggerProvider>(workspace); const string AnalzyerCount = "Analyzer Count: "; Assert.Equal(AnalzyerCount + 1, AnalzyerCount + diagnosticsAndErrorsSpans.Item1.Length); const string SquigglesCount = "Squiggles Count: "; Assert.Equal(SquigglesCount + 1, SquigglesCount + diagnosticsAndErrorsSpans.Item2.Length); } }
public async Task TestPreviewDiagnosticTagger() { using var workspace = TestWorkspace.CreateCSharp("class { }", composition: EditorTestCompositions.EditorFeatures); using var previewWorkspace = new PreviewWorkspace(workspace.CurrentSolution); // preview workspace and owner of the solution now share solution and its underlying text buffer var hostDocument = workspace.Projects.First().Documents.First(); previewWorkspace.TryApplyChanges(previewWorkspace.CurrentSolution.WithAnalyzerReferences(new[] { DiagnosticExtensions.GetCompilerDiagnosticAnalyzerReference(LanguageNames.CSharp) })); // enable preview diagnostics previewWorkspace.EnableDiagnostic(); var diagnosticsAndErrorsSpans = await SquiggleUtilities.GetDiagnosticsAndErrorSpansAsync<DiagnosticsSquiggleTaggerProvider>(workspace); const string AnalyzerCount = "Analyzer Count: "; Assert.Equal(AnalyzerCount + 1, AnalyzerCount + diagnosticsAndErrorsSpans.Item1.Length); const string SquigglesCount = "Squiggles Count: "; Assert.Equal(SquigglesCount + 1, SquigglesCount + diagnosticsAndErrorsSpans.Item2.Length); }
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 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); }
private async Task <object> CreateNewDifferenceViewerAsync(PreviewWorkspace leftWorkspace, PreviewWorkspace rightWorkspace, IProjectionBuffer originalBuffer, IProjectionBuffer changedBuffer, double zoomLevel, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // leftWorkspace can be null if the change is adding a document. // rightWorkspace can be null if the change is removing a document. // However both leftWorkspace and rightWorkspace can't be null at the same time. Contract.ThrowIfTrue((leftWorkspace == null) && (rightWorkspace == null)); var diffBuffer = _differenceBufferService.CreateDifferenceBuffer( originalBuffer, changedBuffer, new StringDifferenceOptions(), disableEditing: true); var diffViewer = _differenceViewerService.CreateDifferenceView(diffBuffer, _previewRoleSet); diffViewer.Closed += (s, e) => { if (leftWorkspace != null) { leftWorkspace.Dispose(); leftWorkspace = null; } if (rightWorkspace != null) { rightWorkspace.Dispose(); rightWorkspace = null; } }; const string DiffOverviewMarginName = "deltadifferenceViewerOverview"; if (leftWorkspace == null) { diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; diffViewer.RightView.ZoomLevel *= zoomLevel; diffViewer.RightHost.GetTextViewMargin(DiffOverviewMarginName).VisualElement.Visibility = Visibility.Collapsed; } else if (rightWorkspace == null) { diffViewer.ViewMode = DifferenceViewMode.LeftViewOnly; diffViewer.LeftView.ZoomLevel *= zoomLevel; diffViewer.LeftHost.GetTextViewMargin(DiffOverviewMarginName).VisualElement.Visibility = Visibility.Collapsed; } else { diffViewer.ViewMode = DifferenceViewMode.Inline; diffViewer.InlineView.ZoomLevel *= zoomLevel; diffViewer.InlineHost.GetTextViewMargin(DiffOverviewMarginName).VisualElement.Visibility = Visibility.Collapsed; } // Disable focus / tab stop for the diff viewer. diffViewer.RightView.VisualElement.Focusable = false; diffViewer.LeftView.VisualElement.Focusable = false; diffViewer.InlineView.VisualElement.Focusable = false; // This code path must be invoked on UI thread. AssertIsForeground(); // We use ConfigureAwait(true) to stay on the UI thread. await diffViewer.SizeToFitAsync().ConfigureAwait(true); if (leftWorkspace != null) { leftWorkspace.EnableDiagnostic(); } if (rightWorkspace != null) { rightWorkspace.EnableDiagnostic(); } return(diffViewer); }
private Task <object> CreateChangedDocumentViewAsync(ITextBuffer oldBuffer, ITextBuffer newBuffer, string description, List <LineSpan> originalSpans, List <LineSpan> changedSpans, PreviewWorkspace leftWorkspace, PreviewWorkspace rightWorkspace, double zoomLevel, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); if (!(originalSpans.Any() && changedSpans.Any())) { // Both line spans must be non-empty. Otherwise, below projection buffer factory API call will throw. // So if either is empty (signalling that there are no changes to preview in the document), then we bail out. // This can happen in cases where the user has already applied the fix and light bulb has already been dismissed, // but platform hasn't cancelled the preview operation yet. Since the light bulb has already been dismissed at // this point, the preview that we return will never be displayed to the user. So returning null here is harmless. return(SpecializedTasks.Default <object>()); } var originalBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, _editorOptionsFactoryService.GlobalOptions, oldBuffer.CurrentSnapshot, "...", description, originalSpans.ToArray()); var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, _editorOptionsFactoryService.GlobalOptions, newBuffer.CurrentSnapshot, "...", description, changedSpans.ToArray()); return(CreateNewDifferenceViewerAsync(leftWorkspace, rightWorkspace, originalBuffer, changedBuffer, zoomLevel, cancellationToken)); }
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)); }
private Task <object> CreateRemovedDocumentPreviewViewCoreAsync(ITextBuffer oldBuffer, PreviewWorkspace workspace, TextDocument document, double zoomLevel, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var firstLine = string.Format(EditorFeaturesResources.RemovingFromWithContent, document.Name, document.Project.Name); var span = new SnapshotSpan(oldBuffer.CurrentSnapshot, Span.FromBounds(0, oldBuffer.CurrentSnapshot.Length)) .CreateTrackingSpan(SpanTrackingMode.EdgeExclusive); var originalBuffer = _projectionBufferFactoryService.CreatePreviewProjectionBuffer( sourceSpans: new List <object> { firstLine, "\r\n", span }, registryService: _contentTypeRegistryService); var changedBuffer = _projectionBufferFactoryService.CreatePreviewProjectionBuffer( sourceSpans: new List <object> { firstLine, "\r\n" }, registryService: _contentTypeRegistryService); return(CreateNewDifferenceViewerAsync(workspace, null, originalBuffer, changedBuffer, zoomLevel, cancellationToken)); }
private async ValueTask <DifferenceViewerPreview?> CreateChangedDocumentViewAsync(ITextBuffer oldBuffer, ITextBuffer newBuffer, string?description, List <LineSpan> originalSpans, List <LineSpan> changedSpans, PreviewWorkspace leftWorkspace, PreviewWorkspace rightWorkspace, double zoomLevel, CancellationToken cancellationToken) { if (!(originalSpans.Any() && changedSpans.Any())) { // Both line spans must be non-empty. Otherwise, below projection buffer factory API call will throw. // So if either is empty (signaling that there are no changes to preview in the document), then we bail out. // This can happen in cases where the user has already applied the fix and light bulb has already been dismissed, // but platform hasn't cancelled the preview operation yet. Since the light bulb has already been dismissed at // this point, the preview that we return will never be displayed to the user. So returning null here is harmless. // TODO: understand how this can even happen. The diff input is stable -- we shouldn't be depending on some sort of // state that could change underneath us. If we know the file changed, how would we discover here it didn't? return(null); } // IProjectionBufferFactoryService is a Visual Studio API which is not documented as free-threaded await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var originalBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, _editorOptionsFactoryService.GlobalOptions, oldBuffer.CurrentSnapshot, "...", description, originalSpans.ToArray()); var changedBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation( _contentTypeRegistryService, _editorOptionsFactoryService.GlobalOptions, newBuffer.CurrentSnapshot, "...", description, changedSpans.ToArray()); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return(await CreateNewDifferenceViewerAsync(leftWorkspace, rightWorkspace, originalBuffer, changedBuffer, zoomLevel, cancellationToken)); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task }
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 }
private async ValueTask <DifferenceViewerPreview> CreateRemovedDocumentPreviewViewCoreAsync(ITextBuffer oldBuffer, PreviewWorkspace workspace, TextDocument document, double zoomLevel, CancellationToken cancellationToken) { // IProjectionBufferFactoryService is a Visual Studio API which is not documented as free-threaded await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var firstLine = string.Format(EditorFeaturesResources.Removing_0_from_1_with_content_colon, document.Name, document.Project.Name); var span = new SnapshotSpan(oldBuffer.CurrentSnapshot, Span.FromBounds(0, oldBuffer.CurrentSnapshot.Length)) .CreateTrackingSpan(SpanTrackingMode.EdgeExclusive); var originalBuffer = _projectionBufferFactoryService.CreatePreviewProjectionBuffer( sourceSpans: new List <object> { firstLine, "\r\n", span }, registryService: _contentTypeRegistryService); var changedBuffer = _projectionBufferFactoryService.CreatePreviewProjectionBuffer( sourceSpans: new List <object> { firstLine, "\r\n" }, registryService: _contentTypeRegistryService); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task (containing method uses JTF) return(await CreateNewDifferenceViewerAsync(workspace, null, originalBuffer, changedBuffer, zoomLevel, cancellationToken)); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task }