diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23ab953..3285d76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 2023.1.1
+* Taking some advice from the Jetbrains team, I've pushed the XML Def detection/storage into a `SimpleICache`, massively improving performance
+
## 2023.1
* The first release of this plugin.
* On top of the features from the Alphas, it also includes automatically detecting and using Rimworlds `Assembly-CSharp.dll` if it's not already part of your project
diff --git a/build.gradle b/build.gradle
index 1467f17..6669d98 100644
--- a/build.gradle
+++ b/build.gradle
@@ -160,16 +160,6 @@ rdgen {
}
}
-patchPluginXml {
- // TODO: See also org.jetbrains.changelog: https://github.com/JetBrains/gradle-changelog-plugin
- def changelogText = file("${rootDir}/CHANGELOG.md").text
- def changelogMatches = changelogText =~ /(?s)(-.+?)(?=##|$)/
-
- changeNotes = changelogMatches.collect {
- it[1].replaceAll(/(?s)\r?\n/, "
\n")
- }.take(1).join('')
-}
-
prepareSandbox {
dependsOn compileDotNet
diff --git a/buildPlugin.ps1 b/buildPlugin.ps1
index 6631623..5b4ee26 100644
--- a/buildPlugin.ps1
+++ b/buildPlugin.ps1
@@ -1,5 +1,5 @@
Param(
- $Version = "2023.1.0"
+ $Version = "2023.1.1"
)
Set-StrictMode -Version Latest
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..7f412b9
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "7.0.202",
+ "rollForward": "latestMajor",
+ "allowPrerelease": true
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index f069c51..37eb0da 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.1.0
+PluginVersion=2023.1.1
BuildConfiguration=Release
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..ccebba7
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bbfe5a5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-7.3-all.zip
+networkTimeout=10000
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
index c83b28d..471c655 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/ReSharperPlugin.RimworldDev.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
index a80cb7b..12c8fd8 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/References/RimworldReferenceProvider.cs
@@ -2,15 +2,16 @@
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.Modules;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
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;
@@ -91,12 +92,12 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
!classContext.GetAllSuperTypes().Any(superType => superType.GetClrName().FullName == "Verse.Def"))
return new ReferenceCollection();
- var tagId = $"{classContext.ShortName}/{element.GetText()}";
- if (!RimworldXMLDefUtil.DefTags.ContainsKey(tagId)) return new ReferenceCollection();
-
- var tag = RimworldXMLDefUtil.DefTags[tagId];
-
+ var xmlSymbolTable = element.GetSolution().GetComponent();
+ var tagId = $"{classContext.ShortName}/{element.GetText()}";
+ if (xmlSymbolTable.GetTagByDef(classContext.ShortName, element.GetText()) is not { } tag)
+ return new ReferenceCollection();
+
return new ReferenceCollection(new RimworldXmlDefReference(element,
tag.GetNestedTags("defName").FirstOrDefault() ?? tag, tagId));
}
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworlXMLCompletionContextProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworlXMLCompletionContextProvider.cs
index 4e10eab..1431573 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworlXMLCompletionContextProvider.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworlXMLCompletionContextProvider.cs
@@ -59,11 +59,6 @@ public override ISpecificCodeCompletionContext GetCompletionContext(CodeCompleti
if (treeNode == null)
return (ISpecificCodeCompletionContext) null;
TreeTextRange treeRange = reference == null ? XmlCodeCompletionContextProvider.GetElementRange(treeNode) : reference.GetTreeTextRange();
-
- if (1 == 1)
- {
- RimworldXMLDefUtil.UpdateDefs(xmlFile.GetSolution());
- }
DocumentRange documentRange = unterminatedContext.ToDocumentRange(treeRange);
if (!documentRange.IsValid())
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLDefUtil.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLDefUtil.cs
deleted file mode 100644
index 82384ec..0000000
--- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLDefUtil.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using JetBrains.ProjectModel;
-using JetBrains.ReSharper.Psi;
-using JetBrains.ReSharper.Psi.Xml.Tree;
-using JetBrains.Util;
-
-namespace ReSharperPlugin.RimworldDev;
-
-public class RimworldXMLDefUtil
-{
- public static Dictionary DefTags = new();
-
- public static void UpdateDefs(ISolution solution)
- {
- foreach (var project in solution.GetAllProjects())
- {
- var files = project.GetAllProjectFiles(file => file.LanguageType.Name == "XML");
-
- foreach (var projectFile in files)
- {
- if (projectFile.GetPrimaryPsiFile() is not IXmlFile xmlFile) continue;
-
- var tags = xmlFile.GetNestedTags("Defs/*").Where(tag =>
- {
- var defNameTag = tag.GetNestedTags("defName").FirstOrDefault();
- if (defNameTag is null) return false;
-
- return true;
- });
-
- foreach (var tag in tags)
- {
- var defName = tag.GetNestedTags("defName").FirstOrDefault()?.InnerText;
- if (defName is null || DefTags.ContainsKey($"{tag.GetTagName()}/{defName}")) continue;
-
- DefTags.Add($"{tag.GetTagName()}/{defName}", tag);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
index 2c054cb..baa4d97 100644
--- a/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
@@ -1,11 +1,10 @@
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Application.Progress;
+using JetBrains.ProjectModel;
using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure;
using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.LookupItems;
-using JetBrains.ReSharper.Feature.Services.CodeCompletion.Infrastructure.LookupItems.Impl;
using JetBrains.ReSharper.Feature.Services.Navigation.Requests;
using JetBrains.ReSharper.Feature.Services.Occurrences;
using JetBrains.ReSharper.Features.Intellisense.CodeCompletion.CSharp;
@@ -20,7 +19,7 @@
using JetBrains.ReSharper.Psi.VB.Util;
using JetBrains.ReSharper.Psi.Xml;
using JetBrains.ReSharper.Psi.Xml.Impl.Tree;
-using JetBrains.Util;
+using ReSharperPlugin.RimworldDev.SymbolScope;
using ReSharperPlugin.RimworldDev.TypeDeclaration;
namespace ReSharperPlugin.RimworldDev;
@@ -179,13 +178,15 @@ protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IIte
var className = classContext.ShortName;
- var keys = RimworldXMLDefUtil.DefTags.Keys
+ var xmlSymbolTable = context.TreeNode!.GetSolution().GetSolution().GetComponent();
+
+ var keys = xmlSymbolTable.DefTags.Keys
.Where(key => key.StartsWith($"{className}/"))
.Select(key => key.Substring(className.Length + 1));
foreach (var key in keys)
{
- var item = RimworldXMLDefUtil.DefTags[$"{className}/{key}"];
+ var item = xmlSymbolTable.GetTagByDef(className, key);
var lookup = LookupFactory.CreateDeclaredElementLookupItem(context, key,
new DeclaredElementInstance(new XMLTagDeclaredElement(item, key, false)));
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs
new file mode 100644
index 0000000..c0bc0ed
--- /dev/null
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldSymbolScope.cs
@@ -0,0 +1,118 @@
+using System.Collections.Generic;
+using System.Linq;
+using JetBrains;
+using JetBrains.Annotations;
+using JetBrains.Application.Threading;
+using JetBrains.Collections;
+using JetBrains.Lifetimes;
+using JetBrains.ReSharper.Psi;
+using JetBrains.ReSharper.Psi.Caches;
+using JetBrains.ReSharper.Psi.Files;
+using JetBrains.ReSharper.Psi.Xml.Tree;
+
+namespace ReSharperPlugin.RimworldDev.SymbolScope;
+
+[PsiComponent]
+public class RimworldSymbolScope : SimpleICache>
+{
+ public Dictionary DefTags = new();
+
+ public RimworldSymbolScope
+ (Lifetime lifetime, [NotNull] IShellLocks locks, [NotNull] IPersistentIndexManager persistentIndexManager, long? version = null)
+ : base(lifetime, locks, persistentIndexManager, RimworldXmlDefSymbol.Marshaller, version)
+ {
+ }
+
+ protected override bool IsApplicable(IPsiSourceFile sourceFile)
+ {
+ return base.IsApplicable(sourceFile) && sourceFile.LanguageType.Name == "XML";
+ }
+
+ [CanBeNull]
+ public IXmlTag GetTagByDef(string defType, string defName)
+ {
+ return GetTagByDef($"{defType}/{defName}");
+ }
+
+ [CanBeNull]
+ public IXmlTag GetTagByDef(string defId)
+ {
+ if (!DefTags.ContainsKey(defId))
+ return null;
+
+ return DefTags[defId];
+ }
+
+ public override object Build(IPsiSourceFile sourceFile, bool isStartup)
+ {
+ if (!IsApplicable(sourceFile))
+ return null;
+
+ if (sourceFile.GetPrimaryPsiFile() is not IXmlFile xmlFile) return null;
+
+ var tags = xmlFile.GetNestedTags("Defs/*").Where(tag =>
+ {
+ var defNameTag = tag.GetNestedTags("defName").FirstOrDefault();
+ return defNameTag is not null;
+ });
+
+ List defs = new();
+
+ foreach (var tag in tags)
+ {
+ var defName = tag.GetNestedTags("defName").FirstOrDefault()?.InnerText;
+ if (defName is null) continue;
+
+ defs.Add(new RimworldXmlDefSymbol(tag, defName, tag.GetTagName()));
+ }
+
+ return defs;
+ }
+
+ public override void Merge(IPsiSourceFile sourceFile, object builtPart)
+ {
+ RemoveFromLocalCache(sourceFile);
+ AddToLocalCache(sourceFile, builtPart as List);
+ base.Merge(sourceFile, builtPart);
+ }
+
+ public override void MergeLoaded(object data)
+ {
+ PopulateLocalCache();
+ base.MergeLoaded(data);
+ }
+
+ public override void Drop(IPsiSourceFile sourceFile)
+ {
+ RemoveFromLocalCache(sourceFile);
+ base.Drop(sourceFile);
+ }
+
+ private void AddToLocalCache(IPsiSourceFile sourceFile, [CanBeNull] List cacheItem)
+ {
+ if (sourceFile.GetPrimaryPsiFile() is not IXmlFile xmlFile) return;
+
+ cacheItem?.ForEach(item =>
+ {
+ if (!DefTags.ContainsKey($"{item.DefType}/{item.DefName}"))
+ DefTags.Add($"{item.DefType}/{item.DefName}", xmlFile.GetNestedTags("Defs/*").FirstOrDefault(tag => tag.GetTreeStartOffset().Offset == item.DocumentOffset));
+ });
+ }
+
+ private void RemoveFromLocalCache(IPsiSourceFile sourceFile)
+ {
+ var items = Map!.GetValueSafe(sourceFile);
+
+ items?.ForEach(item =>
+ {
+ if (!DefTags.ContainsKey($"{item.DefType}/{item.DefName}"))
+ DefTags.Remove($"{item.DefType}/{item.DefName}");
+ });
+ }
+
+ private void PopulateLocalCache()
+ {
+ foreach (var (sourceFile, cacheItem) in Map)
+ AddToLocalCache(sourceFile, cacheItem);
+ }
+}
\ No newline at end of file
diff --git a/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldXmlDefSymbol.cs b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldXmlDefSymbol.cs
new file mode 100644
index 0000000..9bf0b60
--- /dev/null
+++ b/src/dotnet/ReSharperPlugin.RimworldDev/SymbolScope/RimworldXmlDefSymbol.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using JetBrains.ReSharper.Psi.Xml.Tree;
+using JetBrains.Serialization;
+using JetBrains.Util.PersistentMap;
+
+namespace ReSharperPlugin.RimworldDev.SymbolScope;
+
+public class RimworldXmlDefSymbol
+{
+ public static readonly IUnsafeMarshaller> Marshaller =
+ UnsafeMarshallers.GetCollectionMarshaller(new UniversalMarshaller(Read, Write), (size) => new List());
+
+ public string DefName { get; }
+ public string DefType { get; }
+
+ public int DocumentOffset { get; }
+
+ // public IXmlTag Tag { get; }
+
+ public RimworldXmlDefSymbol(IXmlTag tag, string defName, string defType)
+ {
+ DefName = defName;
+ DefType = defType;
+ DocumentOffset = tag.GetTreeStartOffset().Offset;
+ }
+
+ public RimworldXmlDefSymbol(int documentOffset, string defName, string defType)
+ {
+ DefName = defName;
+ DefType = defType;
+ DocumentOffset = documentOffset;
+ }
+
+ private static RimworldXmlDefSymbol Read(UnsafeReader reader)
+ {
+ var defType = reader.ReadString();
+ var defName = reader.ReadString();
+ var documentOffset = reader.ReadInt();
+
+ return new RimworldXmlDefSymbol(documentOffset, defName, defType);
+ }
+
+ private static void Write(UnsafeWriter writer, RimworldXmlDefSymbol value)
+ {
+ writer.Write(value.DefType);
+ writer.Write(value.DefName);
+ writer.Write(value.DocumentOffset);
+ }
+}
\ No newline at end of file
diff --git a/src/rider/main/resources/META-INF/plugin.xml b/src/rider/main/resources/META-INF/plugin.xml
index 8cf3121..9e55e50 100644
--- a/src/rider/main/resources/META-INF/plugin.xml
+++ b/src/rider/main/resources/META-INF/plugin.xml
@@ -16,7 +16,7 @@ in your mods!
First release, first set of features
+Massive performance update that stems from better caching of XML Defs
]]>