Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

2024.2 #50

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 2024.2
* Adds support for [Parent=""] attributes

## 2024.1
* Built for 2024.1
* Adding defName auto-completion in C# for DefDatabase and `[DefOf]`
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ into the definitions on which the XML sits.
* Autocomplete DefNames when using `DefDatabase<Def>.GetNamed()`
* Autocomplete DefNames when creating fields in `[DefOf]` classes
* Autocomplete certain values for properties with fixed options (Such as Altitude Layer, boolean and directions)
* Autocompletion for `Parent=""` attributes
* 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<Def>.GetNamed()` calls, jump to the XML definition for that DefName
* When using them on `Parent=""` attributes, jump to the XML definition for that parent
* 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 `<MyMod.CustomThingDef>`)
* A Rimworld Run Configuration
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
DotnetPluginId=ReSharperPlugin.RimworldDev
DotnetSolution=ReSharperPlugin.RimworldDev.sln
RiderPluginId=com.jetbrains.rider.plugins.rimworlddev
PluginVersion=2024.1.1
PluginVersion=2024.2

BuildConfiguration=Release

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ public ReferenceCollection GetReferences(ITreeNode element, ReferenceCollection
element.Parent.GetText().StartsWith("<defName>"))
return GetReferenceForDeclaredElement(element, oldReferences);

if (element.Parent != null && element.NodeType.ToString() == "STRING" &&
(element.Parent.GetText().StartsWith("Name") || element.Parent.GetText().StartsWith("ParentName")))
{
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();
Expand Down Expand Up @@ -129,15 +135,13 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
if (classContext == null) return new ReferenceCollection();

var rimworldSymbolScope = ScopeHelper.RimworldScope;
var allSymbolScopes = ScopeHelper.AllScopes;

if (classContext.GetType().Name == "Enum")
{
var @class = rimworldSymbolScope.GetElementsByShortName(classContext.ShortName).FirstOrDefault();
var col = new ReferenceCollection(new RimworldXmlReference(@class, element));

return col;
// return new ReferenceCollection();
}

if (!ScopeHelper.ExtendsFromVerseDef(classContext.GetClrName().FullName))
Expand Down Expand Up @@ -169,15 +173,21 @@ private ReferenceCollection GetReferencesForText(ITreeNode element, ReferenceCol
private ReferenceCollection GetReferenceForDeclaredElement(ITreeNode element, ReferenceCollection oldReferences)
{
// We're currently in a text node inside a <defName> inside another ThingDef node. We want to get that node
var defTypeName = element.Parent?.Parent?
// Alternatively, we may be in a string node inside a Name Attribute, so we need to go a step further
var parentTag = element.Parent?.Parent;
if (parentTag is not XmlTag && parentTag?.Parent is XmlTag) parentTag = parentTag.Parent;

if (parentTag is null) return new ReferenceCollection();

var defTypeName = parentTag
// And then get the TagHeader (<ThingDef>) of that node
.Children().FirstOrDefault(childElement => childElement is XmlTagHeaderNode)?
// And then get the text that provides the ID of that node (ThingDef)
.Children().FirstOrDefault(childElement => childElement is XmlIdentifier)?.GetText();

if (defTypeName is null) new ReferenceCollection();

var defName = element.GetText();
if (defTypeName is null) return new ReferenceCollection();
var defName = element.GetText().Trim('"');

var xmlSymbolTable = element.GetSolution().GetComponent<RimworldSymbolScope>();

Expand Down
56 changes: 47 additions & 9 deletions src/dotnet/ReSharperPlugin.RimworldDev/RimworldXMLItemProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ protected override bool IsAvailable(RimworldXmlCodeCompletionContext context)
if (context.TreeNode is XmlTagEndToken && context.TreeNode.PrevSibling is XmlIdentifier &&
context.TreeNode.PrevSibling.PrevSibling?.GetText() != "</") return true;

if (context.TreeNode is XmlValueToken &&
context.TreeNode.Parent is XmlAttribute attribute &&
attribute.AttributeName == "ParentName"
) return true;

return false;
}

Expand Down Expand Up @@ -79,7 +84,6 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
if (context.TreeNode is XmlFloatingTextToken && context.TreeNode.NodeType.ToString() == "TEXT")
{
AddTextLookupItems(context, collector);
var a = 1 + 1;
return base.AddLookupItems(context, collector);
}

Expand All @@ -90,6 +94,15 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
return base.AddLookupItems(context, collector);
}

if (context.TreeNode is XmlValueToken &&
context.TreeNode.Parent is XmlAttribute attribute &&
attribute.AttributeName == "ParentName"
)
{
AddParentNameItems(context, collector);
return base.AddLookupItems(context, collector);
}

/**
* <Defs>
* <{CARET HERE
Expand Down Expand Up @@ -125,6 +138,33 @@ protected override bool AddLookupItems(RimworldXmlCodeCompletionContext context,
return base.AddLookupItems(context, collector);
}

protected void AddParentNameItems(RimworldXmlCodeCompletionContext context, IItemsCollector collector)
{
if (context.TreeNode?.Parent is not XmlAttribute attribute) return;
if (attribute.Parent is not XmlTagHeaderNode defTag) return;

var defClassName = defTag.ContainerName;
var defClass = ScopeHelper.GetScopeForClass(defClassName);

var xmlSymbolTable = context.TreeNode!.GetSolution().GetSolution().GetComponent<RimworldSymbolScope>();

var keys = xmlSymbolTable.GetDefsByType(defClassName);

foreach (var key in keys)
{
if (!xmlSymbolTable.IsDefAbstract(key)) continue;

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);
}
}

protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IItemsCollector collector)
{
var hierarchy = GetHierarchy(context.TreeNode);
Expand Down Expand Up @@ -197,8 +237,6 @@ protected void AddTextLookupItems(RimworldXmlCodeCompletionContext context, IIte
new DeclaredElementInstance(new XMLTagDeclaredElement(item, defType, defName, false)));
collector.Add(lookup);
}

return;
}

protected void AddThingDefClasses(RimworldXmlCodeCompletionContext context, IItemsCollector collector,
Expand Down Expand Up @@ -296,7 +334,7 @@ public static List<IField> GetAllPublicFields(ITypeElement desiredClass, ISymbol
.FirstOrDefault(attribute => attribute.GetClrName().FullName == "Verse.LoadAliasAttribute");

if (loadAliasAttributes != null) return true;

return false;
})
.Select(member => member.Member)
Expand Down Expand Up @@ -499,18 +537,18 @@ attribute is MetadataAttributeInstance
);

if (loadAliasAttribute == null) continue;

var writer = new StringWriter(new StringBuilder());
((MetadataAttributeInstance) loadAliasAttribute).Dump(writer, "");
((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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,28 @@
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.ReSharper.Psi.Xml.Impl.Tree;
using JetBrains.ReSharper.Psi.Xml.Tree;
using ReSharperPlugin.RimworldDev.TypeDeclaration;

namespace ReSharperPlugin.RimworldDev.SymbolScope;

public struct DefTag
{
public DefTag(ITreeNode treeNode, bool isAbstract = false)
{
TreeNode = treeNode;
IsAbstract = isAbstract;
}

public ITreeNode TreeNode { get; }
public bool IsAbstract { get; }
}

[PsiComponent]
public class RimworldSymbolScope : SimpleICache<List<RimworldXmlDefSymbol>>
{
private Dictionary<string, ITreeNode> DefTags = new();
private Dictionary<string, DefTag> DefTags = new();
private Dictionary<string, string> ExtraDefTagNames = new();
private Dictionary<string, XMLTagDeclaredElement> _declaredElements = new();
private SymbolTable _symbolTable;
Expand Down Expand Up @@ -57,7 +70,12 @@ public ITreeNode GetTagByDef(string defId)
if (!DefTags.ContainsKey(defId))
return null;

return DefTags[defId];
return DefTags[defId].TreeNode;
}

public bool IsDefAbstract(string defId)
{
return DefTags.ContainsKey(defId) && DefTags[defId].IsAbstract;
}

public DefNameValue GetDefName(DefNameValue value) =>
Expand Down Expand Up @@ -87,15 +105,33 @@ public override object Build(IPsiSourceFile sourceFile, bool isStartup)
var tags = xmlFile.GetNestedTags<IXmlTag>("Defs/*").Where(tag =>
{
var defNameTag = tag.GetNestedTags<IXmlTag>("defName").FirstOrDefault();
return defNameTag is not null;
if (defNameTag is not null) return true;

var nameAttribute = tag.GetAttribute("Name");
return nameAttribute is not null;
});

List<RimworldXmlDefSymbol> defs = new();

foreach (var tag in tags)
{
var defName = tag.GetNestedTags<IXmlTag>("defName").FirstOrDefault()?.InnerText;
var defNameTag = tag.GetNestedTags<IXmlTag>("defName").FirstOrDefault().Children().ElementAt(1);
var defName = tag
.GetNestedTags<IXmlTag>("defName")
.FirstOrDefault()?.InnerText ??
tag
.GetAttribute("Name")?
.Children()
.FirstOrDefault(element => element is IXmlValueToken)?
.GetUnquotedText();

var defNameTag = tag.GetNestedTags<IXmlTag>("defName").
FirstOrDefault()?.
Children().
ElementAt(1) ??
tag.GetAttribute("Name")?.
Children().
FirstOrDefault(element => element is IXmlValueToken);

if (defName is null) continue;

defs.Add(new RimworldXmlDefSymbol(defNameTag, defName, tag.GetTagName()));
Expand Down Expand Up @@ -131,12 +167,27 @@ private void AddToLocalCache(IPsiSourceFile sourceFile, [CanBeNull] List<Rimworl
cacheItem?.ForEach(item =>
{
var matchingDefTag = xmlFile
.GetNestedTags<IXmlTag>("Defs/*/defName").FirstOrDefault(tag =>
tag.Children().ElementAt(1).GetTreeStartOffset().Offset == item.DocumentOffset);

.GetNestedTags<IXmlTag>("Defs/*/defName").FirstOrDefault(tag =>
tag.Children().ElementAt(1).GetTreeStartOffset().Offset ==
item.DocumentOffset) ??
xmlFile
.GetNestedTags<IXmlTag>("Defs/*")
.FirstOrDefault(tag =>
tag.GetAttribute("Name")?
.Children()
.FirstOrDefault(element => element is IXmlValueToken)?
.GetTreeStartOffset().Offset == item.DocumentOffset
)?
.GetAttribute("Name")?
.Children()
.FirstOrDefault(element => element is IXmlValueToken);

if (matchingDefTag is null) return;

var xmlTag = matchingDefTag.Children().ElementAt(1);
// If the DefName is in a [Name=""] Attribute, it'll be matched to a XmlValueToken, which doesn't have any
// children. Otherwise, it'll be matched to the XmlTag for <defName>, where we want the first child as the
// string value
var xmlTag = matchingDefTag is IXmlValueToken ? matchingDefTag : matchingDefTag.Children().ElementAt(1);

AddDefTagToList(item, xmlTag);
});
Expand Down Expand Up @@ -167,10 +218,15 @@ void AddDefTagToList(RimworldXmlDefSymbol item, ITreeNode xmlTag)
}
}

var isAbstract = xmlTag is IXmlValueToken &&
xmlTag.Parent?.Parent is XmlTagHeaderNode defTypeTag &&
defTypeTag.GetAttribute("Abstract") is {} attribute &&
attribute.UnquotedValue.ToLower() == "true";

if (!DefTags.ContainsKey($"{item.DefType}/{item.DefName}"))
DefTags.Add($"{item.DefType}/{item.DefName}", xmlTag);
DefTags.Add($"{item.DefType}/{item.DefName}", new DefTag(xmlTag, isAbstract));
else
DefTags[$"{item.DefType}/{item.DefName}"] = xmlTag;
DefTags[$"{item.DefType}/{item.DefName}"] = new DefTag(xmlTag, isAbstract);
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions src/rider/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<idea-plugin require-restart="true">
<id>com.jetbrains.rider.plugins.rimworlddev</id>
<name>Rimworld Development Environment</name>
<version>2024.1</version>
<version>2024.2</version>
<vendor url="https://github.com/Garethp/Rider-RimworldDevelopment">Garethp</vendor>
<idea-version since-build="241" />
<depends>com.intellij.modules.rider</depends>
Expand All @@ -18,11 +18,7 @@ in your mods!</p>
<change-notes>
<![CDATA[
<p><ul>
<li>Built for 2024.1</li>
<li>Added References from C# to XML for DefDatabase and [DefOf]</li>
<li>When defining a RunConfig, you can now select a ModList config and Save file to be loaded for just this one launch</li>
<li>Added support for `[LoadAlias]`</li>
<li>New Mod Template now adds a PublisherPlus configuration file</li>
<li>Add support for Parent="" referencesin XML defs</li>
</ul></p>
]]>
</change-notes>
Expand Down