Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context actions for inspector attributes(Space, Header, Tooltip, Range) + refactor existing #1244

Merged
merged 4 commits into from
Jul 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using JetBrains.Application.Progress;
using JetBrains.Application.UI.Controls.BulbMenu.Anchors;
using JetBrains.DocumentModel;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.ContextActions;
using JetBrains.ReSharper.Feature.Services.CSharp.Analyses.Bulbs;
using JetBrains.ReSharper.Feature.Services.Intentions;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Impl;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.Unity.CSharp.Feature.Services.ContextActions
{
[ContextAction(Group = UnityContextActions.GroupID,
Name = "Add 'Header' attribute",
Description =
"Add header for this property in the Unity Editor inspector")]
public class AddHeaderAttributeAction : AddInspectorAttributeAction
{
[NotNull] private static readonly SubmenuAnchor ourSubmenuAnchor = new SubmenuAnchor(ourBaseAnchor, SubmenuBehavior.Executable);
protected override IClrTypeName AttributeTypeName => KnownTypes.Header;

public AddHeaderAttributeAction(ICSharpContextActionDataProvider dataProvider) : base(dataProvider, ourSubmenuAnchor)
{
}

public override BulbActionBase GetActionForOne(IMultipleFieldDeclaration multipleFieldDeclaration, IFieldDeclaration fieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute)
{
return new AddHEaderActionOne(multipleFieldDeclaration.Declarators.Count, fieldDeclaration, module);
}

public override BulbActionBase GetActionForAll(IMultipleFieldDeclaration multipleFieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute)
{
return new AddSpaceActionAll(multipleFieldDeclaration, module, elementFactory);
}

private class AddHEaderActionOne : BulbActionBase
{
private readonly int myDeclaratorsCount;
private readonly IFieldDeclaration myFieldDeclaration;
private readonly IPsiModule myModule;

public AddHEaderActionOne(int declaratorsCount, IFieldDeclaration fieldDeclaration, IPsiModule module)
{
myDeclaratorsCount = declaratorsCount;
myFieldDeclaration = fieldDeclaration;
myModule = module;
}

protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
return ExecuteHotspotSession(solution, myFieldDeclaration, myModule);
}

public override string Text => myDeclaratorsCount == 1 ? "Add 'Header'" : $"Annotate '{myFieldDeclaration.DeclaredName}' with 'Header' attribute";
}

private class AddSpaceActionAll : BulbActionBase
{
private readonly IMultipleFieldDeclaration myMultipleFieldDeclaration;
private readonly IPsiModule myModule;

public AddSpaceActionAll(IMultipleFieldDeclaration multipleFieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory)
{
myMultipleFieldDeclaration = multipleFieldDeclaration;
myModule = module;
}

protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
var fieldDeclaration = (IFieldDeclaration) myMultipleFieldDeclaration.Declarators[0];
return ExecuteHotspotSession(solution, fieldDeclaration, myModule);
}

public override string Text => "Annotate all fields with 'Header' attribute";
}

