提交 e17229cf 编写于 作者: P Paul Vick

Add remote service

上级 7e9969b0
......@@ -16,6 +16,9 @@ protected static async Task RunCountTest(XElement input, int cap = 0)
{
using (var workspace = await TestWorkspace.CreateAsync(input))
{
var service = workspace.GetService<ICodeLensReferencesService>();
Assert.NotNull(service);
foreach (var annotatedDocument in workspace.Documents.Where(d => d.AnnotatedSpans.Any()))
{
var document = workspace.CurrentSolution.GetDocument(annotatedDocument.Id);
......@@ -28,7 +31,7 @@ protected static async Task RunCountTest(XElement input, int cap = 0)
foreach (var span in annotatedSpan.Value)
{
var declarationSyntaxNode = syntaxNode.FindNode(span);
var result = await new CodeLensReferenceService().GetReferenceCountAsync(workspace.CurrentSolution, annotatedDocument.Id,
var result = await service.GetReferenceCountAsync(workspace.CurrentSolution, annotatedDocument.Id,
declarationSyntaxNode, cap, CancellationToken.None);
Assert.NotNull(result);
Assert.Equal(expected, result.Count);
......@@ -48,6 +51,9 @@ protected static async Task RunReferenceTest(XElement input)
{
using (var workspace = await TestWorkspace.CreateAsync(input))
{
var service = workspace.GetService<ICodeLensReferencesService>();
Assert.NotNull(service);
foreach (var annotatedDocument in workspace.Documents.Where(d => d.AnnotatedSpans.Any()))
{
var document = workspace.CurrentSolution.GetDocument(annotatedDocument.Id);
......@@ -59,7 +65,7 @@ protected static async Task RunReferenceTest(XElement input)
foreach (var span in annotatedSpan.Value)
{
var declarationSyntaxNode = syntaxNode.FindNode(span);
var result = await new CodeLensReferenceService().FindReferenceLocationsAsync(workspace.CurrentSolution,
var result = await service.FindReferenceLocationsAsync(workspace.CurrentSolution,
annotatedDocument.Id, declarationSyntaxNode, CancellationToken.None);
var count = result.Count();
Assert.Equal(expected, count);
......@@ -78,6 +84,9 @@ protected static async Task RunMethodReferenceTest(XElement input)
{
using (var workspace = await TestWorkspace.CreateAsync(input))
{
var service = workspace.GetService<ICodeLensReferencesService>();
Assert.NotNull(service);
foreach (var annotatedDocument in workspace.Documents.Where(d => d.AnnotatedSpans.Any()))
{
var document = workspace.CurrentSolution.GetDocument(annotatedDocument.Id);
......@@ -89,7 +98,7 @@ protected static async Task RunMethodReferenceTest(XElement input)
foreach (var span in annotatedSpan.Value)
{
var declarationSyntaxNode = syntaxNode.FindNode(span);
var result = await new CodeLensReferenceService().FindReferenceMethodsAsync(workspace.CurrentSolution,
var result = await service.FindReferenceMethodsAsync(workspace.CurrentSolution,
annotatedDocument.Id, declarationSyntaxNode, CancellationToken.None);
var count = result.Count();
Assert.Equal(expected, count);
......
......@@ -15,8 +15,7 @@
namespace Microsoft.CodeAnalysis.CodeLens
{
[ExportWorkspaceService(typeof(ICodeLensReferencesService)), Shared]
internal sealed class CodeLensReferenceService : ICodeLensReferencesService
internal sealed class CodeLensReferencesService : ICodeLensReferencesService
{
private static readonly SymbolDisplayFormat MethodDisplayFormat =
new SymbolDisplayFormat(
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.CodeLens
{
[ExportWorkspaceServiceFactory(typeof(ICodeLensReferencesService)), Shared]
internal sealed class CodeLensReferencesServiceFactory : IWorkspaceServiceFactory
{
public static readonly ICodeLensReferencesService Instance = new CodeLensReferencesService();
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
return Instance;
}
}
}
......@@ -81,5 +81,23 @@ public ReferenceLocationDescriptor(string longDescription, string language, Glyp
AfterReferenceText1 = afterReferenceText1;
AfterReferenceText2 = afterReferenceText2;
}
public ReferenceLocationDescriptor(string longDescription, string language, Glyph? glyph, int lineNumber, int columnNumber, DocumentId documentId, string referenceLineText, int referenceStart, int referenceLength, string beforeReferenceText1, string beforeReferenceText2, string afterReferenceText1, string afterReferenceText2)
{
LongDescription = longDescription;
Language = language;
Glyph = glyph;
LineNumber = lineNumber;
ColumnNumber = columnNumber;
// We want to keep track of the location's document if it comes from a file in your solution.
DocumentId = documentId;
ReferenceLineText = referenceLineText;
ReferenceStart = referenceStart;
ReferenceLength = referenceLength;
BeforeReferenceText1 = beforeReferenceText1;
BeforeReferenceText2 = beforeReferenceText2;
AfterReferenceText1 = afterReferenceText1;
AfterReferenceText2 = afterReferenceText2;
}
}
}
......@@ -10,16 +10,16 @@ internal sealed class ReferenceMethodDescriptor
/// <summary>
/// Describe a caller method of a callee
/// </summary>
/// <param name="methodFullName">Method's fully qualified name</param>
/// <param name="methodFilePath">Method full path</param>
/// <param name="fullName">Method's fully qualified name</param>
/// <param name="filePath">Method full path</param>
/// <remarks>
/// Method full name is expected to be in the .NET full name type convention. That is,
/// namespace/type is delimited by '.' and nested type is delimited by '+'
/// </remarks>
public ReferenceMethodDescriptor(string methodFullName, string methodFilePath)
public ReferenceMethodDescriptor(string fullName, string filePath)
{
FullName = methodFullName;
FilePath = methodFilePath;
FullName = fullName;
FilePath = filePath;
}
/// <summary>
......
......@@ -45,6 +45,7 @@
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.ExternalDependencyServices" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.VisualBasic" />
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Xaml" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.ServiceHub" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.TestImpact.Orchestrator" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.EditorFeatures" />
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.VisualBasic.Features" />
......@@ -91,6 +92,7 @@
</Compile>
<Compile Include="AddMissingReference\AbstractAddMissingReferenceCodeFixProvider.cs" />
<Compile Include="AddMissingReference\CodeAction.cs" />
<Compile Include="CodeLens\CodeLensReferencesServiceFactory.cs" />
<Compile Include="Structure\Syntax\BlockStructureExtensions.cs" />
<Compile Include="Structure\Syntax\AbstractBlockStructureProvider.cs" />
<Compile Include="Structure\BlockSpan.cs" />
......@@ -161,7 +163,7 @@
<Compile Include="CodeFixes\Suppression\ExportSuppressionFixProviderAttribute.cs" />
<Compile Include="CodeFixes\Suppression\WrapperCodeFixProvider.cs" />
<Compile Include="CodeFixes\Suppression\AbstractSuppressionCodeFixProvider.cs" />
<Compile Include="CodeLens\CodeLensReferenceService.cs" />
<Compile Include="CodeLens\CodeLensReferencesService.cs" />
<Compile Include="CodeLens\CodeLensFindReferenceProgress.cs" />
<Compile Include="CodeLens\ICodeLensReferencesService.cs" />
<Compile Include="CodeLens\ICodeLensDisplayInfoService.cs" />
......@@ -671,4 +673,4 @@
<ItemGroup />
<Import Project="..\..\..\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems" Label="Shared" />
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</Project>
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CodeLens;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeLens
{
/// <summary>
/// This interface is solely here to prevent VS.Next dll from loading
/// when RemoteHost option is off
/// </summary>
internal interface IRemoteCodeLensReferencesService : ICodeLensReferencesService
{
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeLens;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeLens
{
[ExportWorkspaceService(typeof(ICodeLensReferencesService), layer: ServiceLayer.Host), Shared]
internal sealed class VisualStudioCodeLensReferencesService : ICodeLensReferencesService
{
public async Task<ReferenceCount> GetReferenceCountAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode, int maxSearchResults,
CancellationToken cancellationToken)
{
var remoteService = solution.Workspace.Services.GetService<IRemoteCodeLensReferencesService>();
return remoteService == null
? await
CodeLensReferencesServiceFactory.Instance.GetReferenceCountAsync(solution, documentId, syntaxNode,
maxSearchResults, cancellationToken).ConfigureAwait(false)
: await
remoteService.GetReferenceCountAsync(solution, documentId, syntaxNode, maxSearchResults,
cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<ReferenceLocationDescriptor>> FindReferenceLocationsAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteService = solution.Workspace.Services.GetService<IRemoteCodeLensReferencesService>();
return remoteService == null
? await
CodeLensReferencesServiceFactory.Instance.FindReferenceLocationsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false)
: await
remoteService.FindReferenceLocationsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<ReferenceMethodDescriptor>> FindReferenceMethodsAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteService = solution.Workspace.Services.GetService<IRemoteCodeLensReferencesService>();
return remoteService == null
? await
CodeLensReferencesServiceFactory.Instance.FindReferenceMethodsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false)
: await
remoteService.FindReferenceMethodsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
public async Task<string> GetFullyQualifiedName(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteService = solution.Workspace.Services.GetService<IRemoteCodeLensReferencesService>();
return remoteService == null
? await
CodeLensReferencesServiceFactory.Instance.GetFullyQualifiedName(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false)
: await
remoteService.GetFullyQualifiedName(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
}
}
......@@ -54,6 +54,8 @@
<Compile Include="Implementation\AnalyzerDependency\IgnorableAssemblyIdentityList.cs" />
<Compile Include="Implementation\AnalyzerDependency\IgnorableAssemblyNameList.cs" />
<Compile Include="Implementation\AnalyzerDependency\IgnorableAssemblyNamePrefixList.cs" />
<Compile Include="Implementation\CodeLens\IRemoteCodeLensReferencesService.cs" />
<Compile Include="Implementation\CodeLens\VisualStudioCodeLensReferencesService.cs" />
<Compile Include="Implementation\CompilationErrorTelemetry\CompilationErrorTelemetryIncrementalAnalyzer.cs" />
<Compile Include="Implementation\Diagnostics\IRemoteHostDiagnosticAnalyzerExecutor.cs" />
<Compile Include="Implementation\Diagnostics\VisualStudioDiagnosticAnalyzerExecutor.cs" />
......
......@@ -92,6 +92,7 @@
<Compile Include="..\..\..\Workspaces\Remote\ServiceHub\Shared\WellKnownServiceHubServices.cs">
<Link>Shared\WellKnownServiceHubServices.cs</Link>
</Compile>
<Compile Include="CodeLens\RemoteCodeLensReferencesService.cs" />
<Compile Include="Diagnostics\OutOfProcDiagnosticAnalyzerExecutor.cs" />
<Compile Include="Extensions\RemoteHostClientExtensions.cs" />
<Compile Include="FindReferences\FindReferencesTableControlEventProcessorProvider.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeLens;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeLens;
using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions;
using Microsoft.VisualStudio.LanguageServices.Remote;
namespace Microsoft.VisualStudio.LanguageServices.CodeLens
{
[ExportWorkspaceService(typeof(IRemoteCodeLensReferencesService)), Shared]
internal sealed class RemoteCodeLensReferencesService : IRemoteCodeLensReferencesService
{
public async Task<ReferenceCount> GetReferenceCountAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode, int maxSearchResults,
CancellationToken cancellationToken)
{
var remoteHostClient = await solution.Workspace.Services.GetService<IRemoteHostClientService>().GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteHostClient == null)
{
// remote host is not running. this can happen if remote host is disabled.
return await CodeLensReferencesServiceFactory.Instance.GetReferenceCountAsync(solution, documentId, syntaxNode, maxSearchResults, cancellationToken).ConfigureAwait(false);
}
// TODO: send telemetry on session
using (var session = await remoteHostClient.CreateCodeAnalysisServiceSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
return await session.InvokeAsync<ReferenceCount>(WellKnownServiceHubServices.CodeAnalysisService_GetReferenceCountAsync, documentId, syntaxNode.Span, maxSearchResults).ConfigureAwait(false);
}
}
public async Task<IEnumerable<ReferenceLocationDescriptor>> FindReferenceLocationsAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteHostClient = await solution.Workspace.Services.GetService<IRemoteHostClientService>().GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteHostClient == null)
{
// remote host is not running. this can happen if remote host is disabled.
return await CodeLensReferencesServiceFactory.Instance.FindReferenceLocationsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
// TODO: send telemetry on session
using (var session = await remoteHostClient.CreateCodeAnalysisServiceSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
return await session.InvokeAsync<IEnumerable<ReferenceLocationDescriptor>>(WellKnownServiceHubServices.CodeAnalysisService_FindReferenceLocationsAsync, documentId, syntaxNode.Span).ConfigureAwait(false);
}
}
public async Task<IEnumerable<ReferenceMethodDescriptor>> FindReferenceMethodsAsync(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteHostClient = await solution.Workspace.Services.GetService<IRemoteHostClientService>().GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteHostClient == null)
{
// remote host is not running. this can happen if remote host is disabled.
return await CodeLensReferencesServiceFactory.Instance.FindReferenceMethodsAsync(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
// TODO: send telemetry on session
using (var session = await remoteHostClient.CreateCodeAnalysisServiceSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
return await session.InvokeAsync<IEnumerable<ReferenceMethodDescriptor>>(WellKnownServiceHubServices.CodeAnalysisService_FindReferenceMethodsAsync, documentId, syntaxNode.Span).ConfigureAwait(false);
}
}
public async Task<string> GetFullyQualifiedName(Solution solution, DocumentId documentId, SyntaxNode syntaxNode,
CancellationToken cancellationToken)
{
var remoteHostClient = await solution.Workspace.Services.GetService<IRemoteHostClientService>().GetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteHostClient == null)
{
// remote host is not running. this can happen if remote host is disabled.
return await CodeLensReferencesServiceFactory.Instance.GetFullyQualifiedName(solution, documentId, syntaxNode, cancellationToken).ConfigureAwait(false);
}
// TODO: send telemetry on session
using (var session = await remoteHostClient.CreateCodeAnalysisServiceSessionAsync(solution, cancellationToken).ConfigureAwait(false))
{
return await session.InvokeAsync<string>(WellKnownServiceHubServices.CodeAnalysisService_GetFullyQualifiedName, documentId, syntaxNode.Span).ConfigureAwait(false);
}
}
}
}
......@@ -351,5 +351,9 @@ internal enum FunctionId
AssetService_SynchronizeSolutionAssetsAsync,
SolutionChecksumServiceFactory_GetChecksumObjects,
ChecksumTreeNode_GetOrCreateChecksumObject,
CodeAnalysisService_GetReferenceCountAsync,
CodeAnalysisService_FindReferenceLocationsAsync,
CodeAnalysisService_FindReferenceMethodsAsync,
CodeAnalysisService_GetFullyQualifiedName,
}
}
......@@ -19,6 +19,10 @@
<Project>{1EE8CAD3-55F9-4D91-96B2-084641DA9A6C}</Project>
<Name>CodeAnalysis</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Features\Core\Portable\Features.csproj">
<Project>{edc68a0e-c68d-4a74-91b7-bf38ec909888}</Project>
<Name>Features</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Desktop\Workspaces.Desktop.csproj">
<Project>{2e87fa96-50bb-4607-8676-46521599f998}</Project>
<Name>Workspaces.Desktop</Name>
......@@ -45,6 +49,7 @@
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
<Compile Include="Services\CodeAnalysisService_CodeLens.cs" />
<Compile Include="Services\SnapshotService.JsonRpcAssetSource.cs" />
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CodeLens;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Remote
{
internal partial class CodeAnalysisService
{
public async Task<ReferenceCount> GetReferenceCountAsync(DocumentId documentId, TextSpan textSpan, int maxResultCount, byte[] solutionChecksum)
{
using (Internal.Log.Logger.LogBlock(FunctionId.CodeAnalysisService_GetReferenceCountAsync, documentId.ProjectId.DebugName, CancellationToken))
{
var solution = await RoslynServices.SolutionService.GetSolutionAsync(new Checksum(solutionChecksum), CancellationToken).ConfigureAwait(false);
var syntaxNode = (await solution.GetDocument(documentId).GetSyntaxRootAsync().ConfigureAwait(false)).FindNode(textSpan);
return await CodeLensReferencesServiceFactory.Instance.GetReferenceCountAsync(solution, documentId,
syntaxNode, maxResultCount, CancellationToken).ConfigureAwait(false);
}
}
public async Task<IEnumerable<ReferenceLocationDescriptor>> FindReferenceLocationsAsync(DocumentId documentId, TextSpan textSpan, byte[] solutionChecksum)
{
using (Internal.Log.Logger.LogBlock(FunctionId.CodeAnalysisService_FindReferenceLocationsAsync, documentId.ProjectId.DebugName, CancellationToken))
{
var solution = await RoslynServices.SolutionService.GetSolutionAsync(new Checksum(solutionChecksum), CancellationToken).ConfigureAwait(false);
var syntaxNode = (await solution.GetDocument(documentId).GetSyntaxRootAsync().ConfigureAwait(false)).FindNode(textSpan);
return await CodeLensReferencesServiceFactory.Instance.FindReferenceLocationsAsync(solution, documentId,
syntaxNode, CancellationToken).ConfigureAwait(false);
}
}
public async Task<IEnumerable<ReferenceMethodDescriptor>> FindReferenceMethodsAsync(DocumentId documentId, TextSpan textSpan, byte[] solutionChecksum)
{
using (Internal.Log.Logger.LogBlock(FunctionId.CodeAnalysisService_FindReferenceMethodsAsync, documentId.ProjectId.DebugName, CancellationToken))
{
var solution = await RoslynServices.SolutionService.GetSolutionAsync(new Checksum(solutionChecksum), CancellationToken).ConfigureAwait(false);
var syntaxNode = (await solution.GetDocument(documentId).GetSyntaxRootAsync().ConfigureAwait(false)).FindNode(textSpan);
return await CodeLensReferencesServiceFactory.Instance.FindReferenceMethodsAsync(solution, documentId,
syntaxNode, CancellationToken).ConfigureAwait(false);
}
}
public async Task<string> GetFullyQualifiedName(DocumentId documentId, TextSpan textSpan, byte[] solutionChecksum)
{
using (Internal.Log.Logger.LogBlock(FunctionId.CodeAnalysisService_GetFullyQualifiedName, documentId.ProjectId.DebugName, CancellationToken))
{
var solution = await RoslynServices.SolutionService.GetSolutionAsync(new Checksum(solutionChecksum), CancellationToken).ConfigureAwait(false);
var syntaxNode = (await solution.GetDocument(documentId).GetSyntaxRootAsync().ConfigureAwait(false)).FindNode(textSpan);
return await CodeLensReferencesServiceFactory.Instance.GetFullyQualifiedName(solution, documentId,
syntaxNode, CancellationToken).ConfigureAwait(false);
}
}
}
}
......@@ -9,6 +9,10 @@ internal static class WellKnownServiceHubServices
public const string CodeAnalysisService = "codeAnalysisService";
public const string CodeAnalysisService_CalculateDiagnosticsAsync = "CalculateDiagnosticsAsync";
public const string CodeAnalysisService_GetReferenceCountAsync = "GetReferenceCountAsync";
public const string CodeAnalysisService_FindReferenceLocationsAsync = "FindReferenceLocationsAsync";
public const string CodeAnalysisService_FindReferenceMethodsAsync = "FindReferenceMethodsAsync";
public const string CodeAnalysisService_GetFullyQualifiedName = "GetFullyQualifiedName";
public const string AssetService_RequestAssetAsync = "RequestAssetAsync";
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册