diff --git a/CHANGELOG.md b/CHANGELOG.md index 6906898..7aeafc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2024.1 + * Built for 2024.1 + * Adding defName auto-completion in C# for DefDatabase and `[DefOf]` + * When defining a RunConfig, you can now select a ModList config and Save file to be loaded for just this one launch + * Added support for `[LoadAlias]` + * New Mod Template now adds a PublisherPlus configuration file + ## 2023.4 * Added support for Custom Def Classes * Implemented XML Def Find Usages, so you can just Right-Click a defName and see all usages of that def in XML diff --git a/README.md b/README.md index cc3ef91..2a5257b 100644 --- a/README.md +++ b/README.md @@ -12,18 +12,32 @@ into the definitions on which the XML sits. ## Features so far - * Enjoy autocompletion of DefTypes and their properties - * Ctrl+Click into a tag in order to view the Class or Property that the tag is referring to - * Autocomplete from classes defined in `Class=""` attributes, such as for comps. - * When referring to other XML Defs (such as `ThoughtDefName`), auto complete and link to that defs XML - * Autocomplete certain values for properties with fixed options (Such as Altitude Layer, boolean and directions) - * A Rimworld Run Configuration with a Debug option + * Autocompletion for Defs + * Autocomplete the DefTypes available + * Autocomplete the properties available for a given DefType + * Autocomplete the available DefNames when referencing other defs + * Autocomplete DefNames when using `DefDatabase.GetNamed()` + * Autocomplete DefNames when creating fields in `[DefOf]` classes + * Autocomplete certain values for properties with fixed options (Such as Altitude Layer, boolean and directions) + * Use `Ctrl+Click` to go references + * When using them on DefTypes, just to the C# class for that Def + * When using them on XML Properties, jump to the C# definition for that property + * When using them on DefName references, jump to the XML definition for that DefName + * When using them on certain XML values, jump to the C# definition for that value + * When using them on `[DefOf]` fields, or `DefDatabase.GetNamed()` calls, jump to the XML definition for that DefName + * Read the values in `Class=""` attributes to fetch the correct class to autocomplete from, such as in comps + * Support for Custom Def Classes (Such as ``) + * A Rimworld Run Configuration + * Automatically loads Unity Doorstop if run in Debug mode + * Specify a ModList and Save file to load for just this one launch * Rimworld Mod support in New Solution * Custom Rimworld XML Projects + * Automatically includes Rimworlds Core and DLC defs + * Reads your `About.xml` to automatically include the defs of mods that you rely on in your workspace * Basic validation for some XML Values - * Support for Custom Def Classes (Such as ``) * The ability to perform Find Usages on XML Defs * A "Generate" menu option to generate properties for a given XML Def + * Includes a Rimworld Dictionary so that Rimworld terms don't get flagged as not real words by Rider ## Quick Architecture For Developers diff --git a/buildPlugin.ps1 b/buildPlugin.ps1 index 77156c2..dfedd6e 100644 --- a/buildPlugin.ps1 +++ b/buildPlugin.ps1 @@ -1,5 +1,5 @@ Param( - $Version = "2023.4" + $Version = "2024.1-EAP" ) Set-StrictMode -Version Latest diff --git a/gradle.properties b/gradle.properties index fac652b..b3b905d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ DotnetPluginId=ReSharperPlugin.RimworldDev DotnetSolution=ReSharperPlugin.RimworldDev.sln RiderPluginId=com.jetbrains.rider.plugins.rimworlddev -PluginVersion=2023.4 +PluginVersion=2024.1.1 BuildConfiguration=Release @@ -14,7 +14,7 @@ PublishToken="_PLACEHOLDER_" # Release: 2020.2 # Nightly: 2020.3-SNAPSHOT # EAP: 2020.3-EAP2-SNAPSHOT -ProductVersion=2023.3 +ProductVersion=2024.1.1 # Kotlin 1.4 will bundle the stdlib dependency by default, causing problems with the version bundled with the IDE # https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-rc-released/#stdlib-default diff --git a/src/dotnet/ReSharperPlugin.RimworldDev.Tests/ReSharperPlugin.RimworldDev.Tests.csproj b/src/dotnet/ReSharperPlugin.RimworldDev.Tests/ReSharperPlugin.RimworldDev.Tests.csproj index 2dae672..7c5cdb9 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev.Tests/ReSharperPlugin.RimworldDev.Tests.csproj +++ b/src/dotnet/ReSharperPlugin.RimworldDev.Tests/ReSharperPlugin.RimworldDev.Tests.csproj @@ -11,6 +11,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/DefNameValue.cs b/src/dotnet/ReSharperPlugin.RimworldDev/DefNameValue.cs new file mode 100644 index 0000000..d487142 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/DefNameValue.cs @@ -0,0 +1,25 @@ +using System.Linq; + +namespace ReSharperPlugin.RimworldDev; + +public class DefNameValue +{ + public readonly string DefType; + + public readonly string DefName; + + public string TagId => $"{DefType}/{DefName}"; + + public DefNameValue(string tagId) + { + var split = tagId.Split('/'); + DefType = split.First(); + DefName = split.Last(); + } + + public DefNameValue(string defType, string defName) + { + DefType = defType; + DefName = defName; + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/CSharpDefsOfItemProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/CSharpDefsOfItemProvider.cs new file mode 100644 index 0000000..ec4da4a --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/CSharpDefsOfItemProvider.cs @@ -0,0 +1,60 @@ +using System.Linq; +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure; +using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.LookupItems; +using JetBrains.ReSharper.Feature.Services.CSharp.CodeCompletion.Infrastructure; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.CSharp.Tree; +using JetBrains.ReSharper.Psi.Tree; +using ReSharperPlugin.RimworldDev.SymbolScope; +using ReSharperPlugin.RimworldDev.TypeDeclaration; + +namespace ReSharperPlugin.RimworldDev.ItemCompletion; + +[Language(typeof(CSharpLanguage))] +public class CSharpDefsOfItemProvider : ItemsProviderOfSpecificContext +{ + private static RimworldCSharpLookupFactory LookupFactory = new(); + + protected override bool IsAvailable(CSharpCodeCompletionContext context) + { + var node = context.NodeInFile; + if (!node.Language.IsLanguage(CSharpLanguage.Instance)) return false; + if (node.Parent is not IFieldDeclaration fieldDeclaration) return false; + if (fieldDeclaration.Type is not IDeclaredType) return false; + if (fieldDeclaration.GetContainingTypeElement() is not IClass containingClass) return false; + if (containingClass + .GetAttributeInstances(AttributesSource.Self) + .FirstOrDefault(attribute => attribute.GetClrName().FullName == "RimWorld.DefOf") is null) return false; + + return true; + } + + protected override bool AddLookupItems(CSharpCodeCompletionContext context, IItemsCollector collector) + { + var node = context.NodeInFile; + + if (node.Parent is not IFieldDeclaration fieldDeclaration) return false; + if (fieldDeclaration.Type is not IDeclaredType fieldType) return false; + + var defTypeName = fieldType.GetClrName().ShortName; + var xmlSymbolTable = context.NodeInFile.GetSolution().GetComponent(); + + var allDefs = xmlSymbolTable.GetDefsByType(defTypeName); + + foreach (var key in allDefs) + { + var defType = key.Split('/').First(); + var defName = key.Split('/').Last(); + + var item = xmlSymbolTable.GetTagByDef(defType, defName); + + var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, defName, + new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false))); + collector.Add(lookup); + } + + return base.AddLookupItems(context, collector); + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/RimworldDefCSharpItemProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/RimworldDefCSharpItemProvider.cs new file mode 100644 index 0000000..73296d4 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ItemCompletion/RimworldDefCSharpItemProvider.cs @@ -0,0 +1,58 @@ +using System.Linq; +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure; +using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.LookupItems; +using JetBrains.ReSharper.Feature.Services.CSharp.CodeCompletion.Infrastructure; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.CSharp.Impl.Tree; +using JetBrains.ReSharper.Psi.CSharp.Parsing; +using JetBrains.ReSharper.Psi.CSharp.Tree; +using JetBrains.ReSharper.Psi.Tree; +using ReSharperPlugin.RimworldDev.SymbolScope; +using ReSharperPlugin.RimworldDev.TypeDeclaration; + +namespace ReSharperPlugin.RimworldDev.ItemCompletion; + +[Language(typeof(CSharpLanguage))] +public class RimworldDefCSharpItemProvider: ItemsProviderOfSpecificContext +{ + private static RimworldCSharpLookupFactory LookupFactory = new(); + + protected override bool IsAvailable(CSharpCodeCompletionContext context) + { + var node = context.NodeInFile; + if (!node.Language.IsLanguage(CSharpLanguage.Instance)) return false; + if (node is not CSharpGenericToken) return false; + if (node.NodeType != CSharpTokenType.STRING_LITERAL_REGULAR) return false; + + return true; + } + + protected override bool AddLookupItems(CSharpCodeCompletionContext context, IItemsCollector collector) + { + var node = context.NodeInFile; + if (node?.Parent?.Parent?.Parent?.Parent is not IInvocationExpression invocationExpression + || !invocationExpression.GetText().Contains("DefDatabase") + || invocationExpression.Type() is not IDeclaredType declaredType) return false; + + var defTypeName = declaredType.GetClrName().ShortName; + var xmlSymbolTable = context.NodeInFile.GetSolution().GetComponent(); + + var allDefs = xmlSymbolTable.GetDefsByType(defTypeName); + + foreach (var key in allDefs) + { + var defType = key.Split('/').First(); + var defName = key.Split('/').Last(); + + var item = xmlSymbolTable.GetTagByDef(defType, defName); + + var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, defName, + new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false))); + collector.Add(lookup); + } + + return base.AddLookupItems(context, collector); + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/CustomSearcher.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/CustomSearcher.cs index 38c45cd..2f40912 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/CustomSearcher.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/CustomSearcher.cs @@ -4,15 +4,17 @@ using JetBrains.Diagnostics; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; +using JetBrains.ReSharper.Psi.CSharp; using JetBrains.ReSharper.Psi.ExtensionsAPI; using JetBrains.ReSharper.Psi.ExtensionsAPI.Finder; using JetBrains.ReSharper.Psi.Files; using JetBrains.ReSharper.Psi.Search; using JetBrains.ReSharper.Psi.Tree; +using JetBrains.ReSharper.Psi.Xml; namespace ReSharperPlugin.RimworldDev.Navigation; -public class CustomSearcher : IDomainSpecificSearcher where TLanguage : KnownLanguage +public class CustomSearcher : IDomainSpecificSearcher { private readonly JetHashSet myNames; private readonly JetHashSet myWordsInText; @@ -50,7 +52,8 @@ public bool ProcessProjectItem( { if (!CanContainWord(sourceFile)) return false; - foreach (ITreeNode psiFile in sourceFile.GetPsiFiles()) + foreach (var psiFile in sourceFile.GetPsiFiles() + .Concat(sourceFile.GetPsiFiles())) { if (ProcessElement(psiFile, consumer)) return true; diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/RimworldSearcherFactory.cs b/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/RimworldSearcherFactory.cs index af10669..e9e1ece 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/RimworldSearcherFactory.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/Navigation/RimworldSearcherFactory.cs @@ -24,8 +24,7 @@ public override IDomainSpecificSearcher CreateReferenceSearcher( elements = new DeclaredElementsSet(elements.Where(element => IsCompatibleWithLanguage(element.PresentationLanguage))); - return new CustomSearcher(this, - elements, referenceSearcherParameters, false); + return new CustomSearcher(this, elements, referenceSearcherParameters, false); } public override IEnumerable GetAllPossibleWordsInFile(IDeclaredElement element) diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json index 49ecdef..2120b9e 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/.template.config/template.json @@ -25,7 +25,7 @@ "datatype": "text", "replaces": "ModName", "isRequired": "false", - "defaultValue": "teds" + "defaultValue": "" }, "ModAuthor": { "type": "parameter", @@ -101,6 +101,9 @@ { "path": ".gitignore" }, + { + "path": "_PublisherPlus.xml" + }, { "path": "Languages/Keyed/English.xml" } diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.cs b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.cs new file mode 100644 index 0000000..33ddb7e --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.cs @@ -0,0 +1,10 @@ +using Verse; + +namespace MyRimWorldMod; + +public class MyRimWorldMod : Mod +{ + public MyRimWorldMod(ModContentPack content) : base(content) + { + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.csproj b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj similarity index 96% rename from src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.csproj rename to src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj index eb926cd..2c6e5ec 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.csproj +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimWorldMod.csproj @@ -2,8 +2,8 @@ - MyRimworldMod - MyRimworldMod + MyRimWorldMod + MyRimWorldMod net472 false false diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.cs b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.cs deleted file mode 100644 index af5d41c..0000000 --- a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/Source/MyRimworldMod.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Verse; - -namespace MyRimworldMod; - -public class MyRimworldMod : Mod -{ - public MyRimworldMod(ModContentPack content) : base(content) - { - } -} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/_PublisherPlus.xml b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/_PublisherPlus.xml new file mode 100644 index 0000000..75ea525 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ProjectTemplates/RimworldProjectTemplate/_PublisherPlus.xml @@ -0,0 +1,11 @@ + + + + .git + .idea + .run + MyRimWorldMod.sln + packages + Source + + \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.Rider.csproj b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.Rider.csproj index 70c5350..003309a 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.Rider.csproj +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.Rider.csproj @@ -22,12 +22,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldCSharpDefProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldCSharpDefProvider.cs new file mode 100644 index 0000000..7501252 --- /dev/null +++ b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldCSharpDefProvider.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using JetBrains.DataFlow; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.Caches; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.CSharp.Tree; +using JetBrains.ReSharper.Psi.Resolve; +using JetBrains.ReSharper.Psi.Tree; +using JetBrains.ReSharper.Psi.Util; +using JetBrains.ReSharper.Psi.Web.WebConfig; +using JetBrains.ReSharper.Psi.Xml; +using JetBrains.ReSharper.Psi.Xml.Impl.Tree; +using JetBrains.ReSharper.Psi.Xml.Tree; +using ReSharperPlugin.RimworldDev.SymbolScope; + + +namespace ReSharperPlugin.RimworldDev.TypeDeclaration; + +public class RimworldCSharpDefProvider +{ +} + +[ReferenceProviderFactory] +public class RimworldCSharpReferenceProvider : IReferenceProviderFactory +{ + public RimworldCSharpReferenceProvider(Lifetime lifetime) => + Changed = new Signal(lifetime, GetType().FullName); + + public IReferenceFactory CreateFactory(IPsiSourceFile sourceFile, IFile file, IWordIndex wordIndexForChecks) + { + return sourceFile.PrimaryPsiLanguage.Is() + // && sourceFile.GetExtensionWithDot().Equals(".xml", StringComparison.CurrentCultureIgnoreCase) + ? new RimworldCSharpReferenceFactory() + : null; + } + + public ISignal Changed { get; } +} + +/** + * The reference provider is just that, it provides a reference from XML into C# (And hopefully someday the other way + * around with Find Usages), where you can Ctrl+Click into the C# for a property or class + */ +public class RimworldCSharpReferenceFactory : IReferenceFactory +{ + public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection oldReferences) + { + if (element is ICSharpLiteralExpression expression && + expression.Parent?.Parent?.Parent is IInvocationExpression invocationExpression && + invocationExpression.GetText().Contains("DefDatabase") && + invocationExpression.Type() is IDeclaredType declaredType) + { + var defTypeName = declaredType.GetClrName().ShortName; + var defName = element.GetText().Trim('"'); + + var xmlSymbolTable = element.GetSolution().GetComponent(); + + var defNameTag = new DefNameValue(defTypeName, defName); + defNameTag = xmlSymbolTable.GetDefName(defNameTag); + + if (!xmlSymbolTable.HasTag(defNameTag)) return new ReferenceCollection(); + + if (xmlSymbolTable.GetTagByDef(defNameTag) is not { } tag) + return new ReferenceCollection(); + + return new ReferenceCollection(new RimworldXmlDefReference(element, tag, defNameTag.DefType, defNameTag.DefName)); + } + + if (element.GetContainingTypeElement() is not IClass containingClass || containingClass + .GetAttributeInstances(AttributesSource.Self) + .FirstOrDefault(attribute => attribute.GetClrName().FullName == "RimWorld.DefOf") is null) + { + return new ReferenceCollection(); + } + + if (element is IFieldDeclaration fieldDeclaration && fieldDeclaration.Type is IDeclaredType fieldType) + { + var defTypeName = fieldType.GetClrName().ShortName; + var defName = element.GetText(); + + var xmlSymbolTable = element.GetSolution().GetComponent(); + + var defNameTag = new DefNameValue(defTypeName, defName); + defNameTag = xmlSymbolTable.GetDefName(defNameTag); + + if (!xmlSymbolTable.HasTag(defNameTag)) return new ReferenceCollection(); + + if (xmlSymbolTable.GetTagByDef(defNameTag) is not { } tag) + return new ReferenceCollection(); + + return new ReferenceCollection(new RimworldXmlDefReference(element, tag, defNameTag.DefType, defNameTag.DefName)); + } + + return new ReferenceCollection(); + } + + public bool HasReference(ITreeNode element, IReferenceNameContainer names) + { + if (element.NodeType.ToString() != "TEXT") return false; + return !element.Parent.GetText().Contains("defName") && names.Contains(element.GetText()); + } +} \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs index 9350f87..f3f43bc 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs @@ -1,10 +1,13 @@ using System; +using System.IO; using System.Linq; +using System.Text; using JetBrains.DataFlow; using JetBrains.Lifetimes; using JetBrains.ProjectModel; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; +using JetBrains.ReSharper.Psi.Impl.Reflection2; using JetBrains.ReSharper.Psi.Resolve; using JetBrains.ReSharper.Psi.Tree; using JetBrains.ReSharper.Psi.Util; @@ -46,7 +49,7 @@ public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection if (element.Parent != null && element.NodeType.ToString() == "TEXT" && element.Parent.GetText().StartsWith("")) return GetReferenceForDeclaredElement(element, oldReferences); - + if (element.NodeType.ToString() == "TEXT") return GetReferencesForText(element, oldReferences); if (element is not XmlIdentifier identifier) return new ReferenceCollection(); if (element.GetSourceFile() is not { } sourceFile) return new ReferenceCollection(); @@ -62,11 +65,11 @@ public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection var allWorkingScopes = ScopeHelper.AllScopes.Where(scope => scope.GetTypeElementByCLRName(className) is not null); - + var @class = className.Contains(".") ? ScopeHelper.GetScopeForClass(className).GetTypeElementByCLRName(className) : rimworldSymbolScope.GetElementsByShortName(className).FirstOrDefault(); - + if (@class == null) return new ReferenceCollection(); return new ReferenceCollection(new RimworldXmlReference(@class, identifier)); } @@ -75,11 +78,43 @@ public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection RimworldXMLItemProvider.GetContextFromHierachy(hierarchy, rimworldSymbolScope, allSymbolScopes); if (classContext == null) return new ReferenceCollection(); - var field = RimworldXMLItemProvider.GetAllFields(classContext, rimworldSymbolScope) - .FirstOrDefault(field => field.ShortName == identifier.GetText()); + var possibleFields = RimworldXMLItemProvider.GetAllFields(classContext, rimworldSymbolScope); + var field = possibleFields + .FirstOrDefault( + field => field.ShortName == identifier.GetText() + ) ?? possibleFields.FirstOrDefault(field => + field.GetAttributeInstances(AttributesSource.Self).Any( + attribute => + { + var match = identifier.GetText(); + + if (attribute.GetClrName().FullName != "Verse.LoadAliasAttribute") return false; + if (attribute.PositionParameterCount != 1) return false; + + var test = field.GetXMLDoc(true); + + if (!attribute.PositionParameter(0).ConstantValue.IsString()) + { + var writer = new StringWriter(new StringBuilder()); + if (attribute is not MetadataAttributeInstance) return false; + + ((MetadataAttributeInstance)attribute).Dump(writer, ""); + writer.Close(); + + if (writer.ToString().Contains($"Arguments: \"{match}\"")) return true; + return false; + } + + return attribute.PositionParameter(0).ConstantValue.StringValue == match; + })); if (field == null) return new ReferenceCollection(); + if (field.ShortName != identifier.GetText()) + { + return new ReferenceCollection(new RimworldXmlReference(field, identifier, field.ShortName)); + } + return new ReferenceCollection(new RimworldXmlReference(field, identifier)); } @@ -112,18 +147,15 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol var defType = classContext.ShortName; var defName = element.GetText(); - - if (xmlSymbolTable.ExtraDefTagNames.ContainsKey($"{defType}/{defName}")) - { - var newDef = xmlSymbolTable.ExtraDefTagNames[$"{defType}/{defName}"]; - defType = newDef.Split('/').First(); - defName = newDef.Split('/').Last(); - } - - if (xmlSymbolTable.GetTagByDef(defType, defName) is not { } tag) + + var defNameTag = new DefNameValue(defType, defName); + defNameTag = xmlSymbolTable.GetDefName(defNameTag); + + if (xmlSymbolTable.GetTagByDef(defNameTag) is not { } tag) return new ReferenceCollection(); - - return new ReferenceCollection(new RimworldXmlDefReference(element, tag, defType, defName)); + + return new ReferenceCollection( + new RimworldXmlDefReference(element, tag, defNameTag.DefType, defNameTag.DefName)); } /** @@ -149,14 +181,15 @@ private ReferenceCollection GetReferenceForDeclaredElement(ITreeNode element, Re var xmlSymbolTable = element.GetSolution().GetComponent(); - var tagId = $"{defTypeName}/{defName}"; - if (!xmlSymbolTable.DefTags.ContainsKey(tagId)) return new ReferenceCollection(); + var defNameTag = new DefNameValue(defTypeName, defName); + if (!xmlSymbolTable.HasTag(defNameTag)) return new ReferenceCollection(); + defNameTag = xmlSymbolTable.GetDefName(defNameTag); - if (xmlSymbolTable.GetTagByDef(defTypeName, defName) is not { } tag) + if (xmlSymbolTable.GetTagByDef(defNameTag) is not { } tag) return new ReferenceCollection(); - return new ReferenceCollection(new RimworldXmlDefReference(element, tag, defTypeName, - element.GetText())); + return new ReferenceCollection(new RimworldXmlDefReference(element, tag, defNameTag.DefType, + defNameTag.DefName)); } public bool HasReference(ITreeNode element, IReferenceNameContainer names) diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldXmlReference.cs b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldXmlReference.cs index f5056bd..9a92a38 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldXmlReference.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldXmlReference.cs @@ -25,6 +25,9 @@ public class RimworldXmlReference : private readonly ISymbolFilter myPropertyFilter; private readonly IDeclaredElement myTypeElement; + + [CanBeNull] + private readonly string overrideName; public RimworldXmlReference(IDeclaredElement typeElement, [NotNull] ITreeNode owner) : base(owner) @@ -33,7 +36,13 @@ public RimworldXmlReference(IDeclaredElement typeElement, [NotNull] ITreeNode ow myExactNameFilter = new ExactNameFilter(myOwner.GetText()); myPropertyFilter = new PredicateFilter(FilterToApplicableProperties); } - + + public RimworldXmlReference(IDeclaredElement typeElement, [NotNull] ITreeNode owner, string overrideName) + : this(typeElement, owner) + { + this.overrideName = overrideName; + } + private static bool FilterToApplicableProperties([NotNull] ISymbolInfo symbolInfo) { if (!(symbolInfo.GetDeclaredElement() is ITypeMember declaredElement)) @@ -50,7 +59,7 @@ private static bool FilterToApplicableProperties([NotNull] ISymbolInfo symbolInf return property.Type.IsImplicitlyConvertibleTo(type, typeConversionRule); } - public override string GetName() => myOwner.GetText(); + public override string GetName() => this.overrideName ?? myOwner.GetText(); private string GetShortName() => GetName().Split('.').Last(); public override TreeTextRange GetTreeTextRange() => myOwner.GetTreeTextRange(); diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldCSharpLookupFactory.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldCSharpLookupFactory.cs index 54e7c8c..8286cc5 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldCSharpLookupFactory.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldCSharpLookupFactory.cs @@ -1,15 +1,15 @@ using System; -using System.Collections.Generic; -using System.Drawing; using System.Text; using JetBrains.Annotations; using JetBrains.Diagnostics; +using JetBrains.DocumentModel; using JetBrains.ProjectModel; using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure; using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.AspectLookupItems.BaseInfrastructure; using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.AspectLookupItems.Info; using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.AspectLookupItems.Matchers; using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.AspectLookupItems.Presentations; +using JetBrains.ReSharper.Feature.Services.CSharp.CodeCompletion.Infrastructure; using JetBrains.ReSharper.Features.Intellisense.CodeCompletion.CSharp; using JetBrains.ReSharper.Features.Intellisense.CodeCompletion.CSharp.AspectLookupItems; using JetBrains.ReSharper.Psi; @@ -32,176 +32,223 @@ namespace ReSharperPlugin.RimworldDev; public class RimworldCSharpLookupFactory { - private readonly Func, ILookupItemPresentation> myFGetDeclaredElementPresentation = (item => - { - var info = item.Info; - var nullable = new bool?(); - IUnresolvedDeclaredElement unresolvedDeclaredElement = null; - var preferredDeclaredElement = info.PreferredDeclaredElement; - if (preferredDeclaredElement != null) + private readonly Func, ILookupItemPresentation> + myFGetDeclaredElementPresentation = (item => + { + var info = item.Info; + var nullable = new bool?(); + IUnresolvedDeclaredElement unresolvedDeclaredElement = null; + var preferredDeclaredElement = info.PreferredDeclaredElement; + if (preferredDeclaredElement != null) + { + var element = preferredDeclaredElement.Element; + if (element is ITypeParameter) + nullable = + info.Owner.Services.DeclaredElementDescriptionPresenter.IsDeclaredElementObsolete(element); + unresolvedDeclaredElement = element as IUnresolvedDeclaredElement; + } + + var presentation = GetOrCreatePresentation(info, PresenterStyles.DefaultPresenterStyle); + if (nullable.HasValue) + presentation.IsObsolete = nullable.Value; + if (unresolvedDeclaredElement != null) + presentation.TextColor = unresolvedDeclaredElement.IsDynamic ? JetRgbaColors.Blue : JetRgbaColors.Red; + return presentation; + }); + + private readonly Func, ILookupItemPresentation> + myFGetLocalFunctionPresentationWithoutSignature = item => + GetOrCreatePresentation(item.Info, PresenterStyles.DefaultNoParametersPresenter); + + private readonly Func, ILookupItemBehavior> myFGetDeclaredElementBehavior = + item => new CSharpDeclaredElementBehavior(item); + + private readonly Func, ILookupItemMatcher> myFGetDeclaredElementMatcher = + item => new DeclaredElementMatcher(item.Info, "@"); + + private readonly Func, ILookupItemBehavior> myFGetTypeElementBehavior = + item => new CSharpDeclaredElementBehavior(item); + + private readonly Func, ILookupItemPresentation> myFGetTypeElementPresentation = item => { - var element = preferredDeclaredElement.Element; - if (element is ITypeParameter) - nullable = info.Owner.Services.DeclaredElementDescriptionPresenter.IsDeclaredElementObsolete(element); - unresolvedDeclaredElement = element as IUnresolvedDeclaredElement; + var info = item.Info; + var style = info.InsertAngleBrackets + ? PresenterStyles.TypeElementPresenterStyle + : PresenterStyles.TypeShortNamePresenterStyle; + var presentation = GetOrCreatePresentation(info, style); + presentation.VisualReplaceRangeMarker = info.Ranges.CreateVisualReplaceRangeMarker(); + return presentation; + }; + + private static DeclaredElementPresentation GetOrCreatePresentation( + [NotNull] DeclaredElementInfo info, + DeclaredElementPresenterStyle style, + [CanBeNull] string typeParameterString = null) + { + var preferredDeclaredElement = info.PreferredDeclaredElement; + var name = info.Text; + if (preferredDeclaredElement != null && preferredDeclaredElement.Element is ICompiledElement element && + element.Type() == null) + return GetPresentationCache(element.GetSolution()) + .GetPresentation(info, element, style, typeParameterString); + else + return new DeclaredElementPresentation(info, style, typeParameterString); } - var presentation = GetOrCreatePresentation(info, PresenterStyles.DefaultPresenterStyle); - if (nullable.HasValue) - presentation.IsObsolete = nullable.Value; - if (unresolvedDeclaredElement != null) - presentation.TextColor = unresolvedDeclaredElement.IsDynamic ? JetRgbaColors.Blue : JetRgbaColors.Red; - return presentation; - }); - - private readonly Func, ILookupItemPresentation> myFGetLocalFunctionPresentationWithoutSignature = item => GetOrCreatePresentation(item.Info, PresenterStyles.DefaultNoParametersPresenter); - private readonly Func, ILookupItemBehavior> myFGetDeclaredElementBehavior = item => new CSharpDeclaredElementBehavior(item); - private readonly Func, ILookupItemMatcher> myFGetDeclaredElementMatcher = item => new DeclaredElementMatcher(item.Info, "@"); - private readonly Func, ILookupItemBehavior> myFGetTypeElementBehavior = item => new CSharpDeclaredElementBehavior(item); - private readonly Func, ILookupItemPresentation> myFGetTypeElementPresentation = item => - { - var info = item.Info; - var style = info.InsertAngleBrackets ? PresenterStyles.TypeElementPresenterStyle : PresenterStyles.TypeShortNamePresenterStyle; - var presentation = GetOrCreatePresentation(info, style); - presentation.VisualReplaceRangeMarker = info.Ranges.CreateVisualReplaceRangeMarker(); - return presentation; - }; - - private static DeclaredElementPresentation GetOrCreatePresentation( - [NotNull] DeclaredElementInfo info, - DeclaredElementPresenterStyle style, - [CanBeNull] string typeParameterString = null) - { - var preferredDeclaredElement = info.PreferredDeclaredElement; - var name = info.Text; - if (preferredDeclaredElement != null && preferredDeclaredElement.Element is ICompiledElement element && - element.Type() == null) - return GetPresentationCache(element.GetSolution()).GetPresentation(info, element, style, typeParameterString); - else - return new DeclaredElementPresentation(info, style, typeParameterString); - } - - private static LookupItemPresentationCache GetPresentationCache( - [NotNull] ISolution solution) - { - var presentationCache1 = ourPresentationCache; - if (presentationCache1 != null) - return presentationCache1; - var presentationCache2 = ourPresentationCache = solution.GetComponent(); - solution.GetSolutionLifetimes().MaximumLifetime.OnTermination(() => ourPresentationCache = null); - return presentationCache2; - } - - private static LookupItemPresentationCache ourPresentationCache; - - - public LookupItem CreateDeclaredElementLookupItem( - [NotNull] RimworldXmlCodeCompletionContext context, - [NotNull] string name, - [NotNull] DeclaredElementInstance instance, - bool includeFollowingExpression = true, - bool bind = false, - QualifierKind qualifierKind = QualifierKind.NONE) + + private static LookupItemPresentationCache GetPresentationCache( + [NotNull] ISolution solution) { - Assertion.Assert(instance != null && instance.IsValid(), "instance != null && instance.IsValid()"); - var basicContext = context.BasicContext; - var declaredElementInfo = new CSharpDeclaredElementInfo(name, instance, basicContext.LookupItemsOwner, context) + var presentationCache1 = ourPresentationCache; + if (presentationCache1 != null) + return presentationCache1; + var presentationCache2 = ourPresentationCache = solution.GetComponent(); + solution.GetSolutionLifetimes().MaximumLifetime.OnTermination(() => ourPresentationCache = null); + return presentationCache2; + } + + private static LookupItemPresentationCache ourPresentationCache; + + public LookupItem CreateDeclaredElementLookupItem( + [NotNull] CSharpCodeCompletionContext context, + [NotNull] string name, + [NotNull] DeclaredElementInstance instance, + bool includeFollowingExpression = true, + bool bind = false, + QualifierKind qualifierKind = QualifierKind.NONE) + { + var replaceRange = context.CompletionRanges.ReplaceRange; + + // @TODO: Find out why we needed to do this + if (context.NodeInFile.GetText().StartsWith("\"")) + { + replaceRange = new DocumentRange(replaceRange.Document, + new TextRange(replaceRange.TextRange.StartOffset + 1, replaceRange.TextRange.EndOffset - 1)); + } + + var completionRange = new TextLookupRanges(replaceRange, replaceRange, null, false); + + return CreateDeclaredElementLookupItem(context, completionRange, name, instance, includeFollowingExpression, + bind, qualifierKind); + } + + public LookupItem CreateDeclaredElementLookupItem( + [NotNull] ISpecificCodeCompletionContext context, + [NotNull] TextLookupRanges completionRange, + [NotNull] string name, + [NotNull] DeclaredElementInstance instance, + bool includeFollowingExpression = true, + bool bind = false, + QualifierKind qualifierKind = QualifierKind.NONE) + { + Assertion.Assert(instance != null && instance.IsValid(), "instance != null && instance.IsValid()"); + var basicContext = context.BasicContext; + + var declaredElementInfo = new CSharpDeclaredElementInfo(name, instance, basicContext.LookupItemsOwner, context) { - Bind = bind, - Ranges = context.Ranges + Bind = bind, + Ranges = completionRange }; - var element = instance.Element; - if (element is ITypeElement typeElement && typeElement.HasTypeParameters()) - { - declaredElementInfo.AppendToIdentity(2); - declaredElementInfo.Placement.OrderString += "<>"; - } - - if (qualifierKind != QualifierKind.NONE) - declaredElementInfo.QualifierKind = qualifierKind; - - if (element is IField field) - { - if (!field.GetContainingType().IsValueTuple()) - declaredElementInfo.Bind = true; - } - else if (element is ITypeElement) - { - declaredElementInfo.Bind = true; - if (element is not ITypeParameter) - declaredElementInfo.IsTypeElement = true; - } - - switch (element) - { - case IExternAlias: - declaredElementInfo.TailType = CSharpTailType.DoubleColon; - break; - case IClass: - case IStruct: - declaredElementInfo.Placement.OrderString += " T"; - break; - case IProperty: - declaredElementInfo.Placement.OrderString += " P"; - break; - } - - if (element is not IParametersOwner declaredElement) - declaredElement = (element as IDelegate).IfNotNull(_ => _.InvokeMethod); - - if (declaredElement != null && (context.BasicContext.ShowSignatures || element is IUnresolvedDeclaredElement) && declaredElement.Parameters.Count > 0 && !declaredElement.IsCSharpProperty()) - { - var stringBuilder = new StringBuilder(); - var parameters = declaredElement.Parameters; - for (var index = 0; index < parameters.Count; ++index) + var element = instance.Element; + if (element is ITypeElement typeElement && typeElement.HasTypeParameters()) { - var type = parameters[index].Type; - stringBuilder.Append((parameters[index].IsParameterArray ? "params" : string.Empty) + type.GetPresentableName(CSharpLanguage.Instance)); - if (index < parameters.Count - 1) - stringBuilder.Append(", "); + declaredElementInfo.AppendToIdentity(2); + declaredElementInfo.Placement.OrderString += "<>"; } - declaredElementInfo.Placement.OrderString = name + "(" + stringBuilder + ")"; - } - var fGetPresentation = myFGetDeclaredElementPresentation; - if (element is ILocalFunction && !context.BasicContext.ShowSignatures) - fGetPresentation = myFGetLocalFunctionPresentationWithoutSignature; - var dataHolder = LookupItemFactory.CreateLookupItem(declaredElementInfo) - .WithPresentation(fGetPresentation) - .WithBehavior(myFGetDeclaredElementBehavior) - .WithMatcher(myFGetDeclaredElementMatcher); - - var typeOwner = element as ITypeOwner; - if (instance.Element is IAnonymousTypeProperty) - declaredElementInfo.HighlightSameType = true; - IDelegate @delegate = null; - if (typeOwner != null && GetType(typeOwner) is IDeclaredType type1) - @delegate = type1.GetTypeElement() as IDelegate; - if (element is ITypeElement || element is INamespace) - dataHolder.PutKey(CompletionKeys.IsTypeOrNamespaceKey); - // if (@delegate == null && !(element1 is ITypeElement) && context.ReplaceRangeWithJoinedArguments.IsValid()) - // { - // TextLookupRanges textLookupRanges = context.CompletionRanges.WithReplaceRange(context.ReplaceRangeWithJoinedArguments); - // dataHolder.Info.Ranges = textLookupRanges; - // } - // if (qualifierKind == QualifierKind.NONE && context.UnterminatedContext.Reference is IQualifiableReference reference) - // { - // IQualifier qualifier = reference.GetQualifier(); - // qualifierKind = qualifier != null ? qualifier.GetKind() : QualifierKind.NONE; - // dataHolder.PutData((Key) CompletionKeys.ContextQualifierKind, (object) qualifierKind); - // } - if (name == "hediff") - { - var displayText = dataHolder.DisplayTypeName; - } - - return dataHolder; + + if (qualifierKind != QualifierKind.NONE) + declaredElementInfo.QualifierKind = qualifierKind; + + if (element is IField field) + { + if (!field.GetContainingType().IsValueTuple()) + declaredElementInfo.Bind = true; + } + else if (element is ITypeElement) + { + declaredElementInfo.Bind = true; + if (element is not ITypeParameter) + declaredElementInfo.IsTypeElement = true; + } + + switch (element) + { + case IExternAlias: + declaredElementInfo.TailType = CSharpTailType.DoubleColon; + break; + case IClass: + case IStruct: + declaredElementInfo.Placement.OrderString += " T"; + break; + case IProperty: + declaredElementInfo.Placement.OrderString += " P"; + break; + } + + if (element is not IParametersOwner declaredElement) + declaredElement = (element as IDelegate).IfNotNull(_ => _.InvokeMethod); + + if (declaredElement != null && (context.BasicContext.ShowSignatures || element is IUnresolvedDeclaredElement) && + declaredElement.Parameters.Count > 0 && !declaredElement.IsCSharpProperty()) + { + var stringBuilder = new StringBuilder(); + var parameters = declaredElement.Parameters; + for (var index = 0; index < parameters.Count; ++index) + { + var type = parameters[index].Type; + stringBuilder.Append((parameters[index].IsParameterArray ? "params" : string.Empty) + + type.GetPresentableName(CSharpLanguage.Instance)); + if (index < parameters.Count - 1) + stringBuilder.Append(", "); + } + + declaredElementInfo.Placement.OrderString = name + "(" + stringBuilder + ")"; + } + + var fGetPresentation = myFGetDeclaredElementPresentation; + if (element is ILocalFunction && !context.BasicContext.ShowSignatures) + fGetPresentation = myFGetLocalFunctionPresentationWithoutSignature; + var dataHolder = LookupItemFactory.CreateLookupItem(declaredElementInfo) + .WithPresentation(fGetPresentation) + .WithBehavior(myFGetDeclaredElementBehavior) + .WithMatcher(myFGetDeclaredElementMatcher); + + var typeOwner = element as ITypeOwner; + if (instance.Element is IAnonymousTypeProperty) + declaredElementInfo.HighlightSameType = true; + IDelegate @delegate = null; + if (typeOwner != null && GetType(typeOwner) is IDeclaredType type1) + @delegate = type1.GetTypeElement() as IDelegate; + if (element is ITypeElement || element is INamespace) + dataHolder.PutKey(CompletionKeys.IsTypeOrNamespaceKey); + + if (name == "hediff") + { + var displayText = dataHolder.DisplayTypeName; + } + + return dataHolder; + } + + public LookupItem CreateDeclaredElementLookupItem( + [NotNull] RimworldXmlCodeCompletionContext context, + [NotNull] string name, + [NotNull] DeclaredElementInstance instance, + bool includeFollowingExpression = true, + bool bind = false, + QualifierKind qualifierKind = QualifierKind.NONE) + { + return CreateDeclaredElementLookupItem(context, context.Ranges, name, instance, includeFollowingExpression, + bind, qualifierKind); + } + + [CanBeNull] + private static IType GetType([NotNull] ITypeOwner typeOwner) + { + if (!(typeOwner is ILocalVariableDeclaration variableDeclaration) || !variableDeclaration.IsVar) + return typeOwner.Type; + return !(variableDeclaration is IManagedVariableImpl managedVariableImpl) + ? null + : managedVariableImpl.CachedType; } - - [CanBeNull] - private static IType GetType([NotNull] ITypeOwner typeOwner) - { - if (!(typeOwner is ILocalVariableDeclaration variableDeclaration) || !variableDeclaration.IsVar) - return typeOwner.Type; - return !(variableDeclaration is IManagedVariableImpl managedVariableImpl) ? null : managedVariableImpl.CachedType; - } } \ No newline at end of file diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs index e80d5a0..97f58e3 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using JetBrains.Application.Progress; using JetBrains.ProjectModel; @@ -11,6 +13,7 @@ using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.Impl.Reflection2; using JetBrains.ReSharper.Psi.Impl.reflection2.elements.Compiled; using JetBrains.ReSharper.Psi.Impl.Types; using JetBrains.ReSharper.Psi.Modules; @@ -182,14 +185,14 @@ protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IIte var xmlSymbolTable = context.TreeNode!.GetSolution().GetSolution().GetComponent(); var keys = xmlSymbolTable.GetDefsByType(className); - + foreach (var key in keys) { var defType = key.Split('/').First(); var defName = key.Split('/').Last(); - + var item = xmlSymbolTable.GetTagByDef(defType, defName); - + var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, defName, new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false))); collector.Add(lookup); @@ -280,8 +283,22 @@ public static List GetHierarchy(ITreeNode treeNode) public static List GetAllPublicFields(ITypeElement desiredClass, ISymbolScope symbolScope) { return desiredClass.GetAllClassMembers() - .Where(field => !field.Member.GetAttributeInstances(AttributesSource.All).Select(attribute => attribute.GetAttributeShortName()).Contains("UnsavedAttribute")) - .Where(member => member.Member.AccessibilityDomain.DomainType == AccessibilityDomain.AccessibilityDomainType.PUBLIC) + .Where(field => !field.Member.GetAttributeInstances(AttributesSource.All) + .Select(attribute => attribute.GetAttributeShortName()).Contains("UnsavedAttribute")) + .Where(member => + { + if (member.Member.AccessibilityDomain.DomainType == + AccessibilityDomain.AccessibilityDomainType.PUBLIC) return true; + + var loadAliasAttributes = member + .Member + .GetAttributeInstances(AttributesSource.All) + .FirstOrDefault(attribute => attribute.GetClrName().FullName == "Verse.LoadAliasAttribute"); + + if (loadAliasAttributes != null) return true; + + return false; + }) .Select(member => member.Member) .ToList(); } @@ -291,11 +308,12 @@ public static List GetAllPublicFields(ITypeElement desiredClass, ISymbol public static List GetAllFields(ITypeElement desiredClass, ISymbolScope symbolScope) { return desiredClass.GetAllClassMembers() - .Where(field => !field.Member.GetAttributeInstances(AttributesSource.All).Select(attribute => attribute.GetAttributeShortName()).Contains("UnsavedAttribute")) + .Where(field => !field.Member.GetAttributeInstances(AttributesSource.All) + .Select(attribute => attribute.GetAttributeShortName()).Contains("UnsavedAttribute")) .Select(member => member.Member) .ToList(); } - + /** * This is where a lot of our heavy lifting is going to be. We're taking the a list of strings as a hierarchy * (See the docblock for GetHierarchy) and getting the Class for that last item in that list. So for example, if we @@ -334,7 +352,7 @@ public static ITypeElement GetContextFromHierachy(List hierarchy, ISymbo classValue = simpleTypeInfo.GetTypeArguments()?.FirstOrDefault()? .GetLongPresentableName(CSharpLanguage.Instance); } - + if (classValue == null) { if (!Regex.Match( @@ -348,7 +366,9 @@ public static ITypeElement GetContextFromHierachy(List hierarchy, ISymbo // Use regex to grab the className and then fetch it from the symbol scope classValue = Regex.Match(previousField.Type.GetLongPresentableName(CSharpLanguage.Instance), @"^System.Collections.Generic.List<(.*?)>$").Groups[1].Value; - }; + } + + ; currentContext = ScopeHelper.GetScopeForClass(classValue).GetTypeElementByCLRName(classValue); continue; @@ -390,14 +410,41 @@ public static ITypeElement GetContextFromHierachy(List hierarchy, ISymbo // current context to be diving into if (!currentContext.IsClass()) { - currentContext = currentNode.Contains(".") ? ScopeHelper.GetScopeForClass(currentNode)?.GetTypeElementByCLRName(currentNode) : symbolScope?.GetElementsByShortName(currentNode).FirstOrDefault() as Class; + currentContext = currentNode.Contains(".") + ? ScopeHelper.GetScopeForClass(currentNode)?.GetTypeElementByCLRName(currentNode) + : symbolScope?.GetElementsByShortName(currentNode).FirstOrDefault() as Class; if (currentContext is null) return null; - + continue; } var fields = GetAllFields(currentContext, symbolScope); - var field = fields.FirstOrDefault(field => field.ShortName == currentNode); + var field = fields.FirstOrDefault( + field => field.ShortName == currentNode + ) ?? fields.FirstOrDefault(field => + field.GetAttributeInstances(AttributesSource.Self).Any( + attribute => + { + if (attribute.GetClrName().FullName != "Verse.LoadAliasAttribute") return false; + if (attribute.PositionParameterCount != 1) return false; + + var test = field.GetXMLDoc(true); + + if (!attribute.PositionParameter(0).ConstantValue.IsString()) + { + var writer = new StringWriter(new StringBuilder()); + if (attribute is not MetadataAttributeInstance) return false; + + ((MetadataAttributeInstance)attribute).Dump(writer, ""); + writer.Close(); + + if (writer.ToString().Contains($"Arguments: \"{currentNode}\"")) return true; + return false; + } + + return attribute.PositionParameter(0).ConstantValue.StringValue == currentNode; + })); + if (field == null) return null; previousField = field; var clrName = field.Type.GetLongPresentableName(CSharpLanguage.Instance); @@ -443,7 +490,29 @@ protected void AddProperties(RimworldXmlCodeCompletionContext context, IItemsCol foreach (var field in fields) { if (!field.IsField || field.AccessibilityDomain.DomainType != - AccessibilityDomain.AccessibilityDomainType.PUBLIC) continue; + AccessibilityDomain.AccessibilityDomainType.PUBLIC) + { + var loadAliasAttribute = field.GetAttributeInstances(AttributesSource.All).FirstOrDefault( + attribute => attribute.GetClrName().FullName == "Verse.LoadAliasAttribute" && + attribute.PositionParameterCount == 1 && + attribute is MetadataAttributeInstance + ); + + if (loadAliasAttribute == null) continue; + + var writer = new StringWriter(new StringBuilder()); + ((MetadataAttributeInstance) loadAliasAttribute).Dump(writer, ""); + writer.Close(); + + var match = Regex.Match(writer.ToString(), "Arguments: \"(.*?)\""); + if (match.Groups.Count < 2) continue; + + + collector.Add(LookupFactory.CreateDeclaredElementLookupItem(context, match.Groups[1].Value, + new DeclaredElementInstance(field), true, false, QualifierKind.NONE)); + + continue; + } var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, field.ShortName, new DeclaredElementInstance(field), true, false, QualifierKind.NONE); diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXmlProject/Project/RimworldProjectDescriptor.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXmlProject/Project/RimworldProjectDescriptor.cs index cdbf947..f826794 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXmlProject/Project/RimworldProjectDescriptor.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXmlProject/Project/RimworldProjectDescriptor.cs @@ -27,11 +27,11 @@ public class RimworldProjectDescriptor : public IProjectProperties ProjectProperties { get; } - public void SetParentProjectPointer( - IProjectSearchDescriptor parentProjectSearchDescriptor) + public void SetParentProjectPointer(IProjectSearchDescriptor parentProjectSearchDescriptor) { if (parentProjectSearchDescriptor == ParentProjectPointer) return; + ParentProjectPointer = ParentProjectPointer == null ? parentProjectSearchDescriptor : throw new InvalidOperationException(string.Format("Parent project already defined for {0} ({1}). Existing parent: {2}. New parent: {3}", Name, ProjectFilePath, ParentProjectPointer, parentProjectSearchDescriptor)); } diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ScopeHelper.cs b/src/dotnet/ReSharperPlugin.RimworldDev/ScopeHelper.cs index 007cc7f..b4c641d 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/ScopeHelper.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/ScopeHelper.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -7,6 +8,7 @@ using JetBrains.Metadata.Reader.API; using JetBrains.ProjectModel; using JetBrains.ProjectModel.model2.Assemblies.Interfaces; +using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; using JetBrains.ReSharper.Psi.Modules; using JetBrains.ReSharper.Psi.Util; @@ -28,34 +30,37 @@ public class ScopeHelper public static bool UpdateScopes(ISolution solution) { if (solution == null) return false; - - allScopes = solution.PsiModules().GetModules().Select(module => - module.GetPsiServices().Symbols.GetSymbolScope(module, true, true)).ToList(); - - // If we haven't determined the Rimworld scope yet, our scopes may not be ready for querying. Since I'd rather - // that we were able to pull the scope from the dependencies than try to find it ourselves, let's check if the - // scopes are ready for querying first. Ofcourse, if we have no scopes at all, there's nothing to wait for - if (rimworldScope == null && allScopes.Any() && allScopes.Any(scope => !scope.GetAllShortNames().Any())) - return false; - - if (rimworldScope == null) + using (CompilationContextCookie.GetOrCreate(UniversalModuleReferenceContext.Instance)) { - rimworldScope = allScopes.FirstOrDefault(scope => scope.GetTypeElementByCLRName("Verse.ThingDef") != null); + allScopes = solution.PsiModules().GetModules().Select(module => + module.GetPsiServices().Symbols.GetSymbolScope(module, true, true)).ToList(); + + // If we haven't determined the Rimworld scope yet, our scopes may not be ready for querying. Since I'd rather + // that we were able to pull the scope from the dependencies than try to find it ourselves, let's check if the + // scopes are ready for querying first. Ofcourse, if we have no scopes at all, there's nothing to wait for + if (rimworldScope == null && allScopes.Any() && allScopes.Any(scope => !scope.GetAllShortNames().Any())) + return false; if (rimworldScope == null) { - AddRef(solution); + rimworldScope = + allScopes.FirstOrDefault(scope => scope.GetTypeElementByCLRName("Verse.ThingDef") != null); - return false; + if (rimworldScope == null) + { + AddRef(solution); + + return false; + } + + rimworldModule = solution.PsiModules().GetModules() + .First(module => + module.GetPsiServices().Symbols.GetSymbolScope(module, true, true) + .GetTypeElementByCLRName("Verse.ThingDef") != null); } - rimworldModule = solution.PsiModules().GetModules() - .First(module => - module.GetPsiServices().Symbols.GetSymbolScope(module, true, true) - .GetTypeElementByCLRName("Verse.ThingDef") != null); + return true; } - - return true; } public static ISymbolScope GetScopeForClass(string className) @@ -187,7 +192,7 @@ private static IEnumerable GetSteamLocations() { @"C:\Program Files (x86)\Steam\steamapps\", @"C:\Program Files\Steam\steamapps\", - "~/.steam/steam/steamapps/" + $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/steam/steam/steamapps/" }; locations.AddRange( diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs index 564814a..9d85669 100644 --- a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs +++ b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs @@ -5,6 +5,7 @@ using JetBrains.Application.Threading; using JetBrains.Collections; using JetBrains.Lifetimes; +using JetBrains.Metadata.Reader.API; using JetBrains.ProjectModel; using JetBrains.ReSharper.Psi; using JetBrains.ReSharper.Psi.Caches; @@ -21,9 +22,8 @@ namespace ReSharperPlugin.RimworldDev.SymbolScope; [PsiComponent] public class RimworldSymbolScope : SimpleICache> { - public Dictionary DefTags = new(); - - public Dictionary ExtraDefTagNames = new(); + private Dictionary DefTags = new(); + private Dictionary ExtraDefTagNames = new(); private Dictionary _declaredElements = new(); private SymbolTable _symbolTable; @@ -39,12 +39,18 @@ protected override bool IsApplicable(IPsiSourceFile sourceFile) return base.IsApplicable(sourceFile) && sourceFile.LanguageType.Name == "XML"; } + public bool HasTag(DefNameValue defName) => + DefTags.ContainsKey(defName.TagId) || ExtraDefTagNames.ContainsKey(defName.TagId); + [CanBeNull] public ITreeNode GetTagByDef(string defType, string defName) { return GetTagByDef($"{defType}/{defName}"); } + [CanBeNull] + public ITreeNode GetTagByDef(DefNameValue defName) => GetTagByDef(defName.TagId); + [CanBeNull] public ITreeNode GetTagByDef(string defId) { @@ -54,6 +60,9 @@ public ITreeNode GetTagByDef(string defId) return DefTags[defId]; } + public DefNameValue GetDefName(DefNameValue value) => + ExtraDefTagNames.TryGetValue(value.TagId, out var defTag) ? new DefNameValue(defTag) : value; + public List GetDefsByType(string defType) { return DefTags @@ -134,32 +143,35 @@ private void AddToLocalCache(IPsiSourceFile sourceFile, [CanBeNull] List context) + { + var locations = new List + { + "C:\\Program Files (x86)\\Steam\\steamapps\\common\\RimWorld\\RimWorldWin64_Data\\Managed\\Assembly-CSharp.dll", + "C:\\Program Files\\Steam\\steamapps\\common\\RimWorld\\RimWorldWin64_Data\\Managed\\Assembly-CSharp.dll", + "D:\\SteamLibrary\\steamapps\\common\\RimWorld\\RimWorldWin64_Data\\Managed\\Assembly-CSharp.dll", + "~/.steam/steam/steamapps/common/RimWorld/RimWorldLinux_Data/Managed/Assembly-CSharp.dll" + }; + + var possiblePaths = locations + .Select(location => FileSystemPath.TryParse(location)).Where(location => location.ExistsFile) + .ToArray(); + + var detectedPath = possiblePaths.FirstOrDefault()?.FullPath; + + return new RdProjectTemplateTextOption(detectedPath ?? "", Name, PresentableName, Tooltip); + } + public override RdProjectTemplateContent CreateContent( DotNetProjectTemplateExpander expander, IDotNetTemplateContentFactory factory, diff --git a/src/rider/main/kotlin/run/ConfigurationOptions.kt b/src/rider/main/kotlin/run/ConfigurationOptions.kt index 1664ef6..3bbe4c0 100644 --- a/src/rider/main/kotlin/run/ConfigurationOptions.kt +++ b/src/rider/main/kotlin/run/ConfigurationOptions.kt @@ -9,6 +9,8 @@ class ConfigurationOptions : RunConfigurationOptions() { private val commandLineOptions: StoredProperty = string("").provideDelegate(this, "commandLineOptions") private val environmentVariables: StoredProperty> = map().provideDelegate(this, "environmentVariables") + private val modListPath: StoredProperty = string("").provideDelegate(this, "modListPath") + private val saveFilePath: StoredProperty = string("").provideDelegate(this, "saveFilePath") fun getScriptName(): String { return scriptName.getValue(this) ?: "" @@ -18,6 +20,22 @@ class ConfigurationOptions : RunConfigurationOptions() { this.scriptName.setValue(this, scriptName) } + fun getModListPath(): String { + return modListPath.getValue(this) ?: ""; + } + + fun setModListPath(modListPath: String) { + this.modListPath.setValue(this, modListPath); + } + + fun getSaveFilePath(): String { + return saveFilePath.getValue(this) ?: "" + } + + fun setSaveFilePath(saveFilePath: String) { + this.saveFilePath.setValue(this, saveFilePath) ?: "" + } + fun getCommandLineOptions(): String { return commandLineOptions.getValue(this) ?: "" } diff --git a/src/rider/main/kotlin/run/QuickStartUtils.kt b/src/rider/main/kotlin/run/QuickStartUtils.kt new file mode 100644 index 0000000..8fc6dea --- /dev/null +++ b/src/rider/main/kotlin/run/QuickStartUtils.kt @@ -0,0 +1,67 @@ +package RimworldDev.Rider.run + +import com.intellij.util.io.delete +import org.apache.commons.lang3.SystemUtils +import java.nio.file.Path +import kotlin.io.path.* + +class QuickStartUtils { + companion object { + fun getLudeonPath(): Path? { + val possiblePaths = listOf( + Path(System.getenv("APPDATA"), "..", "LocalLow", "Ludeon Studios", "RimWorld by Ludeon Studios"), + Path(SystemUtils.getUserHome().absolutePath, ".config", ".unity3d", "Ludeon Studios", "RimWorld by Ludeon Studios"), + ) + + return possiblePaths.firstOrNull { path: Path -> path.exists() } + } + + fun setup(modListPath: String, saveFilePath: String) { + val location = getLudeonPath() ?: return + + val configLocation = Path(location.absolutePathString(), "Config") + val autostartFile = Path(location.absolutePathString(), "Saves", "autostart.rws") + val saveFile = Path(saveFilePath) + val modListFile = Path(modListPath) + + if (!modListFile.isRegularFile()) return; + if (!configLocation.isDirectory()) return; + + if (saveFile.isRegularFile()) { + if (autostartFile.isRegularFile()) { + autostartFile.copyTo(Path(location.absolutePathString(), "Saves", "autostart.rws.rider-bak"), true) + } + + saveFile.copyTo(autostartFile, true) + } + + val existingFile = Path(configLocation.absolutePathString(), "ModsConfig.xml").toFile() + existingFile.copyTo(Path(configLocation.absolutePathString(), "ModsConfig.xml.rider-bak").toFile(), true) + + modListFile.toFile().copyTo(existingFile, true) + } + + fun tearDown(saveFilePath: String) { + Thread.sleep(50) + val location = getLudeonPath() ?: return + + val configLocation = Path(location.absolutePathString(), "Config") + val backupFile = Path(configLocation.absolutePathString(), "ModsConfig.xml.rider-bak") + val autostartFile = Path(location.absolutePathString(), "Saves", "autostart.rws") + val autostartBackupFile = Path(location.absolutePathString(), "Saves", "autostart.rws.rider-bak") + + if (backupFile.isRegularFile() && backupFile.exists()) { + backupFile.copyTo(Path(configLocation.absolutePathString(), "ModsConfig.xml"), true) + backupFile.delete(false) + } + + if (Path(saveFilePath).exists() && autostartFile.isRegularFile() && autostartFile.exists()) { + autostartFile.deleteExisting() + } + + if (autostartBackupFile.isRegularFile() && autostartBackupFile.exists()) { + autostartBackupFile.copyTo(autostartFile) + } + } + } +} \ No newline at end of file diff --git a/src/rider/main/kotlin/run/RunConfiguration.kt b/src/rider/main/kotlin/run/RunConfiguration.kt index 92a7dbe..364c080 100644 --- a/src/rider/main/kotlin/run/RunConfiguration.kt +++ b/src/rider/main/kotlin/run/RunConfiguration.kt @@ -5,9 +5,7 @@ import com.intellij.execution.configuration.EnvironmentVariablesData import com.intellij.execution.configurations.* import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.RunConfiguration -import com.intellij.execution.process.ProcessHandler -import com.intellij.execution.process.ProcessHandlerFactory -import com.intellij.execution.process.ProcessTerminatedListener +import com.intellij.execution.process.* import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project @@ -19,6 +17,7 @@ import com.jetbrains.rider.plugins.unity.run.configurations.UnityAttachToPlayerF import com.jetbrains.rider.plugins.unity.run.configurations.UnityPlayerDebugConfigurationOptions import com.jetbrains.rider.plugins.unity.run.configurations.UnityPlayerDebugConfigurationType import com.jetbrains.rider.run.configurations.AsyncRunConfiguration +import com.jetbrains.rider.run.getProcess import kotlinx.coroutines.ExperimentalCoroutinesApi import org.jetbrains.concurrency.Promise @@ -42,6 +41,12 @@ class RunConfiguration(project: Project, factory: ConfigurationFactory, name: St fun getScriptName(): String = options.getScriptName() fun setScriptName(scriptName: String?) = options.setScriptName(scriptName ?: "") + fun getModListPath(): String = options.getModListPath() + fun setModListPath(modListPath: String?) = options.setModListPath(modListPath ?: "") + + fun getSaveFilePath(): String = options.getSaveFilePath() + fun setSaveFilePath(saveFilePath: String?) = options.setSaveFilePath(saveFilePath ?: "") + fun getCommandLineOptions(): String = options.getCommandLineOptions() fun setCommandLineOptions(scriptName: String?) = options.setCommandLineOptions(scriptName ?: "") @@ -67,6 +72,8 @@ class RunConfiguration(project: Project, factory: ConfigurationFactory, name: St return environment.project.lifetime.startOnUiAsync { RunState( getScriptName(), + getSaveFilePath(), + getModListPath(), getRimworldState(environment), UnityDebugRemoteConfiguration(), environment, @@ -77,7 +84,7 @@ class RunConfiguration(project: Project, factory: ConfigurationFactory, name: St override fun getConfigurationEditor(): SettingsEditor { - return RimworldDev.Rider.run.SettingsEditor() + return RimworldDev.Rider.run.SettingsEditor(project) } private fun getRimworldState(environment: ExecutionEnvironment): CommandLineState { @@ -88,11 +95,26 @@ class RunConfiguration(project: Project, factory: ConfigurationFactory, name: St EnvironmentVariablesData.create(getEnvData(), true).configureCommandLine(commandLine, true) + QuickStartUtils.setup(getModListPath(), getSaveFilePath()); + val processHandler = ProcessHandlerFactory.getInstance() .createColoredProcessHandler(commandLine) + + processHandler.addProcessListener(createProcessListener(processHandler)) ProcessTerminatedListener.attach(processHandler) return processHandler } } } + + private fun createProcessListener(siblingProcessHandler: ProcessHandler?): ProcessListener { + return object : ProcessAdapter() { + override fun processTerminated(event: ProcessEvent) { + val processHandler = event.processHandler + processHandler.removeProcessListener(this) + + QuickStartUtils.tearDown(getSaveFilePath()); + } + } + } } \ No newline at end of file diff --git a/src/rider/main/kotlin/run/RunState.kt b/src/rider/main/kotlin/run/RunState.kt index c6e51a4..8fcc73b 100644 --- a/src/rider/main/kotlin/run/RunState.kt +++ b/src/rider/main/kotlin/run/RunState.kt @@ -6,7 +6,9 @@ import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.process.* import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.execution.runners.ProgramRunner +import com.intellij.ide.actions.searcheverywhere.evaluate import com.intellij.util.system.OS +import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rider.debugger.DebuggerWorkerProcessHandler import com.jetbrains.rider.plugins.unity.run.configurations.UnityAttachProfileState import com.jetbrains.rider.run.configurations.remote.RemoteConfiguration @@ -18,6 +20,8 @@ import kotlin.io.path.Path class RunState( private val rimworldLocation: String, + private val saveFilePath: String, + private val modListPath: String, private val rimworldState: RunProfileState, remoteConfiguration: RemoteConfiguration, executionEnvironment: ExecutionEnvironment, @@ -51,20 +55,20 @@ class RunState( "Doorstop/pdb2mdb.exe", ) ) - - override fun execute( + + override suspend fun execute( executor: Executor, runner: ProgramRunner<*>, - workerProcessHandler: DebuggerWorkerProcessHandler + workerProcessHandler: DebuggerWorkerProcessHandler, + lifetime: Lifetime ): ExecutionResult { setupDoorstop() - val result = super.execute(executor, runner, workerProcessHandler) ProcessTerminatedListener.attach(workerProcessHandler.debuggerWorkerRealHandler) val rimworldResult = rimworldState.execute(executor, runner) workerProcessHandler.debuggerWorkerRealHandler.addProcessListener(createProcessListener(rimworldResult?.processHandler)) - + return result } @@ -75,6 +79,7 @@ class RunState( processHandler.removeProcessListener(this) siblingProcessHandler?.getProcess()?.destroy() + QuickStartUtils.tearDown(saveFilePath) removeDoorstep() } } diff --git a/src/rider/main/kotlin/run/SettingsEditor.kt b/src/rider/main/kotlin/run/SettingsEditor.kt index 622c8ed..0635b38 100644 --- a/src/rider/main/kotlin/run/SettingsEditor.kt +++ b/src/rider/main/kotlin/run/SettingsEditor.kt @@ -3,6 +3,7 @@ package RimworldDev.Rider.run import com.intellij.execution.configuration.EnvironmentVariablesComponent import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.project.Project import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.ui.RawCommandLineEditor import com.intellij.util.ui.FormBuilder @@ -10,11 +11,13 @@ import javax.swing.JComponent import javax.swing.JPanel -class SettingsEditor : SettingsEditor() { +class SettingsEditor(project: Project) : SettingsEditor() { private var myPanel: JPanel = JPanel() private val exePath: TextFieldWithBrowseButton = TextFieldWithBrowseButton() private val commandLineOptions: RawCommandLineEditor = RawCommandLineEditor() private val environmentVariables: EnvironmentVariablesComponent = EnvironmentVariablesComponent() + private val modListPath: TextFieldWithBrowseButton = TextFieldWithBrowseButton() + private val saveFilePath: TextFieldWithBrowseButton = TextFieldWithBrowseButton() init { exePath.addBrowseFolderListener( @@ -24,9 +27,25 @@ class SettingsEditor : SettingsEditor() { FileChooserDescriptorFactory.createSingleFileDescriptor() ) + modListPath.addBrowseFolderListener( + "Modlist Path", + "", + project, + FileChooserDescriptorFactory.createSingleFileDescriptor() + ) + + saveFilePath.addBrowseFolderListener( + "Save File Path", + "", + project, + FileChooserDescriptorFactory.createSingleFileDescriptor() + ) + myPanel = FormBuilder .createFormBuilder() .addLabeledComponent("RimWorld path:", exePath) + .addLabeledComponent("Mod list path:", modListPath) + .addLabeledComponent("Save file path:", saveFilePath) .addLabeledComponent("Program arguments:", commandLineOptions) .addLabeledComponent("Environment variables:", environmentVariables.component) .panel @@ -34,12 +53,16 @@ class SettingsEditor : SettingsEditor() { override fun resetEditorFrom(configuration: RunConfiguration) { exePath.text = configuration.getScriptName() + modListPath.text = configuration.getModListPath() + saveFilePath.text = configuration.getSaveFilePath() commandLineOptions.text = configuration.getCommandLineOptions() environmentVariables.envs = configuration.getEnvData() } override fun applyEditorTo(configuration: RunConfiguration) { configuration.setScriptName(exePath.text) + configuration.setModListPath(modListPath.text.replace('\\', '/')) + configuration.setSaveFilePath(saveFilePath.text.replace('\\', '/')) configuration.setEnvData(environmentVariables.envData.envs) configuration.setCommandLineOptions(commandLineOptions.text) diff --git a/src/rider/main/resources/META-INF/plugin.xml b/src/rider/main/resources/META-INF/plugin.xml index fb6bed4..e7355e4 100644 --- a/src/rider/main/resources/META-INF/plugin.xml +++ b/src/rider/main/resources/META-INF/plugin.xml @@ -1,9 +1,9 @@ com.jetbrains.rider.plugins.rimworlddev Rimworld Development Environment - 2023.4 + 2024.1 Garethp - + com.intellij.modules.rider com.intellij.resharper.unity @@ -18,10 +18,11 @@ in your mods!

    -
  • Added a Find Usages for XML Defs
  • -
  • Added support for Custom Def Classes
  • -
  • Add a new "Generate" menu feature to add props to your XML
  • -
  • Improved detection of various properties that weren't being picked up as references before
  • +
  • Built for 2024.1
  • +
  • Added References from C# to XML for DefDatabase and [DefOf]
  • +
  • When defining a RunConfig, you can now select a ModList config and Save file to be loaded for just this one launch
  • +
  • Added support for `[LoadAlias]`
  • +
  • New Mod Template now adds a PublisherPlus configuration file

]]>
diff --git a/src/rider/main/resources/UnityDoorstop/Windows/Doorstop/0Harmony.dll b/src/rider/main/resources/UnityDoorstop/Windows/Doorstop/0Harmony.dll index e182535..7aac19c 100644 Binary files a/src/rider/main/resources/UnityDoorstop/Windows/Doorstop/0Harmony.dll and b/src/rider/main/resources/UnityDoorstop/Windows/Doorstop/0Harmony.dll differ diff --git a/src/rider/main/resources/UnityDoorstop/macOS/Doorstop/0Harmony.dll b/src/rider/main/resources/UnityDoorstop/macOS/Doorstop/0Harmony.dll index e182535..7aac19c 100644 Binary files a/src/rider/main/resources/UnityDoorstop/macOS/Doorstop/0Harmony.dll and b/src/rider/main/resources/UnityDoorstop/macOS/Doorstop/0Harmony.dll differ