Forge
csharpdeeb25a2
1using CascadeIDE.Features.Chat;
2using CascadeIDE.Models;
3
4namespace CascadeIDE.Services;
5
6public static class SettingsService
7{
8 private static readonly ISettingsValidationSpecification[] ValidationSpecifications =
9 [
10 new DisplaySettingsValidationSpecification()
11 ];
12
13 /// <summary>
14 /// UTC mtime <c>settings.toml</c> на момент последнего успешного <see cref="Load"/> (или <see cref="DateTime.MinValue"/>, если файла не было).
15 /// Если перед <see cref="Save"/> файл новее — подмешиваем <c>[display.screens]</c> с диска, чтобы ручные правки не затирались.
16 /// </summary>
17 private static DateTime _settingsFileMtimeUtcAtLastLoad = DateTime.MinValue;
18
19 public static string GetSettingsDirectory() => UserSettingsPaths.GetSettingsDirectory();
20
21 public static string GetSettingsPath() => UserSettingsPaths.GetSettingsFilePath();
22
23 public static CascadeIdeSettings Load()
24 {
25 UserSettingsTomlFileAccess.TryRead(out var toml, out var mtime);
26 if (toml is null)
27 {
28 _settingsFileMtimeUtcAtLastLoad = mtime;
29 IntercomSendTrace.InvalidateSettingsCache();
30 return ValidateAndReturn(SettingsDefaultsLoader.DeserializeEffective(null));
31 }
32
33 try
34 {
35 var normalized = NormalizeFriendlySectionAliases(toml);
36 var settings = SettingsDefaultsLoader.DeserializeEffective(normalized);
37 _settingsFileMtimeUtcAtLastLoad = mtime;
38 IntercomSendTrace.InvalidateSettingsCache();
39 return ValidateAndReturn(settings);
40 }
41 catch
42 {
43 _settingsFileMtimeUtcAtLastLoad = mtime;
44 IntercomSendTrace.InvalidateSettingsCache();
45 return ValidateAndReturn(SettingsDefaultsLoader.DeserializeEffective(null));
46 }
47 }
48
49 public static void Save(CascadeIdeSettings settings)
50 {
51 try
52 {
53 var path = UserSettingsTomlFileAccess.GetFilePath();
54 if (UserSettingsTomlFileAccess.TryGetLastWriteTimeUtc(out var mtimeNow) && mtimeNow > _settingsFileMtimeUtcAtLastLoad)
55 {
56 try
57 {
58 var diskToml = TextFileReadWrite.TryReadAllTextIfExists(path);
59 if (diskToml is not null)
60 {
61 var normalizedDisk = NormalizeFriendlySectionAliases(diskToml);
62 var disk = CascadeTomlSerializer.Deserialize<CascadeIdeSettings>(normalizedDisk);
63 if (disk is not null)
64 ApplyPresentationFromDisk(settings, disk);
65 }
66 }
67 catch
68 {
69 // merge не обязателен для сохранения остальных полей
70 }
71 }
72
73 var toml = CascadeTomlSerializer.Serialize(settings);
74 UserSettingsTomlFileAccess.WriteAllText(toml, out var writtenMtime);
75 _settingsFileMtimeUtcAtLastLoad = writtenMtime;
76 IntercomSendTrace.InvalidateSettingsCache();
77 }
78 catch
79 {
80 // Игнорируем ошибки записи
81 }
82 }
83
84 /// <summary>Перезаписать <c>[display.screens]</c> в <paramref name="target"/> из <paramref name="disk"/> (клон полей).</summary>
85 internal static void ApplyPresentationFromDisk(CascadeIdeSettings target, CascadeIdeSettings disk)
86 {
87 var s = disk.Display.Screens;
88 target.Display.Screens.Topology = s.Topology;
89 target.Display.Screens.Grammar = new PresentationGrammarSettings
90 {
91 Brackets = s.Grammar.Brackets,
92 BetweenScreens = s.Grammar.BetweenScreens,
93 BetweenZones = s.Grammar.BetweenZones,
94 Pfd = s.Grammar.Pfd,
95 Forward = s.Grammar.Forward,
96 Mfd = s.Grammar.Mfd,
97 };
98 }
99
100 private static CascadeIdeSettings ValidateAndReturn(CascadeIdeSettings settings)
101 {
102 SettingsLegacyStringDefaults.Apply(settings);
103 foreach (var validationError in ValidationSpecifications.SelectMany(spec => spec.Validate(settings)))
104 global::System.Diagnostics.Debug.WriteLine($"Settings validation: {validationError}");
105 return settings;
106 }
107
108 private static string NormalizeFriendlySectionAliases(string toml)
109 {
110 if (string.IsNullOrWhiteSpace(toml))
111 return toml;
112
113 return toml
114 .Replace("[Editor.InlineHints]", "[editor.inline_hints]", StringComparison.Ordinal)
115 .Replace("[editor.InlineHints]", "[editor.inline_hints]", StringComparison.Ordinal)
116 .Replace("[Editor.inline_hints]", "[editor.inline_hints]", StringComparison.Ordinal);
117 }
118}
119
View only · write via MCP/CIDE