Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
lwm1986
roslyn
提交
d53c1f47
R
roslyn
项目概览
lwm1986
/
roslyn
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
roslyn
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
d53c1f47
编写于
10月 06, 2020
作者:
C
Cyrus Najmabadi
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Suppress inline hints in certain clear cases.
上级
e38b7cde
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
264 addition
and
26 deletion
+264
-26
src/EditorFeatures/Test2/InlineHints/AbstractInlineParameterNameHintsTests.vb
...est2/InlineHints/AbstractInlineParameterNameHintsTests.vb
+7
-1
src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb
.../Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb
+150
-0
src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs
...able/InlineHints/CSharpInlineParameterNameHintsService.cs
+46
-16
src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs
...le/InlineHints/AbstractInlineParameterNameHintsService.cs
+56
-6
src/Features/VisualBasic/Portable/InlineParameterNameHints/VisualBasicInlineParameterNameHintsService.vb
...erNameHints/VisualBasicInlineParameterNameHintsService.vb
+5
-3
未找到文件。
src/EditorFeatures/Test2/InlineHints/AbstractInlineParameterNameHintsTests.vb
浏览文件 @
d53c1f47
...
@@ -10,10 +10,16 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints
...
@@ -10,10 +10,16 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.InlineHints
<
[
UseExportProvider
]
>
<
[
UseExportProvider
]
>
Public
MustInherit
Class
AbstractInlineParameterNameHintsTests
Public
MustInherit
Class
AbstractInlineParameterNameHintsTests
Protected
Async
Function
VerifyParamHints
(
test
As
XElement
,
Optional
optionIsEnabled
As
Boolean
=
True
)
As
Task
s
.
Task
Protected
Async
Function
VerifyParamHints
(
test
As
XElement
,
Optional
optionIsEnabled
As
Boolean
=
True
)
As
Task
Using
workspace
=
TestWorkspace
.
Create
(
test
)
Using
workspace
=
TestWorkspace
.
Create
(
test
)
WpfTestRunner
.
RequireWpfFact
(
$
"{NameOf(AbstractInlineParameterNameHintsTests)}.{NameOf(Me.VerifyParamHints)} creates asynchronous taggers"
)
WpfTestRunner
.
RequireWpfFact
(
$
"{NameOf(AbstractInlineParameterNameHintsTests)}.{NameOf(Me.VerifyParamHints)} creates asynchronous taggers"
)
workspace
.
TryApplyChanges
(
workspace
.
CurrentSolution
.
WithOptions
(
workspace
.
Options
.
WithChangedOption
(
InlineHintsOptions
.
EnabledForParameters
,
workspace
.
CurrentSolution
.
Projects
.
Single
().
Language
,
optionIsEnabled
)))
Dim
hostDocument
=
workspace
.
Documents
.
Single
()
Dim
hostDocument
=
workspace
.
Documents
.
Single
()
Dim
snapshot
=
hostDocument
.
GetTextBuffer
().
CurrentSnapshot
Dim
snapshot
=
hostDocument
.
GetTextBuffer
().
CurrentSnapshot
Dim
document
=
workspace
.
CurrentSolution
.
GetDocument
(
hostDocument
.
Id
)
Dim
document
=
workspace
.
CurrentSolution
.
GetDocument
(
hostDocument
.
Id
)
...
...
src/EditorFeatures/Test2/InlineHints/CSharpInlineParameterNameHintsTests.vb
浏览文件 @
d53c1f47
...
@@ -398,5 +398,155 @@ class Derived : Base
...
@@ -398,5 +398,155 @@ class Derived : Base
Await
VerifyParamHints
(
input
)
Await
VerifyParamHints
(
input
)
End
Function
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestNotOnEnableDisableBoolean1
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
EnableLogging
(
bool
value
)
{
}
void
Main
()
{
EnableLogging
(
true
)
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestNotOnEnableDisableBoolean2
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
DisableLogging
(
bool
value
)
{
}
void
Main
()
{
DisableLogging
(
true
)
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestOnEnableDisableNonBoolean1
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
EnableLogging
(
string
value
)
{
}
void
Main
()
{
EnableLogging
({
|
value
:
"IO"
|
})
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestOnEnableDisableNonBoolean2
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
DisableLogging
(
string
value
)
{
}
void
Main
()
{
DisableLogging
({
|
value
:
"IO"
|
})
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestOnSetMethodWithClearContext
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
SetClassification
(
string
classification
)
{
}
void
Main
()
{
SetClassification
(
"IO"
)
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
<
WorkItem
(
47597
,
"https://github.com/dotnet/roslyn/issues/47597"
)
>
<
WpfFact
,
Trait
(
Traits
.
Feature
,
Traits
.
Features
.
InlineParameterNameHints
)
>
Public
Async
Function
TestOnSetMethodWithUnclearContext
()
As
Task
Dim
input
=
<
Workspace
>
<
Project
Language
=
"C#"
CommonReferences
=
"true"
>
<
Document
>
class
A
{
void
SetClassification
(
string
values
)
{
}
void
Main
()
{
SetClassification
({
|
values
:
"IO"
|
})
;
}
}
</
Document
>
</
Project
>
</
Workspace
>
Await
VerifyParamHints
(
input
)
End
Function
End
Class
End
Class
End
Namespace
End
Namespace
src/Features/CSharp/Portable/InlineHints/CSharpInlineParameterNameHintsService.cs
浏览文件 @
d53c1f47
...
@@ -3,14 +3,12 @@
...
@@ -3,14 +3,12 @@
// See the LICENSE file in the project root for more information.
// See the LICENSE file in the project root for more information.
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Composition
;
using
System.Composition
;
using
System.Threading
;
using
System.Threading
;
using
Microsoft.CodeAnalysis.CSharp.Extensions
;
using
Microsoft.CodeAnalysis.CSharp.Extensions
;
using
Microsoft.CodeAnalysis.CSharp.Syntax
;
using
Microsoft.CodeAnalysis.CSharp.Syntax
;
using
Microsoft.CodeAnalysis.Host.Mef
;
using
Microsoft.CodeAnalysis.Host.Mef
;
using
Microsoft.CodeAnalysis.InlineHints
;
using
Microsoft.CodeAnalysis.InlineHints
;
using
Microsoft.CodeAnalysis.PooledObjects
;
namespace
Microsoft.CodeAnalysis.CSharp.InlineHints
namespace
Microsoft.CodeAnalysis.CSharp.InlineHints
{
{
...
@@ -28,29 +26,61 @@ public CSharpInlineParameterNameHintsService()
...
@@ -28,29 +26,61 @@ public CSharpInlineParameterNameHintsService()
}
}
protected
override
void
AddAllParameterNameHintLocations
(
protected
override
void
AddAllParameterNameHintLocations
(
SemanticModel
semanticModel
,
SyntaxNode
node
,
SemanticModel
semanticModel
,
Action
<
InlineParameterHint
>
addHint
,
CancellationToken
cancellationToken
)
SyntaxNode
node
,
Action
<
InlineParameterHint
>
addHint
,
bool
hideForParametersThatDifferBySuffix
,
bool
hideForParametersThatMatchMethodIntent
,
CancellationToken
cancellationToken
)
{
{
if
(
node
is
ArgumentSyntax
argument
)
if
(
node
is
ArgumentSyntax
argument
)
{
{
if
(
argument
.
NameColon
==
null
)
if
(
argument
.
NameColon
!=
null
)
{
return
;
var
param
=
argument
.
DetermineParameter
(
semanticModel
,
cancellationToken
:
cancellationToken
);
if
(!
string
.
IsNullOrEmpty
(
param
?.
Name
))
var
parameter
=
argument
.
DetermineParameter
(
semanticModel
,
cancellationToken
:
cancellationToken
);
addHint
(
new
InlineParameterHint
(
param
.
GetSymbolKey
(
cancellationToken
),
param
.
Name
,
argument
.
SpanStart
,
GetKind
(
argument
.
Expression
)));
if
(
string
.
IsNullOrEmpty
(
parameter
?.
Name
))
}
return
;
if
(
hideForParametersThatMatchMethodIntent
&&
MatchesMethodIntent
(
argument
,
parameter
))
return
;
addHint
(
new
InlineParameterHint
(
parameter
.
GetSymbolKey
(
cancellationToken
),
parameter
.
Name
,
argument
.
Span
.
Start
,
GetKind
(
argument
.
Expression
)));
}
}
else
if
(
node
is
AttributeArgumentSyntax
attribute
)
else
if
(
node
is
AttributeArgumentSyntax
attribute
)
{
{
if
(
attribute
.
NameEquals
==
null
&&
attribute
.
NameColon
==
null
)
if
(
attribute
.
NameEquals
!=
null
||
attribute
.
NameColon
!=
null
)
{
return
;
var
param
=
attribute
.
DetermineParameter
(
semanticModel
,
cancellationToken
:
cancellationToken
);
if
(!
string
.
IsNullOrEmpty
(
param
?.
Name
))
var
parameter
=
attribute
.
DetermineParameter
(
semanticModel
,
cancellationToken
:
cancellationToken
);
addHint
(
new
InlineParameterHint
(
param
.
GetSymbolKey
(
cancellationToken
),
param
.
Name
,
attribute
.
SpanStart
,
GetKind
(
attribute
.
Expression
)));
if
(
string
.
IsNullOrEmpty
(
parameter
?.
Name
))
}
return
;
addHint
(
new
InlineParameterHint
(
parameter
.
GetSymbolKey
(
cancellationToken
),
parameter
.
Name
,
attribute
.
SpanStart
,
GetKind
(
attribute
.
Expression
)));
}
}
}
}
private
static
bool
MatchesMethodIntent
(
ArgumentSyntax
argument
,
IParameterSymbol
parameter
)
{
// Methods like `SetColor(color: "y")` `FromResult(result: "x")` `Enable/DisablePolling(bool)` don't need
// parameter names to improve clarity. The parameter is clear from the context of the method name.
if
(
argument
.
Parent
is
not
ArgumentListSyntax
argumentList
)
return
false
;
if
(
argumentList
.
Arguments
[
0
]
!=
argument
)
return
false
;
if
(
argumentList
.
Parent
is
not
InvocationExpressionSyntax
invocationExpression
)
return
false
;
var
invokedExpression
=
invocationExpression
.
Expression
;
var
rightMostName
=
invokedExpression
.
GetRightmostName
();
if
(
rightMostName
==
null
)
return
false
;
return
MatchesMethodIntent
(
rightMostName
.
Identifier
.
ValueText
,
parameter
);
}
private
static
InlineParameterHintKind
GetKind
(
ExpressionSyntax
arg
)
private
static
InlineParameterHintKind
GetKind
(
ExpressionSyntax
arg
)
=>
arg
switch
=>
arg
switch
{
{
...
...
src/Features/Core/Portable/InlineHints/AbstractInlineParameterNameHintsService.cs
浏览文件 @
d53c1f47
...
@@ -2,11 +2,9 @@
...
@@ -2,11 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// See the LICENSE file in the project root for more information.
#
nullable
disable
using
System
;
using
System
;
using
System.Collections.Generic
;
using
System.Collections.Immutable
;
using
System.Collections.Immutable
;
using
System.Diagnostics.CodeAnalysis
;
using
System.Threading
;
using
System.Threading
;
using
System.Threading.Tasks
;
using
System.Threading.Tasks
;
using
Microsoft.CodeAnalysis.PooledObjects
;
using
Microsoft.CodeAnalysis.PooledObjects
;
...
@@ -19,7 +17,9 @@ namespace Microsoft.CodeAnalysis.InlineHints
...
@@ -19,7 +17,9 @@ namespace Microsoft.CodeAnalysis.InlineHints
internal
abstract
class
AbstractInlineParameterNameHintsService
:
IInlineParameterNameHintsService
internal
abstract
class
AbstractInlineParameterNameHintsService
:
IInlineParameterNameHintsService
{
{
protected
abstract
void
AddAllParameterNameHintLocations
(
protected
abstract
void
AddAllParameterNameHintLocations
(
SemanticModel
semanticModel
,
SyntaxNode
node
,
Action
<
InlineParameterHint
>
addHint
,
CancellationToken
cancellationToken
);
SemanticModel
semanticModel
,
SyntaxNode
node
,
Action
<
InlineParameterHint
>
addHint
,
bool
hideForParametersThatDifferBySuffix
,
bool
hideForParametersThatMatchMethodIntent
,
CancellationToken
cancellationToken
);
public
async
Task
<
ImmutableArray
<
InlineParameterHint
>>
GetInlineParameterNameHintsAsync
(
Document
document
,
TextSpan
textSpan
,
CancellationToken
cancellationToken
)
public
async
Task
<
ImmutableArray
<
InlineParameterHint
>>
GetInlineParameterNameHintsAsync
(
Document
document
,
TextSpan
textSpan
,
CancellationToken
cancellationToken
)
{
{
...
@@ -35,16 +35,24 @@ public async Task<ImmutableArray<InlineParameterHint>> GetInlineParameterNameHin
...
@@ -35,16 +35,24 @@ public async Task<ImmutableArray<InlineParameterHint>> GetInlineParameterNameHin
if
(!
literalParameters
&&
!
objectCreationParameters
&&
!
otherParameters
)
if
(!
literalParameters
&&
!
objectCreationParameters
&&
!
otherParameters
)
return
ImmutableArray
<
InlineParameterHint
>.
Empty
;
return
ImmutableArray
<
InlineParameterHint
>.
Empty
;
var
hideForParametersThatDifferBySuffix
=
options
.
GetOption
(
InlineHintsOptions
.
HideForParametersThatDifferBySuffix
);
var
hideForParametersThatMatchMethodIntent
=
options
.
GetOption
(
InlineHintsOptions
.
HideForParametersThatMatchMethodIntent
);
var
root
=
await
document
.
GetRequiredSyntaxRootAsync
(
cancellationToken
).
ConfigureAwait
(
false
);
var
root
=
await
document
.
GetRequiredSyntaxRootAsync
(
cancellationToken
).
ConfigureAwait
(
false
);
var
semanticModel
=
await
document
.
GetRequiredSemanticModelAsync
(
cancellationToken
).
ConfigureAwait
(
false
);
var
semanticModel
=
await
document
.
GetRequiredSemanticModelAsync
(
cancellationToken
).
ConfigureAwait
(
false
);
using
var
_
=
ArrayBuilder
<
InlineParameterHint
>.
GetInstance
(
out
var
result
);
using
var
_1
=
ArrayBuilder
<
InlineParameterHint
>.
GetInstance
(
out
var
result
);
Action
<
InlineParameterHint
>
addHint
=
AddHint
;
Action
<
InlineParameterHint
>
addHint
=
AddHint
;
foreach
(
var
node
in
root
.
DescendantNodes
(
textSpan
))
foreach
(
var
node
in
root
.
DescendantNodes
(
textSpan
))
{
{
cancellationToken
.
ThrowIfCancellationRequested
();
cancellationToken
.
ThrowIfCancellationRequested
();
AddAllParameterNameHintLocations
(
semanticModel
,
node
,
addHint
,
cancellationToken
);
AddAllParameterNameHintLocations
(
semanticModel
,
node
,
addHint
,
hideForParametersThatDifferBySuffix
,
hideForParametersThatMatchMethodIntent
,
cancellationToken
);
}
}
return
result
.
ToImmutable
();
return
result
.
ToImmutable
();
...
@@ -64,5 +72,47 @@ private static bool HintMatches(InlineParameterHint hint, bool literalParameters
...
@@ -64,5 +72,47 @@ private static bool HintMatches(InlineParameterHint hint, bool literalParameters
InlineParameterHintKind
.
Other
=>
otherParameters
,
InlineParameterHintKind
.
Other
=>
otherParameters
,
_
=>
throw
ExceptionUtilities
.
UnexpectedValue
(
hint
.
Kind
),
_
=>
throw
ExceptionUtilities
.
UnexpectedValue
(
hint
.
Kind
),
};
};
protected
static
bool
MatchesMethodIntent
(
string
methodName
,
IParameterSymbol
parameter
)
{
// Check for something like `EnableLogging(true)`
if
(
TryGetIntent
(
"Enable"
,
methodName
,
out
_
)
||
TryGetIntent
(
"Disable"
,
methodName
,
out
_
))
{
return
parameter
.
Type
.
SpecialType
==
SpecialType
.
System_Boolean
;
}
// More names can be added here if we find other patterns like this.
if
(
TryGetIntent
(
"Set"
,
methodName
,
out
var
methodIntent
)
||
TryGetIntent
(
"From"
,
methodName
,
out
methodIntent
))
{
return
IntentNameMatchesParameterName
(
methodIntent
.
Value
,
parameter
.
Name
);
}
return
false
;
static
bool
TryGetIntent
(
string
prefix
,
string
nameValue
,
[
NotNullWhen
(
true
)]
out
ReadOnlyMemory
<
char
>?
result
)
{
if
(
nameValue
.
Length
>
prefix
.
Length
&&
nameValue
.
StartsWith
(
prefix
)
&&
char
.
IsUpper
(
nameValue
[
prefix
.
Length
]))
{
result
=
nameValue
.
AsMemory
().
Slice
(
prefix
.
Length
);
return
true
;
}
result
=
null
;
return
false
;
}
static
bool
IntentNameMatchesParameterName
(
ReadOnlyMemory
<
char
>
intent
,
string
parameterName
)
{
// Method's name will be something like 'FromResult', so 'intent' will be 'Result' and parameterName
// will be 'result'. So we check if the first letters differ on case and the rest of the method
// matches.
return
char
.
ToLower
(
intent
.
Span
[
0
])
==
parameterName
[
0
]
&&
intent
.
Span
.
Slice
(
1
).
Equals
(
parameterName
.
AsSpan
().
Slice
(
1
),
StringComparison
.
Ordinal
);
}
}
}
}
}
}
src/Features/VisualBasic/Portable/InlineParameterNameHints/VisualBasicInlineParameterNameHintsService.vb
浏览文件 @
d53c1f47
...
@@ -23,14 +23,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InlineParameterNameHints
...
@@ -23,14 +23,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.InlineParameterNameHints
semanticModel
As
SemanticModel
,
semanticModel
As
SemanticModel
,
node
As
SyntaxNode
,
node
As
SyntaxNode
,
addHint
As
Action
(
Of
InlineParameterHint
),
addHint
As
Action
(
Of
InlineParameterHint
),
cancellationToken
As
CancellationToken
)
hideForParametersThatDifferBySuffix
As
Boolean
,
hideForParametersThatMatchMethodIntent
As
Boolean
,
CancellationToken
As
CancellationToken
)
Dim
simpleArgument
=
TryCast
(
node
,
SimpleArgumentSyntax
)
Dim
simpleArgument
=
TryCast
(
node
,
SimpleArgumentSyntax
)
If
simpleArgument
?
.
Expression
IsNot
Nothing
Then
If
simpleArgument
?
.
Expression
IsNot
Nothing
Then
If
Not
simpleArgument
.
IsNamed
AndAlso
simpleArgument
.
NameColonEquals
Is
Nothing
Then
If
Not
simpleArgument
.
IsNamed
AndAlso
simpleArgument
.
NameColonEquals
Is
Nothing
Then
Dim
param
=
simpleArgument
.
DetermineParameter
(
semanticModel
,
allowParamArray
:
=
False
,
c
ancellationToken
)
Dim
param
=
simpleArgument
.
DetermineParameter
(
semanticModel
,
allowParamArray
:
=
False
,
C
ancellationToken
)
If
Not
String
.
IsNullOrEmpty
(
param
?
.
Name
)
Then
If
Not
String
.
IsNullOrEmpty
(
param
?
.
Name
)
Then
addHint
(
New
InlineParameterHint
(
param
.
GetSymbolKey
(
c
ancellationToken
),
param
.
Name
,
simpleArgument
.
Span
.
Start
,
GetKind
(
simpleArgument
.
Expression
)))
addHint
(
New
InlineParameterHint
(
param
.
GetSymbolKey
(
C
ancellationToken
),
param
.
Name
,
simpleArgument
.
Span
.
Start
,
GetKind
(
simpleArgument
.
Expression
)))
End
If
End
If
End
If
End
If
End
If
End
If
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录