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
-- 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