From e7d7a542d5630c366cd7875c15b64e3270980a05 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Tue, 18 Aug 2020 18:32:13 -0700 Subject: [PATCH] Enable go to definition for source generated files This allows go to definition (and similar features) to open the outputs from a source generator as a "virtual" file. The file updates as changes happen in near-real-time. This commit is ignoring for now the problem of how we want to represent source generated files as Documents in the Workspace; this isn't a big deal bit a helper method (TryGetGeneratorAndHint) is added to easily check if a SyntaxTree came from a generator, and if so gives you the information you need to locate the generated file in a later run of the generator. The majority of this pull request doesn't really change once we have that working, so this isn't throw-away work. It does mean though that we don't have a good way to link the generated file back up to the generating project, so you only get a Miscellanous Files experience in the generated document for now. This is also doing a somewhat icky hack where we still have to put an empty file onto disk, and fill it in once the file is open. The challenge being that Visual Studio really doesn't give us any way to have a moniker that isn't a file that passes through the system at this point. --- docs/ide/test-plans/source-generators.md | 34 ++ .../InternalUtilities/EnumerableExtensions.cs | 11 + .../IDefinitionsAndReferencesFactory.cs | 17 + .../Workspace/SourceGeneratedFileManager.cs | 423 ++++++++++++++++++ .../VisualStudioSymbolNavigationService.cs | 14 + .../Core/Def/ServicesVSResources.resx | 16 + .../Core/Def/xlf/ServicesVSResources.cs.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.de.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.es.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.fr.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.it.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.ja.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.ko.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.pl.xlf | 25 ++ .../Def/xlf/ServicesVSResources.pt-BR.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.ru.xlf | 25 ++ .../Core/Def/xlf/ServicesVSResources.tr.xlf | 25 ++ .../Def/xlf/ServicesVSResources.zh-Hans.xlf | 25 ++ .../Def/xlf/ServicesVSResources.zh-Hant.xlf | 25 ++ .../GeneratorDriverRunResultExtensions.cs | 41 ++ 20 files changed, 881 insertions(+) create mode 100644 docs/ide/test-plans/source-generators.md create mode 100644 src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs create mode 100644 src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs diff --git a/docs/ide/test-plans/source-generators.md b/docs/ide/test-plans/source-generators.md new file mode 100644 index 00000000000..969370755a3 --- /dev/null +++ b/docs/ide/test-plans/source-generators.md @@ -0,0 +1,34 @@ +## Features Opening New Windows + +1. Locate a use of a generated symbol in the project, and invoke Go to Definition. + - [ ] It works. +2. Locate a use of a generated symbol in the project, and invoke Go to Implementation. + - [ ] It works. +3. Locate a use of a generated symbol in the project, and invoke Find References. + - [ ] References to the generated symbol in regular code are located. + - [ ] References to the generated symbol in generated code are located. + +## Features That Are Blocked + +1. Locate a use of a generated method in a project, and invoke Rename with Ctrl+R Ctrl+R. + - [ ] It should be blocked. +2. Locate a use of a generated method in a project, change the name at the use site. + - [ ] **[NOT WORKING YET]** Rename tracking should not trigger. + +## Navigating within a Generated Document + +1. Invoke Go to Definition on a generated symbol to open a source generated file. + - [ ] **[NOT WORKING YET]** The navigation bar on the top of the file shows the project that contains the generated source +2. Invoke Find References on the symbol we navigated to. + - [ ] **[NOT WORKING YET]** The original reference should appear in find references. +3. Click on some symbol used more than once in a generated file. + - [ ] Highlight references should highlight all uses in the file. + +## Window > New Window Support + +1. Invoke Go to Definition to open a source-generated file. Then do Window > New Window to ensure that the new window is set up correctly. + - [ ] It's read only. + - [ ] The title and caption are correct. +2. Remove the source generator from the project. + - [ ] Confirm the first window correctly gets an info bar showing the file is no longer valid. + - [ ] Confirm the second window correctly gets an info bar showing the file is no longer valid. diff --git a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs index a4e82f2b8da..9928a48d8af 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/EnumerableExtensions.cs @@ -169,6 +169,17 @@ public static IReadOnlyCollection ToCollection(this IEnumerable sequenc return source.Cast().LastOrDefault(); } + public static T? SingleOrNull(this IEnumerable source, Func predicate) + where T : struct + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source.Cast().SingleOrDefault(v => predicate(v!.Value)); + } + public static bool IsSingle(this IEnumerable list) { using var enumerator = list.GetEnumerator(); diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 3b6ca9de3d8..f8727cf83a2 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages @@ -146,6 +147,22 @@ internal static class DefinitionItemExtensions sourceLocations.Add(documentLocation); } + else + { + // Was this a source generated tree? If so, we don't have a document representaion (yet) so + // we'll create a metadata symbol which will later be handled by the symbol navigation service + // that way. Once we represent generated source trees as propery documents, we'll update the code above + // to correctly make this item. + var project = solution.GetOriginatingProject(definition); + var generatorRunResult = await project.GetGeneratorDriverRunResultAsync(cancellationToken).ConfigureAwait(false); + + if (generatorRunResult.TryGetGeneratorAndHint(location.SourceTree, out _, out _)) + { + return DefinitionItem.CreateMetadataDefinition( + tags, displayParts, nameDisplayParts, solution, + definition, properties, displayIfNoReferences); + } + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs new file mode 100644 index 00000000000..cfde05f4b6a --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Threading; +using Roslyn.Utilities; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation +{ + /// + /// Provides the support for opening files pointing to source generated documents, and keeping the content updated accordingly. + /// + [Export(typeof(SourceGeneratedFileManager))] + internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventListener + { + private readonly IServiceProvider _serviceProvider; + private readonly IThreadingContext _threadingContext; + private readonly IVsRunningDocumentTable _runningDocumentTable; + private readonly ITextDocumentFactoryService _textDocumentFactoryService; + + private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; + + /// + /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. + /// + private readonly string _temporaryDirectory; + + /// + /// Map of currently open generated files; the key is the generated full file path. + /// + private readonly Dictionary _openFiles = new Dictionary(); + private readonly VisualStudioWorkspace _visualStudioWorkspace; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public SourceGeneratedFileManager( + [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, + IThreadingContext threadingContext, + IVsEditorAdaptersFactoryService editorAdaptersFactoryService, + ITextDocumentFactoryService textDocumentFactoryService, + VisualStudioWorkspace visualStudioWorkspace) + { + _serviceProvider = serviceProvider; + _threadingContext = threadingContext; + _textDocumentFactoryService = textDocumentFactoryService; + _temporaryDirectory = Path.Combine(Path.GetTempPath(), "VisualStudioSourceGeneratedDocuments"); + _visualStudioWorkspace = visualStudioWorkspace; + + Directory.CreateDirectory(_temporaryDirectory); + + // The IVsRunningDocumentTable is a free-threaded VS service that allows fetching of the service and advising events + // to be done without implicitly marshalling to the UI thread. + _runningDocumentTable = _serviceProvider.GetService(); + _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker( + threadingContext, + editorAdaptersFactoryService, + _runningDocumentTable, + this); + } + + public void NavigateToSourceGeneratedFile(Project project, ISourceGenerator generator, string generatedSourceHintName, TextSpan sourceSpan) + { + // We will create an file name to represent this generated file; the Visual Studio shell APIs imply you can use a URI, + // but most URIs are blocked other than file:// and http://; they also get extra handling to attempt to download the file so + // those aren't really usable anyways. + + // We put all the files related to the same project in a single directory + var generatorType = generator.GetType(); + var projectDirectory = Path.Combine(_temporaryDirectory, project.Id.Id.ToString()); + Directory.CreateDirectory(projectDirectory); + + // The file name we generate here is chosen to match the compiler's choice, so the debugger can recognize the files should match. + // This can only be changed if the compiler changes the algorithm as well. + var temporaryFilePath = Path.Combine(projectDirectory, $"{generatorType.Module.ModuleVersionId}_{generatorType.FullName}_{generatedSourceHintName}"); + File.WriteAllText(temporaryFilePath, ""); + + var openDocumentService = _serviceProvider.GetService(); + var hr = openDocumentService.OpenDocumentViaProject( + temporaryFilePath, + VSConstants.LOGVIEWID.TextView_guid, + out _, + out _, + out _, + out var windowFrame); + + if (ErrorHandler.Succeeded(hr) && windowFrame != null) + { + windowFrame.Show(); + } + } + + public bool TryParseGeneratedFilePath( + string filePath, + [NotNullWhen(true)] out ProjectId? projectId, + [NotNullWhen(true)] out string? generatorTypeName, + [NotNullWhen(true)] out string? generatedSourceHintName) + { + if (!filePath.StartsWith(_temporaryDirectory)) + { + projectId = null; + generatorTypeName = null; + generatedSourceHintName = null; + return false; + } + + string fileName = Path.GetFileName(filePath); + string[] parts = fileName.Split(new[] { '_' }, count: 3); + + generatorTypeName = parts[1]; + generatedSourceHintName = parts[2]; + + projectId = ProjectId.CreateFromSerialized(Guid.Parse(new FileInfo(filePath).Directory.Name)); + + return true; + } + + void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy? hierarchy, IVsWindowFrame? windowFrame) + { + if (TryParseGeneratedFilePath(moniker, out var projectId, out var generatorTypeName, out var generatedSourceHintName)) + { + // Attach to the text buffer if we haven't already + if (!_openFiles.TryGetValue(moniker, out OpenSourceGeneratedFile openFile)) + { + openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, projectId, generatorTypeName, generatedSourceHintName, _threadingContext); + _openFiles.Add(moniker, openFile); + _threadingContext.JoinableTaskFactory.Run(() => openFile.UpdateBufferContentsAsync(CancellationToken.None)); + + // Update the RDT flags to ensure the file can't be saved or appears in any MRUs as it's a temporary generated file name. + var cookie = ((IVsRunningDocumentTable4)_runningDocumentTable).GetDocumentCookie(moniker); + ErrorHandler.ThrowOnFailure(_runningDocumentTable.ModifyDocumentFlags(cookie, (uint)(_VSRDTFLAGS.RDT_CantSave | _VSRDTFLAGS.RDT_DontAddToMRU), fSet: 1)); + } + + if (windowFrame != null) + { + openFile.SetWindowFrame(windowFrame); + } + } + } + + void IRunningDocumentTableEventListener.OnCloseDocument(string moniker) + { + if (_openFiles.TryGetValue(moniker, out var openFile)) + { + openFile.Dispose(); + _openFiles.Remove(moniker); + } + } + + void IRunningDocumentTableEventListener.OnRefreshDocumentContext(string moniker, IVsHierarchy hierarchy) + { + } + + void IRunningDocumentTableEventListener.OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer textBuffer) + { + } + + private class OpenSourceGeneratedFile : ForegroundThreadAffinitizedObject, IDisposable + { + private readonly SourceGeneratedFileManager _fileManager; + private readonly ITextBuffer _textBuffer; + private readonly Workspace _workspace; + private readonly ProjectId _projectId; + private readonly string _generatorTypeName; + private readonly string _generatedSourceHintName; + + /// + /// A read-only region that we create across the entire file to prevent edits unless we are the one making them. + /// It's a dynamic read-only region that will allow edits if is set. + /// + private readonly IReadOnlyRegion _readOnlyRegion; + private bool _updatingBuffer = false; + + /// + /// A cancellation token used for any background updating of this file; this is cancelled on the UI thread + /// when the file is closed. + /// + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + + /// + /// A queue used to batch updates to the file. + /// + private readonly AsyncBatchingDelay _batchingWorkQueue; + + /// + /// The of the active window. This may be null if we're in the middle of construction and + /// we haven't been given it yet. + /// + private IVsWindowFrame? _windowFrame; + + private string? _windowFrameMessageToShow = null; + private ImageMoniker _windowFrameImageMonikerToShow = default; + private string? _currentWindowFrameMessage = null; + private ImageMoniker _currentWindowFrameImageMoniker = default; + private IVsInfoBarUIElement? _currentWindowFrameInfoBarElement = null; + + public OpenSourceGeneratedFile(SourceGeneratedFileManager fileManager, ITextBuffer textBuffer, Workspace workspace, ProjectId projectId, string generatorTypeName, string generatedSourceHintName, IThreadingContext threadingContext) + : base(threadingContext, assertIsForeground: true) + { + _fileManager = fileManager; + _textBuffer = textBuffer; + _workspace = workspace; + _projectId = projectId; + _generatorTypeName = generatorTypeName; + _generatedSourceHintName = generatedSourceHintName; + + // We'll create a read-only region for the file, but it'll be a dynamic region we can temporarily suspend + // while we're doing edits. + using (var readOnlyRegionEdit = _textBuffer.CreateReadOnlyRegionEdit()) + { + _readOnlyRegion = readOnlyRegionEdit.CreateDynamicReadOnlyRegion( + _textBuffer.CurrentSnapshot.GetFullSpan(), + SpanTrackingMode.EdgeInclusive, + EdgeInsertionMode.Deny, + callback: _ => !_updatingBuffer); + + readOnlyRegionEdit.Apply(); + } + + _workspace.WorkspaceChanged += OnWorkspaceChanged; + + _batchingWorkQueue = new AsyncBatchingDelay( + TimeSpan.FromSeconds(1), + UpdateBufferContentsAsync, + asyncListener: null, + _cancellationTokenSource.Token); + } + + public void Dispose() + { + using (var readOnlyRegionEdit = _textBuffer.CreateReadOnlyRegionEdit()) + { + readOnlyRegionEdit.RemoveReadOnlyRegion(_readOnlyRegion); + readOnlyRegionEdit.Apply(); + } + + _workspace.WorkspaceChanged -= OnWorkspaceChanged; + + // Cancel any remaining asynchronous work we may have had to update this file + _cancellationTokenSource.Cancel(); + } + + public async Task UpdateBufferContentsAsync(CancellationToken cancellationToken) + { + GeneratedSourceResult? generatedSource = null; + var project = _workspace.CurrentSolution.GetProject(_projectId); + + // Locals correspond to the equivalently-named fields; we'll assign these and then assign to the fields while on the + // UI thread to avoid any potential race where we update the InfoBar while this is running. + string? windowFrameMessageToShow; + ImageMoniker windowFrameImageMonikerToShow; + + if (project == null) + { + windowFrameMessageToShow = "The project no longer exists."; + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + + var generatorDriverRunResults = await project.GetGeneratorDriverRunResultAsync(cancellationToken).ConfigureAwait(false); + var generatorRunResult = generatorDriverRunResults?.Results.SingleOrNull(r => r.Generator.GetType().FullName == _generatorTypeName); + + if (generatorRunResult == null) + { + windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_been_removed_from_the_project, _generatorTypeName); + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + generatedSource = generatorRunResult.Value.GeneratedSources.SingleOrNull(r => r.HintName == _generatedSourceHintName); + + if (generatedSource == null) + { + windowFrameMessageToShow = string.Format(ServicesVSResources.The_generator_0_that_generated_this_file_has_stopped_generating_this_file, _generatorTypeName); + windowFrameImageMonikerToShow = KnownMonikers.StatusError; + } + else + { + windowFrameMessageToShow = string.Format(ServicesVSResources.This_file_is_autogenerated_by_0_and_cannot_be_edited, _generatorTypeName); + windowFrameImageMonikerToShow = default; + } + } + } + + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + _windowFrameMessageToShow = windowFrameMessageToShow; + _windowFrameImageMonikerToShow = windowFrameImageMonikerToShow; + + // Update the text if we have new text + if (generatedSource.HasValue) + { + try + { + // Allow us to do our own edits + _updatingBuffer = true; + + // Ensure the encoding matches; this is necessary for debugger checksums to match what is in the PDB. + if (_fileManager._textDocumentFactoryService.TryGetTextDocument(_textBuffer, out var textDocument)) + { + textDocument.Encoding = generatedSource.Value.SourceText.Encoding; + } + + // We'll ask the editor to do the diffing for us so updates don't refresh the entire buffer + using (var edit = _textBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) + { + // TODO: make the edit in some nicer way than creating a massive string + edit.Replace(startPosition: 0, _textBuffer.CurrentSnapshot.Length, generatedSource.Value.SourceText.ToString()); + edit.Apply(); + } + } + finally + { + _updatingBuffer = false; + } + } + + // Update the InfoBar either way + EnsureWindowFrameInfoBarUpdated(); + } + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) + { + var oldProject = e.OldSolution.GetProject(_projectId); + var newProject = e.NewSolution.GetProject(_projectId); + + if (oldProject != null && newProject != null) + { + // We'll start this work asynchronously to figure out if we need to change; if the file is closed the cancellationToken + // is triggered and this will no-op. + Task.Run(async () => + { + if (await oldProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false) != + await newProject.GetDependentVersionAsync(_cancellationTokenSource.Token).ConfigureAwait(false)) + { + _batchingWorkQueue.RequeueWork(); + } + }, _cancellationTokenSource.Token); + } + } + + internal void SetWindowFrame(IVsWindowFrame windowFrame) + { + AssertIsForeground(); + + if (_windowFrame != null) + { + // We already have a window frame, and we don't expect to get a second one + return; + } + + _windowFrame = windowFrame; + + // We'll override the window frame and never show it as dirty, even if there's an underlying edit + windowFrame.SetProperty((int)__VSFPROPID2.VSFPROPID_OverrideDirtyState, false); + windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideCaption, _generatedSourceHintName + " " + ServicesVSResources.generated_suffix); + windowFrame.SetProperty((int)__VSFPROPID5.VSFPROPID_OverrideToolTip, _generatedSourceHintName + " " + string.Format(ServicesVSResources.generated_by_0_suffix, _generatorTypeName)); + + EnsureWindowFrameInfoBarUpdated(); + } + + private void EnsureWindowFrameInfoBarUpdated() + { + AssertIsForeground(); + + if (_windowFrameMessageToShow == null || + _windowFrame == null || + _currentWindowFrameMessage == _windowFrameMessageToShow && + !_currentWindowFrameImageMoniker.Equals(_windowFrameImageMonikerToShow)) + { + // We don't have anything to do, or anything to do yet. + return; + } + + var infoBarFactory = (IVsInfoBarUIFactory)_fileManager._serviceProvider.GetService(typeof(SVsInfoBarUIFactory)); + Assumes.Present(infoBarFactory); + + if (ErrorHandler.Failed(_windowFrame.GetProperty((int)__VSFPROPID7.VSFPROPID_InfoBarHost, out var infoBarHostObject)) || + infoBarHostObject is not IVsInfoBarHost infoBarHost) + { + return; + } + + // Remove the existing bar + if (_currentWindowFrameInfoBarElement != null) + { + infoBarHost.RemoveInfoBar(_currentWindowFrameInfoBarElement); + } + + var infoBar = new InfoBarModel(_windowFrameMessageToShow, _windowFrameImageMonikerToShow, isCloseButtonVisible: false); + var infoBarUI = infoBarFactory.CreateInfoBar(infoBar); + + infoBarHost.AddInfoBar(infoBarUI); + + _currentWindowFrameMessage = _windowFrameMessageToShow; + _currentWindowFrameImageMoniker = _windowFrameImageMonikerToShow; + _currentWindowFrameInfoBarElement = infoBarUI; + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 88bcafeda8e..56fec42a0d8 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Utilities; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.LanguageServices.Implementation.Library; @@ -39,6 +40,7 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff private readonly IServiceProvider _serviceProvider; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; + private readonly SourceGeneratedFileManager _sourceGeneratedFileManager; public VisualStudioSymbolNavigationService( SVsServiceProvider serviceProvider, @@ -50,6 +52,7 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff var componentModel = _serviceProvider.GetService(); _editorAdaptersFactory = componentModel.GetService(); _metadataAsSourceFileService = componentModel.GetService(); + _sourceGeneratedFileManager = componentModel.GetService(); } public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options, CancellationToken cancellationToken) @@ -76,6 +79,17 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio var navigationService = editorWorkspace.Services.GetRequiredService(); return navigationService.TryNavigateToSpan(editorWorkspace, targetDocument.Id, sourceLocation.SourceSpan, options); } + else + { + // This may be a file from a source generator; if so let's go to it instead + var generatorRunResult = project.GetGeneratorDriverRunResultAsync(cancellationToken).WaitAndGetResult(cancellationToken); + + if (generatorRunResult.TryGetGeneratorAndHint(sourceLocation.SourceTree!, out var generator, out var hintName)) + { + _sourceGeneratedFileManager.NavigateToSourceGeneratedFile(project, generator, hintName, sourceLocation.SourceSpan); + return true; + } + } } // We don't have a source document, so show the Metadata as Source view in a preview tab. diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index 4213e6275f7..2cfd1136a4f 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1538,4 +1538,20 @@ I agree to all of the foregoing: Extract Base Class + + This file is auto-generated by the generator '{0}' and cannot be edited. + + + [generated by {0}] + {0} is the name of a generator. + + + [generated] + + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + \ No newline at end of file diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index 414ce438247..27fa6d82c1d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -777,6 +777,21 @@ Cílový obor názvů: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Toto je neplatný obor názvů. @@ -1017,6 +1032,16 @@ {0} se změní na veřejný. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo Daný pracovní prostor nepodporuje vrácení akce zpátky. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index b5173720f2b..82a392bcb98 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -777,6 +777,21 @@ Zielnamespace: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Dies ist ein ungültiger Namespace. @@ -1017,6 +1032,16 @@ "{0}" wird in öffentlichen Wert geändert. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo Der angegebene Arbeitsbereich unterstützt die Funktion "Rückgängig" nicht. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index b96ba3e025f..2af5a6fa9d8 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -777,6 +777,21 @@ Espacio de nombres de destino: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Este espacio de nombres no es válido @@ -1017,6 +1032,16 @@ "{0}" se cambiará a público. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo el área de trabajo determinada no permite deshacer diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index 1fa458067c5..0b9e344700d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -777,6 +777,21 @@ Espace de noms cible : + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Ceci est un espace de noms non valide @@ -1017,6 +1032,16 @@ '{0}' va être changé en valeur publique. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo l'espace de travail donné ne prend pas en charge la fonction Annuler diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index 594c2e76220..8a167fa48a1 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -777,6 +777,21 @@ Spazio dei nomi di destinazione: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Questo è uno spazio dei nomi non valido @@ -1017,6 +1032,16 @@ '{0}' verrà modificato in pubblico. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo l'area di lavoro specificata non supporta l'annullamento di operazioni diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index dd22c780161..677df295548 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -777,6 +777,21 @@ ターゲット名前空間: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace これは無効な名前空間です @@ -1017,6 +1032,16 @@ '{0}' はパブリックに変更されます。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 指定されたワークスペースは元に戻す操作をサポートしていません diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index 2402269309b..937ba4cd451 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -777,6 +777,21 @@ 대상 네임스페이스: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 잘못된 네임스페이스입니다. @@ -1017,6 +1032,16 @@ '{0}'이(가) 공용으로 변경됩니다. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 지정한 작업 영역에서 실행을 취소할 수 없습니다. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 81361c69ef8..82f18388671 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -777,6 +777,21 @@ Docelowa przestrzeń nazw: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace To jest nieprawidłowa przestrzeń nazw @@ -1017,6 +1032,16 @@ Element „{0}” zostanie zmieniony na publiczny. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo dany obszar roboczy nie obsługuje operacji cofania diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 3e6da477dcb..7d3309b8588 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -777,6 +777,21 @@ Namespace de Destino: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Este é um namespace inválido @@ -1017,6 +1032,16 @@ '{0}' será alterado para público. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo o workspace fornecido não dá suporte a desfazer diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 9967f8929fb..4d8a9e8552a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -777,6 +777,21 @@ Целевое пространство имен: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Это недопустимое пространство имен. @@ -1017,6 +1032,16 @@ Элемент "{0}" будет изменен на открытый. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo заданная рабочая область не поддерживает отмену. diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index 1cd54f7ef7e..78ca67678d8 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -777,6 +777,21 @@ Hedef Ad Alanı: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace Bu geçersiz bir ad alanı @@ -1017,6 +1032,16 @@ '{0}' ortak olacak şekilde değiştirildi. + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo sağlanan çalışma alanı, geri almayı desteklemiyor diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 4bc37389e47..4eb77eec1b7 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -777,6 +777,21 @@ 目标命名空间: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 这是一个无效的命名空间 @@ -1017,6 +1032,16 @@ “{0}”将更改为“公共”。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 给定的工作区不支持撤消 diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index 492c8ddefa9..3829ad22d7d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -777,6 +777,21 @@ 目標命名空間: + + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project. + + + + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project. + + + + This file is auto-generated by the generator '{0}' and cannot be edited. + This file is auto-generated by the generator '{0}' and cannot be edited. + + This is an invalid namespace 這是無效的命名空間 @@ -1017,6 +1032,16 @@ '{0}' 會變更為公用。 + + [generated by {0}] + [generated by {0}] + {0} is the name of a generator. + + + [generated] + [generated] + + given workspace doesn't support undo 指定的工作區不支援復原 diff --git a/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs b/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs new file mode 100644 index 00000000000..a3035a0792d --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/GeneratorDriverRunResultExtensions.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.CodeAnalysis.Utilities +{ + internal static class GeneratorDriverRunResultExtensions + { + public static bool TryGetGeneratorAndHint( + this GeneratorDriverRunResult? generatorRunResult, + SyntaxTree tree, + [NotNullWhen(true)] out ISourceGenerator? generator, + [NotNullWhen(true)] out string? generatedSourceHintName) + { + if (generatorRunResult != null) + { + foreach (var generatorResult in generatorRunResult.Results) + { + foreach (var generatedSource in generatorResult.GeneratedSources) + { + if (generatedSource.SyntaxTree == tree) + { + generator = generatorResult.Generator; + generatedSourceHintName = generatedSource.HintName; + + return true; + } + } + } + } + + generator = null; + generatedSourceHintName = null; + return false; + } + } +} -- GitLab