提交 f83a66e8 编写于 作者: C CyrusNajmabadi

Add a new dialog-driven way to generate a constructor.

上级 f7f5f26f
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
......
......@@ -56,7 +56,7 @@ protected override Task<Document> GetChangedDocumentAsync(CancellationToken canc
State state)
{
var factory = _document.GetLanguageService<SyntaxGenerator>();
for (int i = state.DelegatedConstructor.Parameters.Length; i < state.Parameters.Count; i++)
for (int i = state.DelegatedConstructor.Parameters.Length; i < state.Parameters.Length; i++)
{
var symbolName = state.SelectedMembers[i].Name;
var parameterName = state.Parameters[i].Name;
......
......@@ -43,7 +43,7 @@ public async Task<ImmutableArray<CodeAction>> AddConstructorParametersFromMember
if (info != null)
{
var state = State.Generate(this, document, textSpan, info.SelectedMembers, cancellationToken);
if (state != null)
if (state != null && state.MatchingConstructor == null)
{
return CreateCodeActions(document, state).AsImmutableOrNull();
}
......
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
......@@ -13,16 +14,17 @@ internal partial class AddConstructorParametersFromMembersCodeRefactoringProvide
private class State
{
public TextSpan TextSpan { get; private set; }
public IMethodSymbol MatchingConstructor { get; private set; }
public IMethodSymbol DelegatedConstructor { get; private set; }
public INamedTypeSymbol ContainingType { get; private set; }
public IList<ISymbol> SelectedMembers { get; private set; }
public List<IParameterSymbol> Parameters { get; private set; }
public ImmutableArray<ISymbol> SelectedMembers { get; private set; }
public ImmutableArray<IParameterSymbol> Parameters { get; private set; }
public static State Generate(
AddConstructorParametersFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
CancellationToken cancellationToken)
{
var state = new State();
......@@ -38,7 +40,7 @@ private class State
AddConstructorParametersFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
CancellationToken cancellationToken)
{
if (!selectedMembers.All(IsWritableInstanceFieldOrProperty))
......@@ -56,11 +58,7 @@ private class State
this.Parameters = service.DetermineParameters(selectedMembers);
if (service.HasMatchingConstructor(this.ContainingType, this.Parameters))
{
return false;
}
this.MatchingConstructor = service.GetMatchingConstructor(this.ContainingType, this.Parameters);
this.DelegatedConstructor = service.GetDelegatedConstructor(this.ContainingType, this.Parameters);
return this.DelegatedConstructor != null;
}
......
......@@ -56,9 +56,6 @@ protected override Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync
return Task.FromResult(operations);
}
public override string Title
{
get { return FeaturesResources.Extract_Interface; }
}
public override string Title => FeaturesResources.Extract_Interface;
}
}
}
\ No newline at end of file
......@@ -128,6 +128,8 @@
<Compile Include="CodeFixes\FixAllOccurrences\FixSomeCodeAction.cs" />
<Compile Include="AddPackage\InstallPackageDirectlyCodeAction.cs" />
<Compile Include="AddConstructorParametersFromMembers\State.cs" />
<Compile Include="PickMembers\IPickMembersService.cs" />
<Compile Include="PickMembers\PickMembersResult.cs" />
<Compile Include="GenerateFromMembers\SelectedMemberInfo.cs" />
<Compile Include="ImplementType\ImplementTypeOptions.cs" />
<Compile Include="ImplementType\ImplementTypeOptionsProvider.cs" />
......
......@@ -1188,6 +1188,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Generate constructor....
/// </summary>
internal static string Generate_constructor {
get {
return ResourceManager.GetString("Generate_constructor", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Generate constructor &apos;{0}({1})&apos;.
/// </summary>
......@@ -2139,6 +2148,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Pick members to be used as constructor parameters.
/// </summary>
internal static string Pick_members_to_be_used_as_constructor_parameters {
get {
return ResourceManager.GetString("Pick_members_to_be_used_as_constructor_parameters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to + additional {0} {1}.
/// </summary>
......
......@@ -1178,4 +1178,10 @@ This version used in: {2}</value>
<data name="Remove_separators" xml:space="preserve">
<value>Remove separators</value>
</data>
</root>
<data name="Generate_constructor" xml:space="preserve">
<value>Generate constructor...</value>
</data>
<data name="Pick_members_to_be_used_as_constructor_parameters" xml:space="preserve">
<value>Pick members to be used as constructor parameters</value>
</data>
</root>
\ 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 System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -42,10 +43,11 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
var factory = provider.GetService<SyntaxGenerator>();
var codeGenerationService = provider.GetService<ICodeGenerationService>();
var thisConstructorArguments = factory.CreateArguments(_state.DelegatedConstructor.Parameters);
var thisConstructorArguments = factory.CreateArguments(
_state.Parameters.Take(_state.DelegatedConstructor.Parameters.Length).ToImmutableArray());
var statements = new List<SyntaxNode>();
for (var i = _state.DelegatedConstructor.Parameters.Length; i < _state.Parameters.Count; i++)
for (var i = _state.DelegatedConstructor.Parameters.Length; i < _state.Parameters.Length; i++)
{
var symbolName = _state.SelectedMembers[i].Name;
var parameterName = _state.Parameters[i].Name;
......@@ -71,9 +73,10 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
parameters: _state.Parameters,
statements: statements,
thisConstructorArguments: thisConstructorArguments),
new CodeGenerationOptions(contextLocation: syntaxTree.GetLocation(_state.TextSpan)),
cancellationToken: cancellationToken)
.ConfigureAwait(false);
new CodeGenerationOptions(
contextLocation: syntaxTree.GetLocation(_state.TextSpan),
afterThisLocation: _state.TextSpan.IsEmpty ? syntaxTree.GetLocation(_state.TextSpan) : null),
cancellationToken: cancellationToken).ConfigureAwait(false);
return result;
}
......
......@@ -39,7 +39,7 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
// Otherwise, just generate a normal constructor that assigns any provided
// parameters into fields.
var parameterToExistingFieldMap = new Dictionary<string, ISymbol>();
for (int i = 0; i < _state.Parameters.Count; i++)
for (int i = 0; i < _state.Parameters.Length; i++)
{
parameterToExistingFieldMap[_state.Parameters[i].Name] = _state.SelectedMembers[i];
}
......@@ -59,9 +59,10 @@ protected override async Task<Document> GetChangedDocumentAsync(CancellationToke
_document.Project.Solution,
_state.ContainingType,
members,
new CodeGenerationOptions(contextLocation: syntaxTree.GetLocation(_state.TextSpan)),
cancellationToken)
.ConfigureAwait(false);
new CodeGenerationOptions(
contextLocation: syntaxTree.GetLocation(_state.TextSpan),
afterThisLocation: _state.TextSpan.IsEmpty ? syntaxTree.GetLocation(_state.TextSpan) : null),
cancellationToken).ConfigureAwait(false);
return result;
}
......
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.GenerateFromMembers;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.GenerateConstructorFromMembers
......@@ -31,6 +36,52 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
var actions = await this.GenerateConstructorFromMembersAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
context.RegisterRefactorings(actions);
if (actions.IsDefaultOrEmpty && textSpan.IsEmpty)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
if (syntaxFacts.IsOnTypeHeader(root, textSpan.Start) ||
syntaxFacts.IsBetweenTypeMembers(sourceText, root, textSpan.Start))
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var containingType = GetEnclosingNamedType(semanticModel, root, textSpan.Start, cancellationToken);
if (containingType?.TypeKind == TypeKind.Class || containingType?.TypeKind == TypeKind.Struct)
{
if (!containingType.IsStatic)
{
var viableMembers = containingType.GetMembers().WhereAsArray(IsWritableInstanceFieldOrProperty);
if (viableMembers.Length == 0 &&
containingType.InstanceConstructors.Any(c => c.Parameters.Length == 0 && !c.IsImplicitlyDeclared))
{
// If there are no fields, and there's already an explicit empty parameter constructor
// then we can't offer anything.
return;
}
var action = new GenerateConstructorCodeAction(
this, document, textSpan, containingType, viableMembers);
context.RegisterRefactoring(action);
}
}
}
}
}
private INamedTypeSymbol GetEnclosingNamedType(
SemanticModel semanticModel, SyntaxNode root, int start, CancellationToken cancellationToken)
{
for (var node = root.FindToken(start).Parent; node != null; node = node.Parent)
{
if (semanticModel.GetDeclaredSymbol(node) is INamedTypeSymbol declaration)
{
return declaration;
}
}
return null;
}
public async Task<ImmutableArray<CodeAction>> GenerateConstructorFromMembersAsync(
......@@ -42,9 +93,9 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
if (info != null)
{
var state = State.Generate(this, document, textSpan, info.ContainingType, info.SelectedMembers, cancellationToken);
if (state != null)
if (state != null && state.MatchingConstructor == null)
{
return GetCodeActions(document, state).AsImmutableOrNull();
return GetCodeActions(document, state);
}
}
......@@ -52,12 +103,89 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte
}
}
private IEnumerable<CodeAction> GetCodeActions(Document document, State state)
private ImmutableArray<CodeAction> GetCodeActions(Document document, State state)
{
yield return new FieldDelegatingCodeAction(this, document, state);
var result = ArrayBuilder<CodeAction>.GetInstance();
result.Add(new FieldDelegatingCodeAction(this, document, state));
if (state.DelegatedConstructor != null)
{
yield return new ConstructorDelegatingCodeAction(this, document, state);
result.Add(new ConstructorDelegatingCodeAction(this, document, state));
}
return result.ToImmutableAndFree();
}
private class GenerateConstructorCodeAction : CodeActionWithOptions
{
private readonly Document _document;
private readonly INamedTypeSymbol _containingType;
private readonly GenerateConstructorFromMembersCodeRefactoringProvider _service;
private readonly TextSpan _textSpan;
private readonly ImmutableArray<ISymbol> _viableMembers;
public override string Title => FeaturesResources.Generate_constructor;
public GenerateConstructorCodeAction(
GenerateConstructorFromMembersCodeRefactoringProvider service,
Document document, TextSpan textSpan,
INamedTypeSymbol containingType,
ImmutableArray<ISymbol> viableMembers)
{
_service = service;
_document = document;
_textSpan = textSpan;
_containingType = containingType;
_viableMembers = viableMembers;
}
public override object GetOptions(CancellationToken cancellationToken)
{
var workspace = _document.Project.Solution.Workspace;
var service = workspace.Services.GetService<IPickMembersService>();
return service.PickMembers(
FeaturesResources.Pick_members_to_be_used_as_constructor_parameters, _viableMembers);
}
protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(
object options, CancellationToken cancellationToken)
{
var result = (PickMembersResult)options;
if (result.IsCanceled)
{
return ImmutableArray<CodeActionOperation>.Empty;
}
var state = State.Generate(
_service, _document, _textSpan, _containingType,
result.Members, cancellationToken);
// There was an existing constructor that matched what the user wants to create.
// Generate it if it's the implicit, no-arg, constructor, otherwise just navigate
// to the existing constructor
if (state.MatchingConstructor != null)
{
if (state.MatchingConstructor.IsImplicitlyDeclared)
{
var codeAction = new FieldDelegatingCodeAction(_service, _document, state);
return await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
var constructorReference = state.MatchingConstructor.DeclaringSyntaxReferences[0];
var constructorSyntax = await constructorReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
var constructorTree = constructorSyntax.SyntaxTree;
var constructorDocument = _document.Project.Solution.GetDocument(constructorTree);
return ImmutableArray.Create<CodeActionOperation>(new DocumentNavigationOperation(
constructorDocument.Id, constructorSyntax.SpanStart));
}
else
{
var codeAction = state.DelegatedConstructor != null
? new ConstructorDelegatingCodeAction(_service, _document, state)
: (CodeAction)new FieldDelegatingCodeAction(_service, _document, state);
return await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
}
}
}
}
......
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
......@@ -13,17 +14,18 @@ internal partial class GenerateConstructorFromMembersCodeRefactoringProvider
private class State
{
public TextSpan TextSpan { get; private set; }
public IMethodSymbol MatchingConstructor { get; private set; }
public IMethodSymbol DelegatedConstructor { get; private set; }
public INamedTypeSymbol ContainingType { get; private set; }
public IList<ISymbol> SelectedMembers { get; private set; }
public List<IParameterSymbol> Parameters { get; private set; }
public ImmutableArray<ISymbol> SelectedMembers { get; private set; }
public ImmutableArray<IParameterSymbol> Parameters { get; private set; }
public static State Generate(
GenerateConstructorFromMembersCodeRefactoringProvider service,
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
CancellationToken cancellationToken)
{
var state = new State();
......@@ -40,7 +42,7 @@ private class State
Document document,
TextSpan textSpan,
INamedTypeSymbol containingType,
IList<ISymbol> selectedMembers,
ImmutableArray<ISymbol> selectedMembers,
CancellationToken cancellationToken)
{
if (!selectedMembers.All(IsWritableInstanceFieldOrProperty))
......@@ -57,15 +59,10 @@ private class State
}
this.Parameters = service.DetermineParameters(selectedMembers);
if (service.HasMatchingConstructor(this.ContainingType, this.Parameters))
{
return false;
}
this.MatchingConstructor = service.GetMatchingConstructor(this.ContainingType, this.Parameters);
this.DelegatedConstructor = service.GetDelegatedConstructor(this.ContainingType, this.Parameters);
return true;
}
}
}
}
}
\ No newline at end of file
......@@ -60,7 +60,7 @@ private static bool IsWritableFieldOrProperty(ISymbol symbol)
{
switch (symbol)
{
case IFieldSymbol field: return !field.IsConst;
case IFieldSymbol field: return !field.IsConst && field.AssociatedSymbol == null;
case IPropertySymbol property: return property.IsWritableInConstructor();
default: return false;
}
......@@ -75,10 +75,10 @@ private static bool IsProperty(ISymbol symbol)
private static bool IsField(ISymbol symbol)
=> symbol.Kind == SymbolKind.Field;
protected List<IParameterSymbol> DetermineParameters(
IList<ISymbol> selectedMembers)
protected ImmutableArray<IParameterSymbol> DetermineParameters(
ImmutableArray<ISymbol> selectedMembers)
{
var parameters = new List<IParameterSymbol>();
var parameters = ArrayBuilder<IParameterSymbol>.GetInstance();
foreach (var symbol in selectedMembers)
{
......@@ -94,19 +94,19 @@ private static bool IsField(ISymbol symbol)
name: symbol.Name.ToCamelCase().TrimStart(s_underscore)));
}
return parameters;
return parameters.ToImmutableAndFree();
}
private static readonly char[] s_underscore = { '_' };
protected IMethodSymbol GetDelegatedConstructor(
INamedTypeSymbol containingType,
List<IParameterSymbol> parameters)
ImmutableArray<IParameterSymbol> parameters)
{
var q =
from c in containingType.InstanceConstructors
orderby c.Parameters.Length descending
where c.Parameters.Length > 0 && c.Parameters.Length < parameters.Count
where c.Parameters.Length > 0 && c.Parameters.Length < parameters.Length
where c.Parameters.All(p => p.RefKind == RefKind.None) && !c.Parameters.Any(p => p.IsParams)
let constructorTypes = c.Parameters.Select(p => p.Type)
let symbolTypes = parameters.Take(c.Parameters.Length).Select(p => p.Type)
......@@ -116,19 +116,11 @@ where constructorTypes.SequenceEqual(symbolTypes)
return q.FirstOrDefault();
}
protected bool HasMatchingConstructor(
INamedTypeSymbol containingType,
List<IParameterSymbol> parameters)
{
return containingType.InstanceConstructors.Any(c => MatchesConstructor(c, parameters));
}
protected IMethodSymbol GetMatchingConstructor(INamedTypeSymbol containingType, ImmutableArray<IParameterSymbol> parameters)
=> containingType.InstanceConstructors.FirstOrDefault(c => MatchesConstructor(c, parameters));
private bool MatchesConstructor(
IMethodSymbol constructor,
List<IParameterSymbol> parameters)
{
return parameters.Select(p => p.Type).SequenceEqual(constructor.Parameters.Select(p => p.Type));
}
private bool MatchesConstructor(IMethodSymbol constructor, ImmutableArray<IParameterSymbol> parameters)
=> parameters.Select(p => p.Type).SequenceEqual(constructor.Parameters.Select(p => p.Type));
protected static readonly SymbolDisplayFormat SimpleFormat =
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.Collections.Immutable;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.PickMembers
{
internal interface IPickMembersService : IWorkspaceService
{
PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> members);
}
}
\ 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 System.Collections.Immutable;
namespace Microsoft.CodeAnalysis.PickMembers
{
internal class PickMembersResult
{
public static readonly PickMembersResult Canceled = new PickMembersResult(isCanceled: true);
public readonly bool IsCanceled;
public readonly ImmutableArray<ISymbol> Members;
private PickMembersResult(bool isCanceled)
{
IsCanceled = isCanceled;
}
public PickMembersResult(ImmutableArray<ISymbol> members)
{
Members = members;
}
}
}
\ No newline at end of file
<vs:DialogWindow x:Uid="PickMembersDialog"
x:Class="Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers.PickMembersDialog"
x:ClassModifier="internal"
x:Name="dialog"
xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:s="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns:u="clr-namespace:Microsoft.VisualStudio.LanguageServices.Implementation.Utilities"
xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
mc:Ignorable="d"
d:DesignHeight="380" d:DesignWidth="460"
Height="380" Width="460"
MinHeight="380" MinWidth="460"
Title="{Binding ElementName=dialog, Path=PickMembersDialogTitle}"
HasHelpButton="True"
FocusManager.FocusedElement="{Binding ElementName=interfaceNameTextBox}"
ResizeMode="CanResizeWithGrip"
ShowInTaskbar="False"
HasDialogFrame="True"
WindowStartupLocation="CenterOwner">
<Window.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsTabStop"
Value="False" />
</Style>
<Thickness x:Key="labelPadding">0, 5, 0, 2</Thickness>
<Thickness x:Key="okCancelButtonPadding">9,2,9,2</Thickness>
<Thickness x:Key="selectDeselectButtonPadding">9,2,9,2</Thickness>
<Thickness x:Key="textboxPadding">2</Thickness>
<vs:NegateBooleanConverter x:Key="NegateBooleanConverter"/>
<RoutedUICommand x:Key="MoveUp" />
<RoutedUICommand x:Key="MoveDown" />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource MoveUp}" Executed="MoveUp_Click" />
<CommandBinding Command="{StaticResource MoveDown}" Executed="MoveDown_Click" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="Up" Modifiers="Alt" Command="{StaticResource MoveUp}" />
<KeyBinding Key="Down" Modifiers="Alt" Command="{StaticResource MoveDown}" />
</Window.InputBindings>
<Grid Margin="11,6,11,11">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox x:Uid="MemberSelectionGroupBox"
Margin="0, 9, 0, 0"
Grid.Row="0"
Header="{Binding ElementName=dialog, Path=PickMembersTitle}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<u:AutomationDelegatingListView x:Uid="MemberSelectionList"
x:Name="Members"
Grid.Column="0"
Margin="9"
SelectionMode="Single"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
PreviewKeyDown="OnListViewPreviewKeyDown"
MouseDoubleClick="OnListViewDoubleClick"
ItemsSource="{Binding MemberContainers, Mode=TwoWay}">
<u:AutomationDelegatingListView.ItemTemplate x:Uid="SelectableMemberListItem">
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox x:Uid="SelectableMemberCheckBox"
AutomationProperties.Name="{Binding MemberAutomationText}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Width="Auto"
Focusable="False"
AutomationProperties.AutomationId="{Binding MemberName}">
</CheckBox>
<Image x:Uid="SelectableMemberGlyph"
Margin="8,0,0,0"
Source="{Binding Glyph}"/>
<TextBlock x:Uid="SelectableMemberName"
Text="{Binding MemberName}"/>
</StackPanel>
</DataTemplate>
</u:AutomationDelegatingListView.ItemTemplate>
</u:AutomationDelegatingListView>
<StackPanel Grid.Column="1">
<vs:DialogButton Name="UpButton"
AutomationProperties.Name="{Binding MoveUpAutomationText}"
Margin="0 9 4 0"
IsEnabled="{Binding CanMoveUp, Mode=OneWay}"
AutomationProperties.AutomationId="Up"
Height="Auto" Width="Auto"
Command="{StaticResource MoveUp}">
<imaging:CrispImage Name="UpArrowImage"
Height="16"
Width="16"
Moniker="{x:Static imagecatalog:KnownMonikers.MoveUp}"
Grayscale="{Binding IsEnabled, ElementName=UpButton, Converter={StaticResource NegateBooleanConverter}}"/>
</vs:DialogButton>
<vs:DialogButton Name="DownButton"
AutomationProperties.Name="{Binding MoveDownAutomationText}"
Margin="0 9 4 0"
IsEnabled="{Binding CanMoveDown, Mode=OneWay}"
AutomationProperties.AutomationId="Down"
Height="Auto" Width="Auto"
Command="{StaticResource MoveDown}">
<imaging:CrispImage Name="DownArrowImage"
Height="16"
Width="16"
Moniker="{x:Static imagecatalog:KnownMonikers.MoveDown}"
Grayscale="{Binding IsEnabled, ElementName=DownButton, Converter={StaticResource NegateBooleanConverter}}"/>
</vs:DialogButton>
<Button x:Uid="SelectAllButton"
Content="{Binding ElementName=dialog, Path=SelectAll}"
Margin="0 29 4 0"
Click="Select_All_Click"
Padding="{StaticResource ResourceKey=selectDeselectButtonPadding}"
MinWidth="73"
MinHeight="21"/>
<Button x:Uid="DeselectAllButton"
Content="{Binding ElementName=dialog, Path=DeselectAll}"
Margin="0 9 4 0"
Padding="{StaticResource ResourceKey=selectDeselectButtonPadding}"
Click="Deselect_All_Click"
MinWidth="73"
MinHeight="21"/>
</StackPanel>
</Grid>
</GroupBox>
<StackPanel Grid.Row="1"
HorizontalAlignment="Right"
Margin="0, 11, 0, 0"
Orientation="Horizontal">
<Button x:Uid="OkButton"
Content="{Binding ElementName=dialog, Path=OK}"
Margin="0, 0, 0, 0"
Padding="{StaticResource ResourceKey=okCancelButtonPadding}"
Click="OK_Click"
IsDefault="True"
MinWidth="73"
MinHeight="21"/>
<Button x:Uid="CancelButton"
Content="{Binding ElementName=dialog, Path=Cancel}"
Margin="7, 0, 0, 0"
Padding="{StaticResource ResourceKey=okCancelButtonPadding}"
Click="Cancel_Click"
IsCancel="True"
MinWidth="73"
MinHeight="21"/>
</StackPanel>
</Grid>
</vs:DialogWindow>
\ 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 System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Microsoft.VisualStudio.PlatformUI;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers
{
/// <summary>
/// Interaction logic for ExtractInterfaceDialog.xaml
/// </summary>
internal partial class PickMembersDialog : DialogWindow
{
private readonly PickMembersDialogViewModel _viewModel;
/// <summary>
/// For test purposes only. The integration tests need to know when the dialog is up and
/// ready for automation.
/// </summary>
internal static event Action TEST_DialogLoaded;
// Expose localized strings for binding
public string PickMembersDialogTitle => ServicesVSResources.Pick_members;
public string PickMembersTitle { get; }
public string SelectAll => ServicesVSResources.Select_All;
public string DeselectAll => ServicesVSResources.Deselect_All;
public string OK => ServicesVSResources.OK;
public string Cancel => ServicesVSResources.Cancel;
internal PickMembersDialog(PickMembersDialogViewModel viewModel, string title)
{
PickMembersTitle = title;
_viewModel = viewModel;
SetCommandBindings();
InitializeComponent();
DataContext = viewModel;
IsVisibleChanged += PickMembers_IsVisibleChanged;
}
private void PickMembers_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
IsVisibleChanged -= PickMembers_IsVisibleChanged;
TEST_DialogLoaded?.Invoke();
}
}
private void SetCommandBindings()
{
CommandBindings.Add(new CommandBinding(
new RoutedCommand(
"SelectAllClickCommand",
typeof(PickMembersDialog),
new InputGestureCollection(new List<InputGesture> { new KeyGesture(Key.S, ModifierKeys.Alt) })),
Select_All_Click));
CommandBindings.Add(new CommandBinding(
new RoutedCommand(
"DeselectAllClickCommand",
typeof(PickMembersDialog),
new InputGestureCollection(new List<InputGesture> { new KeyGesture(Key.D, ModifierKeys.Alt) })),
Deselect_All_Click));
}
private void OK_Click(object sender, RoutedEventArgs e)
=> DialogResult = true;
private void Cancel_Click(object sender, RoutedEventArgs e)
=> DialogResult = false;
private void Select_All_Click(object sender, RoutedEventArgs e)
=> _viewModel.SelectAll();
private void Deselect_All_Click(object sender, RoutedEventArgs e)
=> _viewModel.DeselectAll();
private void MoveUp_Click(object sender, EventArgs e)
{
int oldSelectedIndex = Members.SelectedIndex;
if (_viewModel.CanMoveUp && oldSelectedIndex >= 0)
{
_viewModel.MoveUp();
Members.Items.Refresh();
Members.SelectedIndex = oldSelectedIndex - 1;
}
SetFocusToSelectedRow();
}
private void MoveDown_Click(object sender, EventArgs e)
{
int oldSelectedIndex = Members.SelectedIndex;
if (_viewModel.CanMoveDown && oldSelectedIndex >= 0)
{
_viewModel.MoveDown();
Members.Items.Refresh();
Members.SelectedIndex = oldSelectedIndex + 1;
}
SetFocusToSelectedRow();
}
private void SetFocusToSelectedRow()
{
if (Members.SelectedIndex >= 0)
{
var row = Members.ItemContainerGenerator.ContainerFromIndex(Members.SelectedIndex) as ListViewItem;
if (row == null)
{
Members.ScrollIntoView(Members.SelectedItem);
row = Members.ItemContainerGenerator.ContainerFromIndex(Members.SelectedIndex) as ListViewItem;
}
row?.Focus();
}
}
private void OnListViewPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space && e.KeyboardDevice.Modifiers == ModifierKeys.None)
{
ToggleCheckSelection();
e.Handled = true;
}
}
private void OnListViewDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
ToggleCheckSelection();
e.Handled = true;
}
}
private void ToggleCheckSelection()
{
var selectedItems = Members.SelectedItems.OfType<PickMembersDialogViewModel.MemberSymbolViewModel>().ToArray();
var allChecked = selectedItems.All(m => m.IsChecked);
foreach (var item in selectedItems)
{
item.IsChecked = !allChecked;
}
}
}
}
\ 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 System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Windows.Media;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers
{
internal class PickMembersDialogViewModel : AbstractNotifyPropertyChanged
{
internal PickMembersDialogViewModel(
IGlyphService glyphService,
ImmutableArray<ISymbol> members)
{
MemberContainers = members.Select(m => new MemberSymbolViewModel(m, glyphService)).OrderBy(s => s.MemberName).ToList();
}
internal void DeselectAll()
{
foreach (var memberContainer in MemberContainers)
{
memberContainer.IsChecked = false;
}
}
internal void SelectAll()
{
foreach (var memberContainer in MemberContainers)
{
memberContainer.IsChecked = true;
}
}
private int? _selectedIndex;
public int? SelectedIndex
{
get
{
return _selectedIndex;
}
set
{
var newSelectedIndex = value == -1 ? null : value;
if (newSelectedIndex == _selectedIndex)
{
return;
}
_selectedIndex = newSelectedIndex;
NotifyPropertyChanged(nameof(CanMoveUp));
NotifyPropertyChanged(nameof(MoveUpAutomationText));
NotifyPropertyChanged(nameof(CanMoveDown));
NotifyPropertyChanged(nameof(MoveDownAutomationText));
}
}
public string MoveUpAutomationText
{
get
{
if (!CanMoveUp)
{
return string.Empty;
}
return string.Format(ServicesVSResources.Move_0_above_1, MemberContainers[SelectedIndex.Value].MemberAutomationText, MemberContainers[SelectedIndex.Value - 1].MemberAutomationText);
}
}
public string MoveDownAutomationText
{
get
{
if (!CanMoveDown)
{
return string.Empty;
}
return string.Format(ServicesVSResources.Move_0_below_1, MemberContainers[SelectedIndex.Value].MemberAutomationText, MemberContainers[SelectedIndex.Value + 1].MemberAutomationText);
}
}
public bool CanMoveUp
{
get
{
if (!SelectedIndex.HasValue)
{
return false;
}
var index = SelectedIndex.Value;
return index > 0;
}
}
public bool CanMoveDown
{
get
{
if (!SelectedIndex.HasValue)
{
return false;
}
var index = SelectedIndex.Value;
return index < MemberContainers.Count - 1;
}
}
internal void MoveUp()
{
Debug.Assert(CanMoveUp);
var index = SelectedIndex.Value;
Move(MemberContainers, index, delta: -1);
}
internal void MoveDown()
{
Debug.Assert(CanMoveDown);
var index = SelectedIndex.Value;
Move(MemberContainers, index, delta: 1);
}
private void Move(List<MemberSymbolViewModel> list, int index, int delta)
{
var param = list[index];
list.RemoveAt(index);
list.Insert(index + delta, param);
SelectedIndex += delta;
//NotifyPropertyChanged(nameof(AllParameters));
//NotifyPropertyChanged(nameof(SignatureDisplay));
//NotifyPropertyChanged(nameof(SignaturePreviewAutomationText));
//NotifyPropertyChanged(nameof(IsOkButtonEnabled));
}
public List<MemberSymbolViewModel> MemberContainers { get; set; }
internal class MemberSymbolViewModel : AbstractNotifyPropertyChanged
{
private readonly IGlyphService _glyphService;
public ISymbol MemberSymbol { get; }
private static SymbolDisplayFormat s_memberDisplayFormat = new SymbolDisplayFormat(
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeParamsRefOut | SymbolDisplayParameterOptions.IncludeOptionalBrackets,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
public MemberSymbolViewModel(ISymbol symbol, IGlyphService glyphService)
{
MemberSymbol = symbol;
_glyphService = glyphService;
_isChecked = true;
}
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
public string MemberName => MemberSymbol.ToDisplayString(s_memberDisplayFormat);
public ImageSource Glyph => MemberSymbol.GetGlyph().GetImageSource(_glyphService);
public string MemberAutomationText => MemberSymbol.Kind + " " + MemberName;
}
}
}
\ 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 System.Collections.Immutable;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PickMembers;
using Microsoft.VisualStudio.Language.Intellisense;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.PickMembers
{
[ExportWorkspaceService(typeof(IPickMembersService), ServiceLayer.Host), Shared]
internal class VisualStudioPickMembersService : IPickMembersService
{
private readonly IGlyphService _glyphService;
[ImportingConstructor]
public VisualStudioPickMembersService(IGlyphService glyphService)
{
_glyphService = glyphService;
}
public PickMembersResult PickMembers(string title, ImmutableArray<ISymbol> members)
{
var viewModel = new PickMembersDialogViewModel(_glyphService, members);
var dialog = new PickMembersDialog(viewModel, title);
var result = dialog.ShowModal();
if (result.HasValue && result.Value)
{
return new PickMembersResult(
viewModel.MemberContainers.Where(c => c.IsChecked)
.Select(c => c.MemberSymbol)
.ToImmutableArray());
}
else
{
return PickMembersResult.Canceled;
}
}
}
}
......@@ -1457,6 +1457,15 @@ internal class ServicesVSResources {
}
}
/// <summary>
/// Looks up a localized string similar to Pick members.
/// </summary>
internal static string Pick_members {
get {
return ResourceManager.GetString("Pick_members", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prefer braces.
/// </summary>
......
......@@ -867,4 +867,7 @@ Additional information: {1}</value>
<data name="Remove" xml:space="preserve">
<value>Remove</value>
</data>
<data name="Pick_members" xml:space="preserve">
<value>Pick members</value>
</data>
</root>
\ No newline at end of file
......@@ -86,6 +86,11 @@
<Compile Include="Implementation\AnalyzerDependency\MissingAnalyzerDependency.cs" />
<Compile Include="Implementation\Options\LocalUserRegistryOptionPersister.cs" />
<Compile Include="Implementation\Options\RoamingVisualStudioProfileOptionPersister.cs" />
<Compile Include="Implementation\PickMembers\PickMembersDialog.xaml.cs">
<DependentUpon>PickMembersDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Implementation\PickMembers\PickMembersDialogViewModel.cs" />
<Compile Include="Implementation\PickMembers\VisualStudioPickMembersService.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.MetadataReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.AnalyzerReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.ProjectReferenceChange.cs" />
......@@ -654,6 +659,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Implementation\PickMembers\PickMembersDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Implementation\PreviewPane\PreviewPane.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
......@@ -695,6 +704,7 @@
<SubType>Designer</SubType>
</VSCTCompile>
</ItemGroup>
<ItemGroup />
<Import Project="..\..\..\Dependencies\CodeAnalysis.Metadata\Microsoft.CodeAnalysis.Metadata.projitems" Label="Shared" />
<Import Project="..\..\..\..\build\Targets\Imports.targets" />
</Project>
\ No newline at end of file
......@@ -1890,5 +1890,69 @@ public override SyntaxNode Visit(SyntaxNode node)
return rewritten;
}
}
public bool IsOnTypeHeader(SyntaxNode root, int position)
{
var token = root.FindToken(position);
var typeDecl = token.GetAncestor<TypeDeclarationSyntax>();
if (typeDecl == null)
{
return false;
}
var start = typeDecl.AttributeLists.LastOrDefault()?.GetLastToken().GetNextToken().SpanStart ??
typeDecl.SpanStart;
var end = typeDecl.TypeParameterList?.GetLastToken().Span.End ??
typeDecl.Identifier.Span.End;
return position >= start && position <= end;
}
public bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int position)
{
var token = root.FindToken(position);
var typeDecl = token.GetAncestor<TypeDeclarationSyntax>();
if (typeDecl != null)
{
if (position >= typeDecl.OpenBraceToken.Span.End &&
position <= typeDecl.CloseBraceToken.Span.Start)
{
var line = sourceText.Lines.GetLineFromPosition(position);
if (!line.IsEmptyOrWhitespace())
{
return false;
}
var member = typeDecl.Members.FirstOrDefault(d => d.FullSpan.Contains(position));
if (member == null)
{
// There are no members, or we're after the last member.
return true;
}
else
{
// We're within a member. Make sure we're in the leading whitespace of
// the member.
if (position < member.SpanStart)
{
foreach (var trivia in member.GetLeadingTrivia())
{
if (!trivia.IsWhitespaceOrEndOfLine())
{
return false;
}
if (trivia.FullSpan.Contains(position))
{
return true;
}
}
}
}
}
}
return false;
}
}
}
\ No newline at end of file
......@@ -44,7 +44,6 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsDocumentationComment(SyntaxTrivia trivia);
bool IsDocumentationComment(SyntaxNode node);
bool IsNumericLiteralExpression(SyntaxNode node);
bool IsNullLiteralExpression(SyntaxNode node);
......@@ -255,6 +254,9 @@ internal interface ISyntaxFactsService : ILanguageService
/// </summary>
string GetNameForArgument(SyntaxNode argument);
bool IsOnTypeHeader(SyntaxNode root, int position);
bool IsBetweenTypeMembers(SourceText sourceText, SyntaxNode root, int position);
// Walks the tree, starting from contextNode, looking for the first construct
// with a missing close brace. If found, the close brace will be added and the
// updates root will be returned. The context node in that new tree will also
......
......@@ -1640,5 +1640,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Public Function IsDocumentationComment(trivia As SyntaxTrivia) As Boolean Implements ISyntaxFactsService.IsDocumentationComment
Return trivia.Kind = SyntaxKind.DocumentationCommentTrivia
End Function
Public Function IsOnTypeHeader(root As SyntaxNode, position As Integer) As Boolean Implements ISyntaxFactsService.IsOnTypeHeader
Return False
End Function
Public Function IsBetweenTypeMembers(sourceText As SourceText, root As SyntaxNode, position As Integer) As Boolean Implements ISyntaxFactsService.IsBetweenTypeMembers
Return False
End Function
End Class
End Namespace
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册