diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/DefElementRenameFactory.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/DefElementRenameFactory.cs new file mode 100644 index 0000000..14144ff --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/DefElementRenameFactory.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; +using JetBrains.Application; +using JetBrains.Application.Progress; +using JetBrains.Collections; +using JetBrains.Diagnostics; +using JetBrains.ReSharper.Feature.Services.Refactorings; +using JetBrains.ReSharper.Feature.Services.Refactorings.Conflicts; +using JetBrains.ReSharper.Feature.Services.Refactorings.Specific.Rename; +using JetBrains.ReSharper.Feature.Services.Util; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.Pointers; +using JetBrains.ReSharper.Psi.Resolve; +using JetBrains.ReSharper.Psi.Tree; +using JetBrains.ReSharper.Psi.Xml; +using JetBrains.Util; + +namespace ReSharperPlugin.RimworldDev.Refactoring; + +[ShellFeaturePart] +public class DefElementRenameFactory : AtomicRenamesFactory +{ + public override bool IsApplicable(IDeclaredElement declaredElement) + { + return declaredElement.PresentationLanguage.Is(); + } + + public override IEnumerable CreateAtomicRenames(IDeclaredElement declaredElement, string newName, + bool doNotAddBindingConflicts) + { + yield return new XmlDefAtomicRename(declaredElement, newName, doNotAddBindingConflicts); + } + + public override RenameAvailabilityCheckResult CheckRenameAvailability( + IDeclaredElement declaredElement) + { + return RenameAvailabilityCheckResult.CanBeRenamed; + // RenameAvailabilityCheckResult availabilityCheckResult = base.CheckRenameAvailability(declaredElement); + // if ((EnumPattern) availabilityCheckResult != (EnumPattern) RenameAvailabilityCheckResult.CanBeRenamed) + // return availabilityCheckResult; + // switch (declaredElement) + // { + // case ITypeMember _: + // return RenameAvailabilityCheckResult.CanBeRenamed; + // case ITypeElement _: + // return RenameAvailabilityCheckResult.CanBeRenamed; + // case IXamlNamespaceAlias _: + // return declaredElement.ShortName != "x" ? RenameAvailabilityCheckResult.CanBeRenamed : XamlRenameAvailabilityCheckResult.NotSupportedInXaml; + // case IXamlResource _: + // if (declaredElement.GetDeclarations().Count > 0) + // return RenameAvailabilityCheckResult.CanBeRenamed; + // break; + // } + // return XamlRenameAvailabilityCheckResult.NotSupportedInXaml; + } +} + +[PublicAPI] +public class XmlDefAtomicRename : AtomicRenameBase +{ + [NotNull] private readonly IDeclaredElementPointer myOriginalElementPointer; + private readonly bool myDoNotShowBindingConflicts; + [NotNull] private readonly List myNewReferences = new(); + [NotNull] private readonly List myDeclarations = new(); + [CanBeNull] private readonly List> mySecondaryElements; + [CanBeNull] private DeclaredElementEnvoy myDeclaredElementEnvoy; + [CanBeNull] private IDeclaredElementPointer myNewElementPointer; + + public override IDeclaredElement NewDeclaredElement => myNewElementPointer.NotNull().FindDeclaredElement(); + + public override IDeclaredElement PrimaryDeclaredElement + { + get + { + IDeclaredElement declaredElement = myOriginalElementPointer.FindDeclaredElement(); + if (declaredElement != null) + return declaredElement; + return myDeclaredElementEnvoy?.GetValidDeclaredElement(); + } + } + + [NotNull] + public override IList SecondaryDeclaredElements + { + get + { + return mySecondaryElements == null + ? EmptyList.Instance + : mySecondaryElements + .SelectNotNull( + (Func, IDeclaredElement>) + (x => x.FindDeclaredElement())).ToList(); + } + } + + public override string NewName { get; } + + public override string OldName { get; } + + + public XmlDefAtomicRename( + [NotNull] IDeclaredElement declaredElement, + [NotNull] string newName, + bool doNotShowBindingConflicts) + { + myOriginalElementPointer = declaredElement.CreateElementPointer(); + NewName = newName; + OldName = declaredElement.ShortName; + myDoNotShowBindingConflicts = doNotShowBindingConflicts; + mySecondaryElements = RenameRefactoringService.Instance.GetRenameService(declaredElement.PresentationLanguage) + .GetSecondaryElements(declaredElement, newName).ToList(x => x.CreateElementPointer()); + BuildDeclarations(); + } + + public override void Rename(IRenameRefactoring executer, IProgressIndicator progress, bool hasConflictsWithDeclarations, + IRefactoringDriver driver) + { + BuildDeclarations(); + var declaredElement = myOriginalElementPointer.FindDeclaredElement(); + if (declaredElement == null) + return; + var psiServices = declaredElement.GetPsiServices(); + var elementReferences = executer.Workflow.GetElementReferences(PrimaryDeclaredElement); + progress.Start(myDeclarations.Count + elementReferences.Count); + foreach (var declaration in myDeclarations) + { + InterruptableActivityCookie.CheckAndThrow(progress); + executer.Workflow.LanguageSpecific[declaration.Language].SetName(declaration, NewName, executer); + progress.Advance(); + } + + psiServices.Caches.Update(); + var newDeclaredElement = myDeclarations[0].DeclaredElement; + myNewElementPointer = newDeclaredElement.CreateElementPointer(); + myNewReferences.Clear(); + PsiLanguageType key1; + ISet referenceSet; + foreach (var referencesWithLanguage in LanguageUtil + .SortReferencesWithLanguages(elementReferences.Where(x => x.IsValid()), psiServices)) + { + referencesWithLanguage.Deconstruct(out key1, out referenceSet); + var language = key1; + var references = referenceSet; + var rename = executer.Workflow.LanguageSpecific[language]; + foreach (IGrouping grouping1 in references.GetSortedReferences() + .GroupBy( + it => it.GetTreeNode().GetSourceFile())) + { + IPsiSourceFile key2 = grouping1.Key; + if (key2 != null) + { + IGrouping grouping = grouping1; + psiServices.Caches.WithSyncUpdateFiltered(key2, () => + { + foreach (IReference reference1 in grouping) + { + // @TODO: Is this where we're meant to change the text?! + IReference oldReferenceForConflict = reference1; + InterruptableActivityCookie.CheckAndThrow(progress); + if (reference1.IsValid()) + { + IReference reference2 = rename.TransformProjectedInitializer(reference1); + DeclaredElementInstance subst = GetSubst(newDeclaredElement, executer); + IReference reference3 = !(subst != null) + ? rename.BindReference(reference2, newDeclaredElement) + : (subst.Substitution.Domain.IsEmpty() + ? rename.BindReference(reference2, subst.Element) + : rename.BindReference(reference2, subst.Element, subst.Substitution)); + if (!(reference3 is IImplicitReference) && !hasConflictsWithDeclarations && + !myDoNotShowBindingConflicts && !rename.IsAlias(newDeclaredElement) && + !rename.IsCheckResolvedTo(reference3, newDeclaredElement)) + driver.AddLateConflict( + () => Conflict.Create(oldReferenceForConflict, + ConflictType.CANNOT_UPDATE_USAGE_CONFLICT), "late bound"); + myNewReferences.Insert(0, reference3); + rename.AdditionalReferenceProcessing(newDeclaredElement, reference3, + myNewReferences); + } + + progress.Advance(); + } + }); + } + } + } + + // foreach ((IDeclaredElement element, IList source) in SecondaryDeclaredElements + // .ToList( + // (Func)>)(secondaryElement => + // (secondaryElement, executer.Workflow.GetElementReferences(secondaryElement))))) + // { + // IDeclaredElement updatedElement = + // UpdateSecondaryElement(element, newDeclaredElement, executer) ?? element; + // foreach (KeyValuePair> referencesWithLanguage in + // LanguageUtil.SortReferencesWithLanguages( + // source.Where(x => x.IsValid()), psiServices)) + // { + // referencesWithLanguage.Deconstruct(out key1, out referenceSet); + // PsiLanguageType language = key1; + // ISet references = referenceSet; + // RenameHelperBase rename = executer.Workflow.LanguageSpecific[language]; + // foreach (IGrouping grouping2 in references.GetSortedReferences() + // .GroupBy( + // it => it.GetTreeNode().GetSourceFile())) + // { + // IGrouping grouping = grouping2; + // psiServices.Caches.WithSyncUpdateFiltered(grouping.Key, () => + // { + // foreach (IReference reference in grouping) + // { + // InterruptableActivityCookie.CheckAndThrow(progress); + // if (reference.IsValid()) + // rename.TransformProjectedInitializer(reference).BindTo(updatedElement); + // } + // }); + // } + // } + // } + } + + private static DeclaredElementInstance GetSubst( + IDeclaredElement element, + IRenameRefactoring executer) + { + return executer.Workflow.LanguageSpecific[element.PresentationLanguage].GetSubst(element); + } + + [CanBeNull] + private static IDeclaredElement UpdateSecondaryElement( + IDeclaredElement element, + IDeclaredElement newDeclaredElement, + IRenameRefactoring executor) + { + return executor.Workflow.LanguageSpecific[element.PresentationLanguage].UpdateSecondaryElement(element, newDeclaredElement); + } + + private void BuildDeclarations() + { + myDeclarations.Clear(); + IDeclaredElement declaredElement = myOriginalElementPointer.FindDeclaredElement(); + if (declaredElement == null) + return; + IList allDeclarations = new MultyPsiDeclarations(declaredElement).AllDeclarations; + if (allDeclarations.Count == 0) + { + Assertion.Fail("Element of type '{0}' has no declarations.", declaredElement.GetElementType()); + } + else + { + foreach (IDeclaration declaration in allDeclarations) + myDeclarations.Add(declaration); + } + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/RenameDef.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/RenameDef.cs new file mode 100644 index 0000000..3e47264 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/Refactoring/RenameDef.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Application.DataContext; +using JetBrains.Application.Progress; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.DataContext; +using JetBrains.ReSharper.Feature.Services.Refactorings; +using JetBrains.ReSharper.Feature.Services.Refactorings.Specific.Rename; +using JetBrains.ReSharper.Feature.Services.Resources; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.DataContext; +using JetBrains.ReSharper.Psi.Pointers; +using JetBrains.ReSharper.Psi.Resolve; +using JetBrains.ReSharper.Refactorings.Rename; +using JetBrains.TextControl; +using JetBrains.TextControl.DataContext; +using JetBrains.Util; + +namespace ReSharperPlugin.RimworldDev.Refactoring; + +[RefactoringWorkflowProvider] +public class RenameDefProvider : IRefactoringWorkflowProvider +{ + public IEnumerable CreateWorkflow(IDataContext dataContext) + { + var solution = dataContext.GetData(ProjectModelDataConstants.SOLUTION); + yield return new RenameDefWorkflow(solution, "RenameXmlDef"); + } +} + +public class RenameDefWorkflow(ISolution solution, string actionId) : RenameWorkflowBase(solution, actionId) +{ + public override bool IsAvailable(IDataContext context) + { + return true; + } + + private List GetDeclaredElements(IDataContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + var declaredElements = new List(); + var data1 = context.GetData(TextControlDataConstants.TEXT_CONTROL); + + DataProvider = DataProvider ?? context.GetData(RenameRefactoringService.RenameDataProvider); + if ((DataProvider == null || DataProvider.CanBeLocal) && data1 != null && + RenameRefactoringService.Instance.CheckLocalRenameAvailability(context)) + return new(); + ICollection data2 = + context.GetData(RenameRefactoringService.PRIMEVAL_DECLARED_ELEMENTS_TO_RENAME); + if (data2.IsNullOrEmpty()) + return new(); + + // @TODO: Check, can we make a rename factory to just **work** with the in-built renaming + declaredElements = data2.Where(x => RenameRefactoringService.Instance.CheckRenameAvailability(x) == RenameAvailabilityCheckResult.CanBeRenamed).ToList(); + // declaredElements = data2.ToList(); + return declaredElements; + } + + public override bool Initialize(IDataContext context) + { + var declaredElements = GetDeclaredElements(context); + if (declaredElements.Count == 0) + return false; + + if (DataModel != null) + return true; + var primaryDeclaredElement = declaredElements.First(); + var fileRenames = RenameRefactoringService.Instance.FileRenameProviders + .SelectMany(provider => provider.GetFileRenames(primaryDeclaredElement, primaryDeclaredElement.ShortName)) + .AsCollection(); + + var canHaveFileRenames = fileRenames.IsEmpty() ? RenameFilesOption.NothingToRename : + fileRenames.All(r => r.AlwaysMustBeRenamed) ? RenameFilesOption.RenameWithoutConfirmation : + RenameFilesOption.RenameWithConfirmation; + + var renameHelperBase = LanguageSpecific[RenameUtil.GetPsiLanguageTypeOrKnownLanguage(primaryDeclaredElement)]; + var data = context.GetData(PsiDataConstants.REFERENCE); + var occurrence = new RenameWorkflowPopupOccurrence(Strings.RenameThisOverload_Text, + Strings.RenameInitialElementOnly_Text, new IDeclaredElement[1] { declaredElements.FirstOrDefault() }); + + var workflowPopupOccurrenceArray1 = new List() + { + occurrence, + new(Strings.RenameAllOverloads_Text, Strings.RenameInitialElementAndAllItsOverloads_Text, declaredElements) + }.ToArray(); + // var workflowPopupOccurrenceArray1 = renameHelperBase.GetPopupOccurences(declaredElements.FirstOrDefault()).AsArray(); + if (workflowPopupOccurrenceArray1.Length > 1) + { + RenameWorkflowPopupOccurrence workflowPopupOccurrence = null; + if (DataProvider == null) + { + workflowPopupOccurrence = ShowOccurrences(workflowPopupOccurrenceArray1, context); + } + else + { + int usages = DataProvider.Usages; + RenameWorkflowPopupOccurrence[] workflowPopupOccurrenceArray2 = workflowPopupOccurrenceArray1.AsArray(); + if (usages >= 0 && workflowPopupOccurrenceArray2.Length > usages) + workflowPopupOccurrence = workflowPopupOccurrenceArray2[usages]; + } + + if (workflowPopupOccurrence == null) + return false; + IEnumerable second = workflowPopupOccurrence.Elements.SelectNotNull( + (Func, IDeclaredElement>)(dep => dep.FindDeclaredElement())); + declaredElements = declaredElements.Concat(second).AsList(); + } + + DataModel = new RenameDataModel(declaredElements, data, canHaveFileRenames, WorkflowExecuterLifetime, Solution, + renameHelperBase.GetOptionsModel(primaryDeclaredElement, data, WorkflowExecuterLifetime)); + return true; + } + + public override IRefactoringExecuter CreateRefactoring(IRefactoringDriver driver) + { + return new RenameRefactoring(this, this.Solution, driver); + } +}; + +public class RenameDef(RenameWorkflowBase workFlow, ISolution solution, IRefactoringDriver driver) + : RenameRefactoring(workFlow, solution, driver) +{ + public override bool Execute(IProgressIndicator pi) + { + return base.Execute(pi); + } +}; \ No newline at end of file