Forge
csharpdeeb25a2
1using CascadeIDE.Contracts;
2using CascadeIDE.Models;
3using CascadeIDE.Services;
4
5namespace CascadeIDE.Features.Settings.DataAcquisition;
6
7/// <summary>
8/// DAL: merged-манифест из шипнутого <c>Settings/editor-languages.toml</c> и
9/// <c>%LocalAppData%\CascadeIDE\editor-languages.toml</c> (оверлей по <see cref="EditorLanguageEntry.Id"/>).
10/// </summary>
11[IoBoundary]
12public static class EditorLanguagesTomlLoader
13{
14 public const string BundledRelativePath = "Settings/editor-languages.toml";
15
16 /// <summary>Только тесты: подменить merged manifest без диска.</summary>
17 internal static EditorLanguagesManifest? ReplaceManifestForTests { get; set; }
18
19 /// <summary>Сброс кэша merged manifest (только тесты).</summary>
20 internal static void ClearCacheForTests() => ReplaceManifestForTests = null;
21
22 public static EditorLanguagesManifest LoadMergedManifest()
23 {
24 if (ReplaceManifestForTests is not null)
25 return CloneManifest(ReplaceManifestForTests);
26
27 var bundled = LoadManifestFromPath(
28 UserSettingsPaths.GetBundledEditorLanguagesFilePath(),
29 BundledRelativePath);
30 var user = LoadManifestFromPath(UserSettingsPaths.GetEditorLanguagesUserFilePath(), embeddedRelativeFallback: null);
31 return MergeManifests(bundled, user);
32 }
33
34 public static string GetEmbeddedBundledEditorLanguagesToml()
35 {
36 if (!BundledAppContent.TryReadDiskThenEmbedded(BundledRelativePath, out var text) || string.IsNullOrWhiteSpace(text))
37 throw new InvalidOperationException(
38 $"Missing bundled {BundledRelativePath} (disk under AppContext.BaseDirectory or embedded resource in CascadeIDE assembly).");
39 return text;
40 }
41
42 internal static EditorLanguagesManifest MergeManifests(EditorLanguagesManifest bundled, EditorLanguagesManifest user)
43 {
44 var languages = new Dictionary<string, EditorLanguageEntry>(StringComparer.OrdinalIgnoreCase);
45 foreach (var entry in bundled.Languages)
46 {
47 if (string.IsNullOrWhiteSpace(entry.Id))
48 continue;
49 languages[entry.Id.Trim()] = CloneLanguage(entry);
50 }
51
52 foreach (var entry in user.Languages)
53 {
54 if (string.IsNullOrWhiteSpace(entry.Id))
55 continue;
56 languages[entry.Id.Trim()] = CloneLanguage(entry);
57 }
58
59 var plainText = new List<EditorPlainTextEntry>();
60 foreach (var entry in bundled.PlainText)
61 plainText.Add(ClonePlainText(entry));
62 foreach (var entry in user.PlainText)
63 plainText.Add(ClonePlainText(entry));
64
65 return new EditorLanguagesManifest
66 {
67 SchemaVersion = user.SchemaVersion > 0 ? user.SchemaVersion : bundled.SchemaVersion,
68 Languages = languages.Values.OrderBy(static x => x.Id, StringComparer.OrdinalIgnoreCase).ToList(),
69 PlainText = plainText,
70 };
71 }
72
73 private static EditorLanguagesManifest LoadManifestFromPath(string path, string? embeddedRelativeFallback)
74 {
75 string? text = null;
76 if (File.Exists(path))
77 text = File.ReadAllText(path);
78 else if (embeddedRelativeFallback is not null && BundledAppContent.TryReadEmbeddedText(embeddedRelativeFallback, out var emb))
79 text = emb;
80
81 if (string.IsNullOrWhiteSpace(text))
82 return new EditorLanguagesManifest();
83
84 try
85 {
86 return CascadeTomlSerializer.Deserialize<EditorLanguagesManifest>(text.Trim()) ?? new EditorLanguagesManifest();
87 }
88 catch
89 {
90 return new EditorLanguagesManifest();
91 }
92 }
93
94 private static EditorLanguageEntry CloneLanguage(EditorLanguageEntry entry) =>
95 new()
96 {
97 Id = entry.Id.Trim(),
98 Display = entry.Display.Trim(),
99 Extensions = entry.Extensions
100 .Where(static x => !string.IsNullOrWhiteSpace(x))
101 .Select(static x => NormalizeExtension(x))
102 .Distinct(StringComparer.OrdinalIgnoreCase)
103 .ToList(),
104 Monaco = string.IsNullOrWhiteSpace(entry.Monaco) ? null : entry.Monaco.Trim(),
105 Attach = entry.Attach,
106 };
107
108 private static EditorPlainTextEntry ClonePlainText(EditorPlainTextEntry entry) =>
109 new()
110 {
111 Extensions = entry.Extensions
112 .Where(static x => !string.IsNullOrWhiteSpace(x))
113 .Select(static x => NormalizeExtension(x))
114 .Distinct(StringComparer.OrdinalIgnoreCase)
115 .ToList(),
116 Attach = entry.Attach,
117 };
118
119 private static EditorLanguagesManifest CloneManifest(EditorLanguagesManifest manifest) =>
120 new()
121 {
122 SchemaVersion = manifest.SchemaVersion,
123 Languages = manifest.Languages.Select(CloneLanguage).ToList(),
124 PlainText = manifest.PlainText.Select(ClonePlainText).ToList(),
125 };
126
127 internal static string NormalizeExtension(string raw)
128 {
129 var trimmed = raw.Trim();
130 if (trimmed.Length == 0)
131 return trimmed;
132 return trimmed.StartsWith('.') ? trimmed : "." + trimmed;
133 }
134}
135
View only · write via MCP/CIDE