Forge
csharpdeeb25a2
1using System.Reflection;
2using Avalonia.Controls;
3using CascadeIDE.Models;
4
5namespace CascadeIDE.Services;
6
7/// <summary>
8/// Автоматическая навигация настроек: <see cref="SettingsPanelAttribute"/> на <c>*PanelView</c>.
9/// <c>settings-categories.toml</c> — опциональный overlay (порядок, подписи, скрытие).
10/// </summary>
11public static class SettingsPanelRegistry
12{
13 public static IReadOnlyList<SettingsCategoryDefinition> LoadOrdered()
14 {
15 var discovered = DiscoverFromAssembly();
16 if (discovered.Count == 0)
17 discovered = SettingsCategoryCatalog.LoadBuiltinFallbackList();
18
19 if (!TryLoadTomlOverlay(out var overlay) || overlay.Count == 0)
20 return discovered;
21
22 return Merge(discovered, overlay);
23 }
24
25 public static IReadOnlyList<SettingsCategoryDefinition> DiscoverFromAssembly()
26 {
27 var assembly = typeof(SettingsPanelRegistry).Assembly;
28 var list = new List<(SettingsPanelAttribute Attr, SettingsCategoryDefinition Def)>();
29
30 foreach (var type in assembly.GetTypes())
31 {
32 if (!typeof(Control).IsAssignableFrom(type) || type.IsAbstract)
33 continue;
34
35 var attr = type.GetCustomAttribute<SettingsPanelAttribute>(inherit: false);
36 if (attr is null || attr.Hidden)
37 continue;
38
39 var panel = attr.PanelId.Trim();
40 var title = attr.Title.Trim();
41 if (string.IsNullOrEmpty(panel) || string.IsNullOrEmpty(title))
42 continue;
43
44 list.Add((attr, new SettingsCategoryDefinition
45 {
46 Id = panel,
47 Panel = panel,
48 Title = title,
49 Group = attr.Group.Trim(),
50 Order = attr.Order,
51 }));
52 }
53
54 return list
55 .OrderBy(x => x.Attr.Order)
56 .ThenBy(x => x.Def.Title, StringComparer.OrdinalIgnoreCase)
57 .Select(x => x.Def)
58 .ToList();
59 }
60
61 internal static IReadOnlyList<SettingsCategoryDefinition> Merge(
62 IReadOnlyList<SettingsCategoryDefinition> discovered,
63 IReadOnlyList<SettingsCategoryDefinition> overlay)
64 {
65 var byPanel = discovered.ToDictionary(c => c.Panel, StringComparer.OrdinalIgnoreCase);
66 var merged = new List<SettingsCategoryDefinition>();
67 var used = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
68
69 foreach (var row in overlay.OrderBy(c => c.Order).ThenBy(c => c.Title, StringComparer.OrdinalIgnoreCase))
70 {
71 if (row.Hidden)
72 {
73 used.Add(row.Panel);
74 continue;
75 }
76
77 if (byPanel.TryGetValue(row.Panel, out var baseDef))
78 {
79 merged.Add(ApplyOverlay(baseDef, row));
80 used.Add(row.Panel);
81 }
82 else
83 {
84 merged.Add(row);
85 used.Add(row.Panel);
86 }
87 }
88
89 foreach (var def in discovered)
90 {
91 if (!used.Contains(def.Panel))
92 merged.Add(def);
93 }
94
95 return merged.Count > 0 ? merged : discovered;
96 }
97
98 private static SettingsCategoryDefinition ApplyOverlay(
99 SettingsCategoryDefinition baseline,
100 SettingsCategoryDefinition overlay) =>
101 new()
102 {
103 Id = string.IsNullOrWhiteSpace(overlay.Id) ? baseline.Id : overlay.Id.Trim(),
104 Panel = overlay.Panel,
105 Title = string.IsNullOrWhiteSpace(overlay.Title) ? baseline.Title : overlay.Title.Trim(),
106 Group = string.IsNullOrWhiteSpace(overlay.Group) ? baseline.Group : overlay.Group.Trim(),
107 Order = overlay.Order != 0 ? overlay.Order : baseline.Order,
108 Hidden = overlay.Hidden,
109 };
110
111 private static bool TryLoadTomlOverlay(out IReadOnlyList<SettingsCategoryDefinition> categories)
112 {
113 categories = [];
114 if (!BundledAppContent.TryReadDiskThenEmbedded(SettingsCategoryCatalog.BundledRelativePath, out var text)
115 || string.IsNullOrWhiteSpace(text))
116 {
117 return false;
118 }
119
120 categories = SettingsCategoryCatalog.ParseTomlCategories(text);
121 return categories.Count > 0;
122 }
123}
124
View only · write via MCP/CIDE