From 79a2f15dbca6b281e5bb3956ef2d2c5fbad44962 Mon Sep 17 00:00:00 2001
From: sator-imaging <16752340+sator-imaging@users.noreply.github.com>
Date: Mon, 12 Aug 2024 11:48:19 +0900
Subject: [PATCH] feat: AssemblyDefinitionSettingsProvider
---
.../AssemblyDefinitionSettingsProvider.meta | 8 +
...ider.ImplicitAssemblyReferenceProcessor.cs | 207 ++++++++
...ImplicitAssemblyReferenceProcessor.cs.meta | 11 +
...ssemblyDefinitionSettingsProvider.Prefs.cs | 72 +++
...lyDefinitionSettingsProvider.Prefs.cs.meta | 11 +
.../AssemblyDefinitionSettingsProvider.cs | 484 ++++++++++++++++++
...AssemblyDefinitionSettingsProvider.cs.meta | 11 +
7 files changed, 804 insertions(+)
create mode 100644 Editor/AssemblyDefinitionSettingsProvider.meta
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs
create mode 100644 Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs.meta
diff --git a/Editor/AssemblyDefinitionSettingsProvider.meta b/Editor/AssemblyDefinitionSettingsProvider.meta
new file mode 100644
index 0000000..6eef396
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7d70236340c3ef24d88290e9ef6ac59c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs
new file mode 100644
index 0000000..3724a27
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs
@@ -0,0 +1,207 @@
+/** Assembly Definitions Manager for Project Settings Panel
+ ** (c) 2024 https://github.com/sator-imaging
+ ** Licensed under the MIT License
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using UnityEditor;
+using UnityEditorInternal;
+using UnityEngine;
+
+#nullable enable
+
+namespace SatorImaging.UnityFundamentals.Editor
+{
+ partial class AssemblyDefinitionSettingsProvider
+ {
+ public sealed class ImplicitAssemblyReferenceProcessor : AssetPostprocessor
+ {
+ const string LOG_PREFIX = "[" + nameof(ImplicitAssemblyReferenceProcessor) + "] ";
+
+
+ /// Get or set processing order. Smaller value to be invoked earlier.
+ public int PostprocessOrder { get; set; } = 0;
+
+ public override int GetPostprocessOrder() => PostprocessOrder;
+
+
+ /* preprocess ================================================================ */
+
+ // be static. not sure whether processing is done by one instance or multiple instances
+ readonly static HashSet _processedFilePathSet = new();
+
+ public void OnPreprocessAsset()
+ {
+ if (assetImporter is not AssemblyDefinitionImporter)
+ return;
+
+ if (!Prefs.Instance.EnableImplicitRefsOnChanges)
+ return;
+
+ // avoid unexpected loop
+ if (!_processedFilePathSet.Add(assetPath))
+ {
+ //Debug.Log(LOG_PREFIX + "already processed: " + assetPath);
+ return;
+ }
+
+ ResetIntegratedInspector();
+ LockIntegratedInspector(true);
+ if (!TryAddImplicitReferences(assetPath))
+ {
+ // don't require. domain reloading will happen by asset import --> LockIntegratedInspector(false);
+ }
+ }
+
+
+ /* internals ================================================================ */
+
+ static StringBuilder? cache_sb;
+
+ public static bool TryAddImplicitReferences(string filePath)
+ {
+ if (!File.Exists(filePath))
+ return false;
+
+ if (!filePath.StartsWith(ASSETS_DIR_SLASH))
+ return false;
+
+ var jsonText = File.ReadAllText(filePath, Encoding.UTF8);
+ var jsonObj = JsonUtility.FromJson(jsonText);
+ var name = jsonObj?.name
+ ?? throw new NotSupportedException("no assembly name: " + filePath);
+ var prefs = Prefs.Instance;
+
+ // don't modify implicit refs source
+ if (!(prefs.ImplicitReferenceNames?.Length > 0) || prefs.ImplicitReferenceNames.Contains(name, StringComparer.OrdinalIgnoreCase))
+ return false;
+
+ bool isGuidMode = true == jsonObj.references?.FirstOrDefault()?.StartsWith(GUID_MODE_PREFIX, StringComparison.OrdinalIgnoreCase);
+ var implicitRefs = (isGuidMode ? prefs.ImplicitReferenceGUIDs : prefs.ImplicitReferenceNames).AsEnumerable();
+
+ if (jsonObj.references != null)
+ {
+ implicitRefs = implicitRefs
+ .Where(x => !jsonObj.references.Contains(x, StringComparer.OrdinalIgnoreCase)) // ignore case for safe
+ ;
+ }
+
+ if (!implicitRefs.Any())
+ {
+ return false;
+ }
+
+ SPECIAL_CASE_FOR_NEWLY_CREATED_FILE:
+ var pos_references = jsonText.AsSpan().IndexOf(JSON_REFS_INSERT_POSITION_FINDER, StringComparison.Ordinal);
+ if (pos_references < 0)
+ {
+ // NOTE: newly created .asmdef has only "name" property. ie. has no ','
+ if (jsonText.Contains(','))
+ {
+ Debug.LogWarning(LOG_PREFIX + "no references property: " + jsonText);
+ return false;
+ }
+
+ jsonText = jsonText.AsSpan().TrimEnd()[..^1].TrimEnd().ToString().Replace("\t", JSON_INDENT)
+ + "," + Environment.NewLine + JSON_REFS_INSERT_POSITION_FINDER + "]" + Environment.NewLine + '}';
+ goto SPECIAL_CASE_FOR_NEWLY_CREATED_FILE;
+ }
+
+ pos_references += JSON_REFS_INSERT_POSITION_FINDER.Length;
+
+ var sb = (cache_sb ??= new());
+ sb.Append(jsonText.AsSpan(0, pos_references));
+ sb.Append(JSON_REFS_ARRAY_ITEM_OPEN.AsSpan());
+ sb.AppendJoin(JSON_REFS_ARRAY_ITEM_CLOSE, implicitRefs);
+ sb.Append('"'); // don't forget closing quote. opening quote is included in ITEM OPEN/CLOSE
+ if (jsonText[pos_references] != ']')
+ {
+ if (jsonObj.references?.Length > 0)
+ sb.Append(',');
+ }
+ else
+ {
+ sb.AppendLine();
+ sb.Append(JSON_INDENT.AsSpan());
+ }
+ sb.Append(jsonText.AsSpan(pos_references));
+
+ var result = sb.ToString();
+ sb.Length = 0; // don't clear to keep allocated buffer!
+
+ File.WriteAllText(filePath, result, Encoding.UTF8);
+ //AssetDatabase.ImportAsset(filePath);
+
+ Debug.Log(LOG_PREFIX + filePath + "...\n" + result);
+
+ return true;
+ }
+
+
+ readonly static Regex re_emptyRef = new(@"^\s+"""",?[\r\n]+", RegexOptions.Compiled | RegexOptions.Multiline);
+
+ public static bool TryRemoveImplicitReferences(string filePath)
+ {
+ if (!File.Exists(filePath))
+ return false;
+
+ if (!filePath.StartsWith(ASSETS_DIR_SLASH))
+ return false;
+
+ // try remove both guid and name
+ var implicitRefs = Prefs.Instance.ImplicitReferenceGUIDs?.Concat(Prefs.Instance.ImplicitReferenceNames);
+ if (implicitRefs == null)
+ return false;
+
+ var contentSpan = File.ReadAllText(filePath, Encoding.UTF8).ToCharArray().AsSpan();
+ var originalLength = contentSpan.Length;
+
+ // NOTE: remove only references entries
+ int pos_references = ((ReadOnlySpan)contentSpan).IndexOf(JSON_REFS_INSERT_POSITION_FINDER, StringComparison.Ordinal);
+ if (pos_references < 0)
+ return false;
+
+ int len_references = contentSpan.Slice(pos_references).IndexOf(']');
+ if (len_references < 0)
+ return false;
+
+ var consumed = contentSpan.Length;
+ int pos;
+ foreach (var item in implicitRefs)
+ {
+ // not sure why implicit cast operator doesn't work
+ while ((pos = ((ReadOnlySpan)contentSpan).Slice(pos_references, len_references).IndexOf(item, StringComparison.Ordinal)) >= 0)
+ {
+ pos += pos_references;
+ contentSpan.Slice(pos + item.Length).CopyTo(contentSpan.Slice(pos));
+
+ consumed -= item.Length;
+ len_references -= item.Length;
+ }
+ }
+
+ if (originalLength == consumed)
+ {
+ return false;
+ }
+
+ var result = contentSpan.Slice(0, consumed).ToString();
+ result = re_emptyRef.Replace(result, string.Empty);
+
+ File.WriteAllText(filePath, result, Encoding.UTF8);
+ //AssetDatabase.ImportAsset(filePath);
+
+ Debug.Log(LOG_PREFIX + filePath + "...\n" + result);
+
+ return true;
+ }
+
+ }
+
+ }
+}
diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta
new file mode 100644
index 0000000..842a8d1
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.ImplicitAssemblyReferenceProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ba90ea4ba17e8fd458fa605554740f23
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs
new file mode 100644
index 0000000..9d0a6f9
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs
@@ -0,0 +1,72 @@
+/** Assembly Definitions Manager for Project Settings Panel
+ ** (c) 2024 https://github.com/sator-imaging
+ ** Licensed under the MIT License
+ */
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using UnityEngine;
+
+#nullable enable
+
+namespace SatorImaging.UnityFundamentals.Editor
+{
+ partial class AssemblyDefinitionSettingsProvider
+ {
+ [Serializable]
+ public sealed class Prefs
+ {
+ // json fields
+ [SerializeField] bool enableImplicitRefsOnChanges = false;
+ [SerializeField] bool turnOnImplicitRefsOnChangesAfterDomainReloading = false;
+ [SerializeField] string[]? implicitReferenceGUIDs;
+ [SerializeField] string[]? implicitReferenceNames;
+
+ //properties
+ ///
+ ///
+ public string[]? ImplicitReferenceNames
+ {
+ get { return implicitReferenceNames; }
+ set { implicitReferenceNames = value; }
+ }
+
+ public string[]? ImplicitReferenceGUIDs
+ {
+ get { return implicitReferenceGUIDs; }
+ set { implicitReferenceGUIDs = value; }
+ }
+
+ public bool EnableImplicitRefsOnChanges
+ {
+ get { return enableImplicitRefsOnChanges; }
+ set { enableImplicitRefsOnChanges = value; }
+ }
+
+ public bool TurnOnImplicitRefsOnChangesAfterDomainReloading
+ {
+ get { return turnOnImplicitRefsOnChangesAfterDomainReloading; }
+ set { turnOnImplicitRefsOnChangesAfterDomainReloading = value; }
+ }
+
+
+ // export path
+ readonly static string OUTPUT_PATH = Application.dataPath
+ + "/../ProjectSettings/" + nameof(AssemblyDefinitionSettingsProvider) + ".json";
+
+ private Prefs()
+ {
+ if (File.Exists(OUTPUT_PATH))
+ JsonUtility.FromJsonOverwrite(File.ReadAllText(OUTPUT_PATH, Encoding.UTF8), this);
+ }
+
+ volatile static Prefs? _instance;
+ public static Prefs Instance => _instance ?? Interlocked.CompareExchange(ref _instance, new(), null) ?? _instance;
+
+ public void Save() => File.WriteAllText(OUTPUT_PATH, JsonUtility.ToJson(this, true), Encoding.UTF8);
+ }
+
+ }
+}
diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta
new file mode 100644
index 0000000..96ef25e
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.Prefs.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2a6149383e9b74d4984d7c58b24537c9
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs
new file mode 100644
index 0000000..86f958f
--- /dev/null
+++ b/Editor/AssemblyDefinitionSettingsProvider/AssemblyDefinitionSettingsProvider.cs
@@ -0,0 +1,484 @@
+/** Assembly Definitions Manager for Project Settings Panel
+ ** (c) 2024 https://github.com/sator-imaging
+ ** Licensed under the MIT License
+ */
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using UnityEditor;
+using UnityEditor.AssetImporters;
+using UnityEditor.Compilation;
+using UnityEditorInternal;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+#nullable enable
+
+namespace SatorImaging.UnityFundamentals.Editor
+{
+ public sealed partial class AssemblyDefinitionSettingsProvider : SettingsProvider
+ {
+ const string LOG_PREFIX = "[" + nameof(AssemblyDefinitionSettingsProvider) + "] ";
+
+ readonly static string DISPLAY_NAME = "Assembly Definitions";
+
+ readonly static string GUID_MODE_PREFIX = "GUID:";
+ readonly static string ASSETS_DIR_SLASH = "Assets/";
+ //readonly static string PACKAGES_DIR_SLASH = "Packages/";
+ readonly static string JSON_INDENT = @" ";
+ readonly static string JSON_REFS_INSERT_POSITION_FINDER = JSON_INDENT + @"""references"": [";
+ readonly static string JSON_REFS_ARRAY_ITEM_OPEN = Environment.NewLine + JSON_INDENT + JSON_INDENT + "\"";
+ readonly static string JSON_REFS_ARRAY_ITEM_CLOSE = "\"," + JSON_REFS_ARRAY_ITEM_OPEN;
+
+ // json representation
+ [Serializable] sealed class AssemDef_JsonLoader { public string? name; public string[]? references; }
+
+
+ public AssemblyDefinitionSettingsProvider(string path, SettingsScope scopes, IEnumerable? keywords = null)
+ : base(path, scopes, keywords) { }
+
+ [SettingsProvider]
+ public static SettingsProvider CreateProvider()
+ {
+ return new AssemblyDefinitionSettingsProvider("Project/" + DISPLAY_NAME, SettingsScope.Project, null);
+ }
+
+
+ /* activate/deactivate ================================================================ */
+
+ readonly List _implicitADefList = new();
+
+ [DescriptionAttribute("tuple: (assetPath, fileNameNoExt)")]
+ readonly List<(string assetPath, string fileNameNoExt)> _assetsADefInfoList = new();
+ readonly List _assetsADefLabelList = new();
+
+ public override void OnDeactivate()
+ {
+ base.OnDeactivate();
+
+ //Debug.Log(LOG_PREFIX + nameof(OnDeactivate));
+ Prefs.Instance.Save();
+ }
+
+
+ public override void OnActivate(string searchContext, VisualElement rootElement)
+ {
+ base.OnActivate(searchContext, rootElement);
+
+ if (_implicitADefList.Count == 0)
+ {
+ var prefs = Prefs.Instance; // to load data
+
+ var implicitRefAssets = prefs.ImplicitReferenceGUIDs
+ .Select(static x => AssetDatabase.GUIDToAssetPath(x.Substring(GUID_MODE_PREFIX.Length)))
+ .Select(static x => AssetDatabase.LoadAssetAtPath(x))
+ .Where(static x => x != null)
+ ;
+
+ _implicitADefList.AddRange(implicitRefAssets);
+ }
+
+ RefreshAssetsADefCaches();
+ }
+
+
+ void RefreshAssetsADefCaches()
+ {
+ var assetsInAssetsFolder = AssetDatabase.FindAssets("t:" + nameof(AssemblyDefinitionAsset))
+ .Select(static guid => AssetDatabase.GUIDToAssetPath(guid))
+ .Where(static path => path.StartsWith(ASSETS_DIR_SLASH))
+ .Select(static path =>
+ {
+ var fileNameNoExt = Path.GetFileNameWithoutExtension(path);
+ return (path, fileNameNoExt);
+ })
+ ;
+
+ _assetsADefInfoList.Clear();
+ _assetsADefInfoList.AddRange(assetsInAssetsFolder);
+
+ _assetsADefLabelList.Clear();
+ _assetsADefLabelList.AddRange(assetsInAssetsFolder.Select(static x => new GUIContent(x.fileNameNoExt, x.path)));
+ }
+
+
+ /* OnGUI ================================================================ */
+
+ readonly GUIContent gui_implicitRefsLabel = new("Implicit Assembly References");
+ readonly GUIContent gui_enableImplicitRefsLabel = new("Auto Update .asmdef Files on Import Event");
+ readonly GUIContent gui_updateBtnLabel = new("Update All .asmdef *", "Only files in `Assets/` folder");
+ readonly GUIContent gui_removeBtnLabel = new("Remove from All");
+ readonly GUIContent gui_assetsADefListLabel
+ = new("Assembly Definitions in Assets *", "Click on selected list item will unveil .asmdef file in Project panel");
+ readonly GUIContent gui_updateADefListLabel = new("Reload List");
+ readonly GUIContent gui_dialogHelpText = new("[Integrated Inspector] *experimental");
+ //readonly Texture gui_searchIcon = EditorGUIUtility.IconContent("d_Search Icon").image;
+ ReorderableList? gui_implicitRefsReorderableList;
+ GUIStyle? style_largeBtn;
+ GUIStyle? style_linkBtn;
+ GUIStyle? style_activeLinkBtn;
+ GUIStyle? style_sectionHeaderLabel;
+ Vector2 gui_assetsADefScrollPos;
+ Vector2 gui_miniInspectorScrollPos;
+
+ // fields must be reset if selection changed
+ static UnityEditor.Editor? gui_cachedEditor;
+ static string? _activeADefAssetPath;
+ static bool _asmdefHasModified_last = false;
+ static bool _asmdefHasModified_changed = false;
+
+ static bool _modifingAssetFiles = false;
+
+
+ public override void OnGUI(string searchContext)
+ {
+ const int MAX_PANEL_WIDTH = 400;
+
+ base.OnGUI(searchContext);
+
+ if (style_largeBtn == null)
+ {
+ style_largeBtn = new(EditorStyles.miniButton);
+ style_largeBtn.fontSize = 14;
+ style_largeBtn.richText = true;
+ style_largeBtn.fixedHeight = 28;
+ style_largeBtn.padding = new(16, 16, 0, 0);
+ }
+
+ if (style_linkBtn == null)
+ {
+ style_linkBtn = new(EditorStyles.linkLabel);
+ style_linkBtn.stretchWidth = true;
+ style_linkBtn.fontSize = 13;
+ style_linkBtn.fixedHeight = 22;
+ }
+
+ if (style_activeLinkBtn == null)
+ {
+ style_activeLinkBtn = new(EditorStyles.selectionRect);
+ style_activeLinkBtn.stretchWidth = style_linkBtn.stretchWidth;
+ style_activeLinkBtn.fontSize = style_linkBtn.fontSize;
+ style_activeLinkBtn.fixedHeight = style_linkBtn.fixedHeight;
+ style_activeLinkBtn.margin = new(0, 0, 0, 0);
+ style_activeLinkBtn.padding.left = 6;
+ }
+
+ if (style_sectionHeaderLabel == null)
+ {
+ style_sectionHeaderLabel = new(EditorStyles.largeLabel);
+ style_sectionHeaderLabel.fontSize = 15;
+ style_sectionHeaderLabel.fixedHeight = 32;
+ style_sectionHeaderLabel.stretchWidth = true;
+ }
+
+ if (gui_implicitRefsReorderableList == null)
+ {
+ gui_implicitRefsReorderableList = new(_implicitADefList, typeof(AssemblyDefinitionAsset), true, false, true, true);
+ gui_implicitRefsReorderableList.drawElementCallback += (rect, index, isActive, isFocused) =>
+ {
+ _implicitADefList[index] = EditorGUI.ObjectField(rect, _implicitADefList[index], typeof(AssemblyDefinitionAsset), false)
+ as AssemblyDefinitionAsset;
+ };
+ gui_implicitRefsReorderableList.onAddCallback = self =>
+ {
+ self.list.Add(null);
+ };
+ }
+
+
+ // start!!
+ var leftPanelWidth = GUILayout.Width(Math.Min(MAX_PANEL_WIDTH, EditorGUIUtility.currentViewWidth * 0.333f));
+
+ using var rootLayout = new EditorGUILayout.HorizontalScope();
+
+ // left side panel
+ using (new EditorGUILayout.VerticalScope(leftPanelWidth))
+ {
+ EditorGUILayout.Space();
+
+ EditorGUILayout.LabelField(gui_implicitRefsLabel, style_sectionHeaderLabel);
+
+ // TODO: add "apply all changes" button to allow changing multiple .asmdef files at once.
+ bool disallowChangeSelection = false;
+ if (gui_cachedEditor is AssetImporterEditor currentEditor)
+ {
+ var hasModified = currentEditor.HasModified();
+ _asmdefHasModified_changed = hasModified != _asmdefHasModified_last;
+ _asmdefHasModified_last = hasModified;
+
+ disallowChangeSelection = hasModified;
+ }
+
+ using (new EditorGUI.DisabledScope(disallowChangeSelection || _modifingAssetFiles))
+ {
+ // implicit refs list
+ using (var cc = new EditorGUI.ChangeCheckScope())
+ {
+ gui_implicitRefsReorderableList.DoLayoutList();
+
+ if (cc.changed)
+ {
+ var snapshot = _implicitADefList
+ .Where(static x => x != null)
+ .Select(static x =>
+ {
+ (string name, string guid) result = (string.Empty, string.Empty);
+
+ if (x != null && !string.IsNullOrWhiteSpace(x.text))
+ {
+ if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(x, out var guid, out long id))
+ {
+ // NOTE: x.name is FILE NAME, reference requires JSON's NAME
+ var jsonObj = JsonUtility.FromJson(x.text);
+
+ result.name = jsonObj?.name ?? string.Empty;
+ result.guid = GUID_MODE_PREFIX + guid;
+
+ //Debug.Log(LOG_PREFIX + result.guid + " / " + result.name);
+ }
+ }
+
+ return result;
+ })
+ .Where(static x => x.name.Length > 0 && x.guid.Length > 0)
+ .Distinct()
+ ;
+
+ Prefs.Instance.ImplicitReferenceGUIDs = snapshot.Select(static x => x.guid).ToArray();
+ Prefs.Instance.ImplicitReferenceNames = snapshot.Select(static x => x.name).ToArray();
+ }
+ }
+
+
+ EditorGUILayout.Space(-EditorGUIUtility.singleLineHeight * 1.0f);
+
+ using (new GUILayout.HorizontalScope())
+ {
+ Prefs.Instance.EnableImplicitRefsOnChanges
+ = EditorGUILayout.ToggleLeft(gui_enableImplicitRefsLabel, Prefs.Instance.EnableImplicitRefsOnChanges, EditorStyles.largeLabel);
+
+ EditorGUILayout.Space();
+ }
+
+ EditorGUILayout.Space();
+
+
+ /* = add/remove buttons = */
+ using (new EditorGUILayout.HorizontalScope())
+ {
+ if (GUILayout.Button(gui_updateBtnLabel, style_largeBtn))
+ {
+ LockAndResetIntegratedInspectorThenUpdateAssets(ImplicitAssemblyReferenceProcessor.TryAddImplicitReferences);
+ }
+
+ if (GUILayout.Button(gui_removeBtnLabel, style_largeBtn))
+ {
+ LockAndResetIntegratedInspectorThenUpdateAssets(ImplicitAssemblyReferenceProcessor.TryRemoveImplicitReferences);
+ }
+
+ GUILayout.FlexibleSpace();
+ }
+
+
+ /* = assets ADef list = */
+
+ EditorGUILayout.Space();
+
+ using (new GUILayout.HorizontalScope())
+ {
+ EditorGUILayout.LabelField(gui_assetsADefListLabel, style_sectionHeaderLabel);
+
+ if (GUILayout.Button(gui_updateADefListLabel))
+ {
+ RefreshAssetsADefCaches();
+ }
+ }
+
+ using (var sv_assetsADef = new GUILayout.ScrollViewScope(gui_assetsADefScrollPos))
+ {
+ gui_assetsADefScrollPos = sv_assetsADef.scrollPosition;
+
+ for (int i = 0; i < _assetsADefLabelList.Count; i++)
+ {
+ var guiLabel = _assetsADefLabelList[i];
+ var fileNameNoExt = guiLabel.text;
+ var assetPath = guiLabel.tooltip;
+
+ var btnStyle = assetPath == _activeADefAssetPath ? style_activeLinkBtn : style_linkBtn;
+ if (GUILayout.Button(guiLabel, btnStyle))
+ {
+ if (_activeADefAssetPath == assetPath)
+ {
+ var asset = AssetDatabase.LoadAssetAtPath(assetPath);
+ EditorGUIUtility.PingObject(asset);
+ }
+ else
+ {
+ var importer = AssetImporter.GetAtPath(assetPath) as AssemblyDefinitionImporter;
+ if (importer != null)
+ {
+ ResetIntegratedInspector();
+ _activeADefAssetPath = assetPath; // must be set after Reset
+
+ UnityEditor.Editor.CreateCachedEditor(importer, null, ref gui_cachedEditor);
+
+ if (gui_cachedEditor is AssetImporterEditor)
+ {
+ // required to show Apply/Revert button, when not, any changes will be automatically applied and cause domain reloading
+ //https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/AssetPipelineEditor/ImportSettings/AssetImporterEditor.cs#L173
+ MethodInfo InternalSetAssetImporterTargetEditor;
+ InternalSetAssetImporterTargetEditor = gui_cachedEditor.GetType()
+ .GetMethod(nameof(InternalSetAssetImporterTargetEditor), BindingFlags.NonPublic | BindingFlags.Instance);
+
+ InternalSetAssetImporterTargetEditor.Invoke(gui_cachedEditor, new object[] { gui_cachedEditor });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ /* = right side panel = */
+
+ //https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/AssemblyDefinitionImporterInspector.cs
+ if (gui_cachedEditor != null)
+ {
+ using (var sv_miniInspector = new EditorGUILayout.ScrollViewScope(gui_miniInspectorScrollPos))
+ {
+ gui_miniInspectorScrollPos = sv_miniInspector.scrollPosition;
+
+ if (_asmdefHasModified_changed)
+ Prefs.Instance.Save();
+
+ try
+ {
+ // TODO: don't ignore exception...!
+ // NOTE: when 'Apply' button is pressed, stack overflow will happen ...!!
+ // ...
+ // ...
+ // at UnityEditor.AssetImporters.AssetImporterEditor.get_preview
+ // at UnityEditor.Editor.ReloadPreviewInstances
+ // at UnityEditor.Editor.ReloadPreviewInstances
+ // at UnityEditor.Editor.ReloadPreviewInstances...
+ // ...
+ // ...
+ gui_cachedEditor.OnInspectorGUI();
+ }
+ catch
+ {
+ EditorGUIUtility.ExitGUI();
+
+ ResetIntegratedInspector();
+
+ EditorGUIUtility.ExitGUI();
+ }
+ }
+ }
+ }
+
+
+ /*
+ public override void OnTitleBarGUI()
+ {
+ if (_activeADefAssetPath != null)
+ {
+ EditorGUILayout.HelpBox(gui_dialogHelpText.text, MessageType.Info, true);
+ }
+ }
+ */
+
+
+ /* integrated inspector ================================================================ */
+
+ [InitializeOnLoadMethod]
+ static void UnityEditor_Initialize()
+ {
+ // NOTE: don't remove this!!
+ // reset is required before compilation and selection change!!
+ CompilationPipeline.compilationStarted -= OnCompilationStarted;
+ CompilationPipeline.compilationStarted += OnCompilationStarted;
+
+ var prefs = Prefs.Instance;
+ if (prefs.TurnOnImplicitRefsOnChangesAfterDomainReloading)
+ {
+ prefs.EnableImplicitRefsOnChanges = true;
+ prefs.TurnOnImplicitRefsOnChangesAfterDomainReloading = false;
+ prefs.Save();
+ }
+ }
+
+
+ public static void LockIntegratedInspector(bool isLocked) => _modifingAssetFiles = isLocked;
+
+
+ readonly static Action