private static Action<ITextControl> ExecuteHotspotSession(ISolution solution, IFieldDeclaration fieldDeclaration, IPsiModule module)
{
AddAttribute(KnownTypes.Header, fieldDeclaration,
new[] {new AttributeValue(new ConstantValue(fieldDeclaration.NameIdentifier.Name, module))}, module);


var hotspotsRegistry = new HotspotsRegistry(solution.GetPsiServices());

var attribute = fieldDeclaration.Attributes.First(t =>
(t.TypeReference.Resolve().DeclaredElement as IClass).GetClrName().Equals(KnownTypes.Header));

hotspotsRegistry.Register(new[] {attribute.Arguments[0]}, new NameSuggestionsExpression(new List<string>() {fieldDeclaration.NameIdentifier.Name}));

return BulbActionUtils.ExecuteHotspotSession(hotspotsRegistry, DocumentOffset.InvalidOffset);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using JetBrains.Application.UI.Controls.BulbMenu.Anchors;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.ContextActions;
using JetBrains.ReSharper.Feature.Services.CSharp.Analyses.Bulbs;
using JetBrains.ReSharper.Feature.Services.Intentions;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Impl;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.Unity.CSharp.Feature.Services.ContextActions
{
public abstract class AddInspectorAttributeAction : IContextAction
{
[NotNull] protected static readonly SubmenuAnchor ourBaseAnchor =
new SubmenuAnchor(IntentionsAnchors.ContextActionsAnchor, SubmenuBehavior.Static("Modify Inspector attribute"));

protected readonly ICSharpContextActionDataProvider DataProvider;
private readonly IAnchor myAnchor;

public AddInspectorAttributeAction(ICSharpContextActionDataProvider dataProvider, IAnchor anchor)
{
DataProvider = dataProvider;
myAnchor = anchor;
}

protected abstract IClrTypeName AttributeTypeName { get; }
protected virtual bool IsRemoveActionAvailable() => false;

public abstract BulbActionBase GetActionForOne(IMultipleFieldDeclaration multipleFieldDeclaration, IFieldDeclaration fieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute);
public abstract BulbActionBase GetActionForAll(IMultipleFieldDeclaration multipleFieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute);

public IEnumerable<IntentionAction> CreateBulbItems()
{
var fieldDeclaration = DataProvider.GetSelectedElement<IFieldDeclaration>();
var multipleFieldDeclaration = MultipleFieldDeclarationNavigator.GetByDeclarator(fieldDeclaration);
var unityApi = DataProvider.Solution.GetComponent<UnityApi>();

if (!unityApi.IsSerialisedField(fieldDeclaration?.DeclaredElement) || multipleFieldDeclaration == null)
return EmptyList<IntentionAction>.Enumerable;

var existingAttribute = AttributeUtil.GetAttribute(fieldDeclaration, AttributeTypeName);

if (multipleFieldDeclaration.Declarators.Count == 1)
{

return new[]
{
GetActionForOne(multipleFieldDeclaration, fieldDeclaration, DataProvider.PsiModule,DataProvider.ElementFactory, existingAttribute).ToContextActionIntention(myAnchor)
};
}

return new[]
{
GetActionForOne(multipleFieldDeclaration, fieldDeclaration, DataProvider.PsiModule, DataProvider.ElementFactory, existingAttribute).ToContextActionIntention(myAnchor),
GetActionForAll(multipleFieldDeclaration, DataProvider.PsiModule, DataProvider.ElementFactory, existingAttribute).ToContextActionIntention(myAnchor)
};
}

public bool IsAvailable(IUserDataHolder cache)
{
if (!DataProvider.Project.IsUnityProject())
return false;

var unityApi = DataProvider.Solution.GetComponent<UnityApi>();
var fieldDeclaration = DataProvider.GetSelectedElement<IFieldDeclaration>();
if (!unityApi.IsSerialisedField(fieldDeclaration?.DeclaredElement))
return false;

var existingAttribute = AttributeUtil.GetAttribute(fieldDeclaration, AttributeTypeName);
if (existingAttribute != null && !IsRemoveActionAvailable())
return false;

var classDeclaration = fieldDeclaration.GetContainingTypeDeclaration();
var classElement = classDeclaration?.DeclaredElement;
if (classElement == null)
return false;


return unityApi.IsDescendantOfMonoBehaviour(classElement) ||
unityApi.IsDescendantOfScriptableObject(classElement);
}

public static void AddAttribute(IClrTypeName attributeClrTypeName, IFieldDeclaration fieldDeclaration, AttributeValue[] values, IPsiModule module)
{
var elementFactory = CSharpElementFactory.GetInstance(module);
var attributeType = TypeFactory.CreateTypeByCLRName(attributeClrTypeName, module);
var attributeTypeElement = attributeType.GetTypeElement();
if (attributeTypeElement == null)
return;

var attribute = elementFactory.CreateAttribute(attributeTypeElement, values, EmptyArray<Pair<string, AttributeValue>>.Instance);


CSharpSharedImplUtil.AddAttributeAfter(fieldDeclaration, attribute, null);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using JetBrains.Application.Progress;
using JetBrains.Application.UI.Controls.BulbMenu.Anchors;
using JetBrains.DocumentModel;
using JetBrains.Metadata.Reader.API;
using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.Bulbs;
using JetBrains.ReSharper.Feature.Services.ContextActions;
using JetBrains.ReSharper.Feature.Services.CSharp.Analyses.Bulbs;
using JetBrains.ReSharper.Feature.Services.Intentions;
using JetBrains.ReSharper.Feature.Services.LiveTemplates.Hotspots;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp;
using JetBrains.ReSharper.Psi.CSharp.Impl;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Modules;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.TextControl;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.Unity.CSharp.Feature.Services.ContextActions
{
[ContextAction(Group = UnityContextActions.GroupID,
Name = "Add 'Range' attribute",
Description =
"Add range for this property in the Unity Editor inspector")]
public class AddRangeAttributeAction : AddInspectorAttributeAction
{
[NotNull] private static readonly SubmenuAnchor ourSubmenuAnchor =
new SubmenuAnchor(ourBaseAnchor, SubmenuBehavior.Executable);
protected override IClrTypeName AttributeTypeName => KnownTypes.Range;

public AddRangeAttributeAction(ICSharpContextActionDataProvider dataProvider) : base(dataProvider, ourSubmenuAnchor)
{
}

public override BulbActionBase GetActionForOne(IMultipleFieldDeclaration multipleFieldDeclaration, IFieldDeclaration fieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute)
{
return new AddRangeActionOne(multipleFieldDeclaration.Declarators.Count, fieldDeclaration, module);
}

public override BulbActionBase GetActionForAll(IMultipleFieldDeclaration multipleFieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory, IAttribute existingAttribute)
{
return new AddRangeActionAll(multipleFieldDeclaration, module, elementFactory);
}

private class AddRangeActionOne : BulbActionBase
{
private readonly int myDeclaratorsCount;
private readonly IFieldDeclaration myFieldDeclaration;
private readonly IPsiModule myModule;

public AddRangeActionOne(int declaratorsCount, IFieldDeclaration fieldDeclaration, IPsiModule module)
{
myDeclaratorsCount = declaratorsCount;
myFieldDeclaration = fieldDeclaration;
myModule = module;
}

protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
return ExecuteHotspotSession(solution, myFieldDeclaration, myModule);
}

public override string Text => myDeclaratorsCount == 1 ? "Add 'Range'" : $"Annotate '{myFieldDeclaration.DeclaredName}' with 'Range' attribute";
}

private class AddRangeActionAll : BulbActionBase
{
private readonly IMultipleFieldDeclaration myMultipleFieldDeclaration;
private readonly IPsiModule myModule;

public AddRangeActionAll(IMultipleFieldDeclaration multipleFieldDeclaration, IPsiModule module,
CSharpElementFactory elementFactory)
{
myMultipleFieldDeclaration = multipleFieldDeclaration;
myModule = module;
}

protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
{
var fieldDeclaration = (IFieldDeclaration) myMultipleFieldDeclaration.Declarators[0];
return ExecuteHotspotSession(solution, fieldDeclaration, myModule);
}

public override string Text => "Annotate all fields with 'Range' attribute";
}

private static Action<ITextControl> ExecuteHotspotSession(ISolution solution, IFieldDeclaration fieldDeclaration, IPsiModule module)
{
AddAttribute(KnownTypes.Range, fieldDeclaration,
new[] {new AttributeValue(new ConstantValue(0, module)), new AttributeValue(new ConstantValue(1, module)),}, module);


var hotspotsRegistry = new HotspotsRegistry(solution.GetPsiServices());

var attribute = fieldDeclaration.Attributes.First(t =>
(t.TypeReference.Resolve().DeclaredElement as IClass).GetClrName().Equals(KnownTypes.Range));

hotspotsRegistry.Register(new[] {attribute.Arguments[0]}, new NameSuggestionsExpression(new List<string>() {"0"}));
hotspotsRegistry.Register(new[] {attribute.Arguments[1]}, new NameSuggestionsExpression(new List<string>() {"1"}));

return BulbActionUtils.ExecuteHotspotSession(hotspotsRegistry, DocumentOffset.InvalidOffset);
}
}
}
Loading