提交 e7d7a542 编写于 作者: J Jason Malinowski

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.
上级 da2aabf8
## 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.
......@@ -169,6 +169,17 @@ public static IReadOnlyCollection<T> ToCollection<T>(this IEnumerable<T> sequenc
return source.Cast<T?>().LastOrDefault();
}
public static T? SingleOrNull<T>(this IEnumerable<T> source, Func<T, bool> predicate)
where T : struct
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
return source.Cast<T?>().SingleOrDefault(v => predicate(v!.Value));
}
public static bool IsSingle<T>(this IEnumerable<T> list)
{
using var enumerator = list.GetEnumerator();
......
......@@ -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);
}
}
}
}
}
......
// 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
{
/// <summary>
/// Provides the support for opening files pointing to source generated documents, and keeping the content updated accordingly.
/// </summary>
[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;
/// <summary>
/// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use.
/// </summary>
private readonly string _temporaryDirectory;
/// <summary>
/// Map of currently open generated files; the key is the generated full file path.
/// </summary>
private readonly Dictionary<string, OpenSourceGeneratedFile> _openFiles = new Dictionary<string, OpenSourceGeneratedFile>();
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<SVsRunningDocumentTable, IVsRunningDocumentTable>();
_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<SVsUIShellOpenDocument, IVsUIShellOpenDocument>();
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;
/// <summary>
/// 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 <see cref="_updatingBuffer"/> is set.
/// </summary>
private readonly IReadOnlyRegion _readOnlyRegion;
private bool _updatingBuffer = false;
/// <summary>
/// A cancellation token used for any background updating of this file; this is cancelled on the UI thread
/// when the file is closed.
/// </summary>
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// A queue used to batch updates to the file.
/// </summary>
private readonly AsyncBatchingDelay _batchingWorkQueue;
/// <summary>
/// The <see cref="IVsWindowFrame"/> of the active window. This may be null if we're in the middle of construction and
/// we haven't been given it yet.
/// </summary>
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;
}
}
}
}
......@@ -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<SComponentModel, IComponentModel>();
_editorAdaptersFactory = componentModel.GetService<IVsEditorAdaptersFactoryService>();
_metadataAsSourceFileService = componentModel.GetService<IMetadataAsSourceFileService>();
_sourceGeneratedFileManager = componentModel.GetService<SourceGeneratedFileManager>();
}
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<IDocumentNavigationService>();
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.
......
......@@ -1538,4 +1538,20 @@ I agree to all of the foregoing:</value>
<data name="Extract_Base_Class" xml:space="preserve">
<value>Extract Base Class</value>
</data>
<data name="This_file_is_autogenerated_by_0_and_cannot_be_edited" xml:space="preserve">
<value>This file is auto-generated by the generator '{0}' and cannot be edited.</value>
</data>
<data name="generated_by_0_suffix" xml:space="preserve">
<value>[generated by {0}]</value>
<comment>{0} is the name of a generator.</comment>
</data>
<data name="generated_suffix" xml:space="preserve">
<value>[generated]</value>
</data>
<data name="The_generator_0_that_generated_this_file_has_been_removed_from_the_project" xml:space="preserve">
<value>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</value>
</data>
<data name="The_generator_0_that_generated_this_file_has_stopped_generating_this_file" xml:space="preserve">
<value>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</value>
</data>
</root>
\ No newline at end of file
......@@ -777,6 +777,21 @@
<target state="translated">Cílový obor názvů:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Toto je neplatný obor názvů.</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">{0} se změní na veřejný.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">Daný pracovní prostor nepodporuje vrácení akce zpátky.</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Zielnamespace:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Dies ist ein ungültiger Namespace.</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">"{0}" wird in öffentlichen Wert geändert.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">Der angegebene Arbeitsbereich unterstützt die Funktion "Rückgängig" nicht.</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Espacio de nombres de destino:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Este espacio de nombres no es válido</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">"{0}" se cambiará a público.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">el área de trabajo determinada no permite deshacer</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Espace de noms cible :</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Ceci est un espace de noms non valide</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' va être changé en valeur publique.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">l'espace de travail donné ne prend pas en charge la fonction Annuler</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Spazio dei nomi di destinazione:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Questo è uno spazio dei nomi non valido</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' verrà modificato in pubblico.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">l'area di lavoro specificata non supporta l'annullamento di operazioni</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">ターゲット名前空間:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">これは無効な名前空間です</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' はパブリックに変更されます。</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">指定されたワークスペースは元に戻す操作をサポートしていません</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">대상 네임스페이스:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">잘못된 네임스페이스입니다.</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}'이(가) 공용으로 변경됩니다.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">지정한 작업 영역에서 실행을 취소할 수 없습니다.</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Docelowa przestrzeń nazw:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">To jest nieprawidłowa przestrzeń nazw</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">Element „{0}” zostanie zmieniony na publiczny.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">dany obszar roboczy nie obsługuje operacji cofania</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Namespace de Destino:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Este é um namespace inválido</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' será alterado para público.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">o workspace fornecido não dá suporte a desfazer</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Целевое пространство имен:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Это недопустимое пространство имен.</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">Элемент "{0}" будет изменен на открытый.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">заданная рабочая область не поддерживает отмену.</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">Hedef Ad Alanı:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">Bu geçersiz bir ad alanı</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' ortak olacak şekilde değiştirildi.</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">sağlanan çalışma alanı, geri almayı desteklemiyor</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">目标命名空间:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">这是一个无效的命名空间</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">“{0}”将更改为“公共”。</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">给定的工作区不支持撤消</target>
......
......@@ -777,6 +777,21 @@
<target state="translated">目標命名空間:</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_been_removed_from_the_project">
<source>The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has been removed from the project; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="The_generator_0_that_generated_this_file_has_stopped_generating_this_file">
<source>The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</source>
<target state="new">The generator '{0}' that generated this file has stopped generating this file; this file is no longer being included in your project.</target>
<note />
</trans-unit>
<trans-unit id="This_file_is_autogenerated_by_0_and_cannot_be_edited">
<source>This file is auto-generated by the generator '{0}' and cannot be edited.</source>
<target state="new">This file is auto-generated by the generator '{0}' and cannot be edited.</target>
<note />
</trans-unit>
<trans-unit id="This_is_an_invalid_namespace">
<source>This is an invalid namespace</source>
<target state="translated">這是無效的命名空間</target>
......@@ -1017,6 +1032,16 @@
<target state="translated">'{0}' 會變更為公用。</target>
<note />
</trans-unit>
<trans-unit id="generated_by_0_suffix">
<source>[generated by {0}]</source>
<target state="new">[generated by {0}]</target>
<note>{0} is the name of a generator.</note>
</trans-unit>
<trans-unit id="generated_suffix">
<source>[generated]</source>
<target state="new">[generated]</target>
<note />
</trans-unit>
<trans-unit id="given_workspace_doesn_t_support_undo">
<source>given workspace doesn't support undo</source>
<target state="translated">指定的工作區不支援復原</target>
......
// 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;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册