Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
69f52d0
Initial plan
Copilot May 25, 2026
5d72b63
Add comprehensive CM→MEC replacement spec (specs/replace-cm-with-mec.md)
Copilot May 25, 2026
95495fa
Fix spec inaccuracies: verify all API references against actual codebase
tig May 25, 2026
112f020
Implement Phase 1: MEC foundation — POCOs, builder, and tests
tig May 25, 2026
607b87e
Add per-view Settings POCO files, remove ViewBorderSettings
tig May 25, 2026
15d58c3
Phase 2: Wire static [ConfigurationProperty] properties to Settings P…
tig May 25, 2026
5878e81
Phase 3: Add IThemeManager/ISchemeManager interfaces and MEC implemen…
tig May 25, 2026
b582e92
Phase 4: Remove [ConfigurationProperty] from POCO-backed properties, …
tig May 25, 2026
c761028
Phase 5: App developer migration — BindAppSettings, [Obsolete] AppSet…
tig May 25, 2026
86e20ff
Phase 6: Mark ConfigurationManager and CM machinery as [Obsolete]
tig May 25, 2026
5ebedbc
Restore [ConfigurationProperty] attributes removed in Phase 4
tig May 25, 2026
c42676e
Fix CI: restore [ConfigurationProperty] attrs and revert ScopeJsonCon…
tig May 25, 2026
1711f21
Fix AOT: add typed Dictionary paths and remove comparer guard in Deep…
tig May 25, 2026
2b97e31
Merge remote-tracking branch 'origin/develop' into copilot/replace-cm…
tig May 26, 2026
3c950dc
Update MEC spec for complex-type completion in #5411
tig May 26, 2026
83ded73
Fixes #4943 - Phase A1/B/C: IThemeManager.ThemeChanged, ThemeChanges …
tig May 26, 2026
25237b8
Merge remote-tracking branch 'origin/develop' into copilot/replace-cm…
tig May 26, 2026
45d2eab
Fixes #4943 - Phase C: Migrate ConfigurationManager.SerializerContext…
tig May 28, 2026
5829500
Fixes #4943 - Phase C followup: restore UTF-8 BOM on test files
tig May 28, 2026
1430124
Fixes #4943 - Phase C doc: spec reflects DeepCloner pragma removed + …
tig May 28, 2026
369cbd3
Merge remote-tracking branch 'origin/develop' into wip/merge-5411-dev…
tig Jun 7, 2026
a676efb
Document actual breaking changes of #5411 vs follow-on #5416
tig Jun 7, 2026
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
4 changes: 4 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
Expand Down
24 changes: 14 additions & 10 deletions Terminal.Gui/App/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
global using Terminal.Gui.ViewBase;
global using Terminal.Gui.Views;
global using Attribute = Terminal.Gui.Drawing.Attribute;
#pragma warning disable CS0618 // Obsolete - ConfigurationManager alias still used internally during transition
global using CM = Terminal.Gui.Configuration.ConfigurationManager;
#pragma warning restore CS0618
global using Color = Terminal.Gui.Drawing.Color;
using System.Globalization;
using System.Reflection;
using System.Resources;
using Terminal.Gui.Tracing;

#pragma warning disable CS0618 // Obsolete - Application still uses ConfigurationPropertyAttribute/SettingsScope during transition

namespace Terminal.Gui.App;

/// <summary>
Expand Down Expand Up @@ -238,14 +242,14 @@ internal static List<CultureInfo> GetSupportedCultures ()
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static AppModel AppModel
{
get;
get => ApplicationSettings.Defaults.AppModel;
set
{
AppModel oldValue = field;
field = value;
AppModelChanged?.Invoke (null, new ValueChangedEventArgs<AppModel> (oldValue, field));
AppModel oldValue = ApplicationSettings.Defaults.AppModel;
ApplicationSettings.Defaults.AppModel = value;
AppModelChanged?.Invoke (null, new ValueChangedEventArgs<AppModel> (oldValue, value));
}
} = AppModel.FullScreen;
}

/// <summary>
/// Gets or sets an override for the initial cursor position used in <see cref="AppModel.Inline"/> mode.
Expand All @@ -268,14 +272,14 @@ public static Point? ForceInlinePosition
[ConfigurationProperty (Scope = typeof (SettingsScope))]
public static string ForceDriver
{
get;
get => ApplicationSettings.Defaults.ForceDriver;
set
{
string oldValue = field;
field = value;
ForceDriverChanged?.Invoke (null, new ValueChangedEventArgs<string> (oldValue, field));
string oldValue = ApplicationSettings.Defaults.ForceDriver;
ApplicationSettings.Defaults.ForceDriver = value;
ForceDriverChanged?.Invoke (null, new ValueChangedEventArgs<string> (oldValue, value));
}
} = string.Empty;
}

/// <summary>
/// Gets or sets the default key bindings for Application-level commands, optionally varying by platform.
Expand Down
2 changes: 2 additions & 0 deletions Terminal.Gui/App/ApplicationImpl.Lifecycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Wcwidth;
using Trace = Terminal.Gui.Tracing.Trace;

#pragma warning disable CS0618 // Obsolete - ConfigurationManager still used internally during transition

namespace Terminal.Gui.App;

internal partial class ApplicationImpl
Expand Down
8 changes: 4 additions & 4 deletions Terminal.Gui/App/Legacy/Application.Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ public static partial class Application // Mouse handling
[Obsolete ("The legacy static Application object is going away.")]
public static bool IsMouseDisabled
{
get;
get => ApplicationSettings.Defaults.IsMouseDisabled;
set
{
bool oldValue = field;
field = value;
IsMouseDisabledChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, field));
bool oldValue = ApplicationSettings.Defaults.IsMouseDisabled;
ApplicationSettings.Defaults.IsMouseDisabled = value;
IsMouseDisabledChanged?.Invoke (null, new ValueChangedEventArgs<bool> (oldValue, value));
}
}

Expand Down
3 changes: 2 additions & 1 deletion Terminal.Gui/App/Tracing/Trace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ public static class Trace
[JsonConverter (typeof (TraceCategoryJsonConverter))]
public static TraceCategory EnabledCategories
{
get => _asyncLocalEnabledCategories.Value;
get => TraceSettings.Defaults.EnabledCategories;
set
{
TraceSettings.Defaults.EnabledCategories = value;
_asyncLocalEnabledCategories.Value = value;
EnsureBackendIfEnabled ();
}
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Configuration/AppSettingsScope.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - AppSettingsScope references ConfigurationPropertyAttribute in docs

namespace Terminal.Gui.Configuration;

/// <summary>The <see cref="Scope{T}"/> class for application-defined configuration settings.</summary>
Expand All @@ -23,6 +25,7 @@ namespace Terminal.Gui.Configuration;
/// },
/// </code>
/// </example>
[Obsolete ("Use Microsoft.Extensions.Configuration with TuiConfigurationBuilder instead. See TuiConfigurationBuilder docs for migration guide.")]
[JsonConverter (typeof (ScopeJsonConverter<AppSettingsScope>))]
public class AppSettingsScope : Scope<AppSettingsScope>
{ }
4 changes: 2 additions & 2 deletions Terminal.Gui/Configuration/AttributeJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ public override Attribute Read (ref Utf8JsonReader reader, Type typeToConvert, J
switch (propertyName?.ToLower ())
{
case "foreground":
foreground = JsonSerializer.Deserialize (ref reader, ConfigurationManager.SerializerContext.Color);
foreground = JsonSerializer.Deserialize (ref reader, TuiSerializerContext.Instance.Color);

break;
case "background":
background = JsonSerializer.Deserialize (ref reader, ConfigurationManager.SerializerContext.Color);
background = JsonSerializer.Deserialize (ref reader, TuiSerializerContext.Instance.Color);

break;
case "style":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ JsonSerializerOptions options
{
string key = reader.GetString ();
reader.Read ();
object value = JsonSerializer.Deserialize (ref reader, typeof (T), ConfigurationManager.SerializerContext);
object value = JsonSerializer.Deserialize (ref reader, typeof (T), TuiSerializerContext.Instance);

if (!dictionary.TryAdd (key, (T)value))
{
Expand All @@ -60,7 +60,7 @@ public override void Write (Utf8JsonWriter writer, ConcurrentDictionary<string,
writer.WriteStartObject ();

writer.WritePropertyName (item.Key);
JsonSerializer.Serialize (writer, item.Value, typeof (T), ConfigurationManager.SerializerContext);
JsonSerializer.Serialize (writer, item.Value, typeof (T), TuiSerializerContext.Instance);
writer.WriteEndObject ();
}

Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Configuration/ConfigProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Text.Json.Serialization;
using Terminal.Gui.Tracing;

#pragma warning disable CS0618 // Obsolete - ConfigProperty references ConfigurationManager/ConfigurationPropertyAttribute internally

namespace Terminal.Gui.Configuration;

/// <summary>
Expand All @@ -19,6 +21,7 @@ namespace Terminal.Gui.Configuration;
/// serialization, a <see cref="JsonConverter"/> must be provided using the <see cref="JsonConverterAttribute"/>
/// attribute.
/// </remarks>
[Obsolete ("Being replaced by Microsoft.Extensions.Configuration. Will be removed in a future version.")]
public class ConfigProperty
{
/// <summary>Describes the property.</summary>
Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using Terminal.Gui.Tracing;

#pragma warning disable CS0618 // Obsolete - ConfigPropertyHostTypes references ConfigurationPropertyAttribute during transition

namespace Terminal.Gui.Configuration;

/// <summary>
Expand All @@ -26,6 +28,7 @@ namespace Terminal.Gui.Configuration;
/// consumer apps.
/// </para>
/// </remarks>
[Obsolete ("Being replaced by Microsoft.Extensions.Configuration. Will be removed in a future version.")]
internal static class ConfigPropertyHostTypes
{
private const DynamicallyAccessedMemberTypes PRESERVED_MEMBERS = DynamicallyAccessedMemberTypes.PublicProperties;
Expand Down
26 changes: 4 additions & 22 deletions Terminal.Gui/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Text.Json.Serialization;
using Trace = Terminal.Gui.Tracing.Trace;

#pragma warning disable CS0618 // Obsolete - ConfigurationManager uses ConfigProperty/SettingsScope/ThemeScope internally

namespace Terminal.Gui.Configuration;

/// <summary>
Expand Down Expand Up @@ -46,6 +48,7 @@ namespace Terminal.Gui.Configuration;
/// See the UICatalog example for a complete example of how to use ConfigurationManager.
/// </para>
/// </summary>
[Obsolete ("ConfigurationManager is being replaced by Microsoft.Extensions.Configuration. Use TuiConfigurationBuilder instead. Will be removed in a future version.")]
public static class ConfigurationManager
{
private static readonly Lock _appNameLock = new ();
Expand Down Expand Up @@ -714,28 +717,7 @@ private static void OnApplied ()
// `Sources` - A source is a location where a configuration can be stored. Sources are defined in the `ConfigLocations` enum.

[SuppressMessage ("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
internal static readonly SourceGenerationContext SerializerContext = new (new JsonSerializerOptions
{
// Be relaxed
ReadCommentHandling = JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = true,
AllowTrailingCommas = true,
Converters =
{
// We override the standard Rune converter to support specifying Glyphs in
// a flexible way
new RuneJsonConverter (),

// Override Key to support "Ctrl+Q" format.
new KeyJsonConverter ()
},

// Enables Key to be "Ctrl+Q" vs "Ctrl\u002BQ"
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
TypeInfoResolver = SourceGenerationContext.Default
});
internal static readonly SourceGenerationContext SerializerContext = TuiSerializerContext.Instance;

private static SourcesManager? _sourcesManager = new ();
private static readonly object _sourcesManagerLock = new ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/// [ConfigurationProperty(Scope = typeof(AppSettingsScope))] public static string? MyProperty { get; set; } = "MyValue";
/// </example>
[AttributeUsage (AttributeTargets.Property)]
[Obsolete ("Use Settings POCOs with TuiConfigurationBuilder instead. Will be removed in a future version.")]
public class ConfigurationPropertyAttribute : System.Attribute
{
/// <summary>
Expand Down
45 changes: 29 additions & 16 deletions Terminal.Gui/Configuration/DeepCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private static object CreateInstance (Type type)
// In AOT, try using the JsonSerializer if available
if (IsAotEnvironment ())
{
JsonTypeInfo? jsonTypeInfo = ConfigurationManager.SerializerContext.GetTypeInfo (type);
JsonTypeInfo? jsonTypeInfo = TuiSerializerContext.Instance.GetTypeInfo (type);

if (jsonTypeInfo is not null)
{
Expand Down Expand Up @@ -390,30 +390,43 @@ private static IDictionary CreateDictionaryInstance (Type dictType, object? comp
return new Dictionary<string, Scheme?> ();
}

if (dictType == typeof (Dictionary<Command, PlatformKeyBinding>))
{
return new Dictionary<Command, PlatformKeyBinding> ();
}

if (dictType == typeof (Dictionary<string, Dictionary<Command, PlatformKeyBinding>>))
{
if (comparer is IEqualityComparer<string> stringComparer)
{
return new Dictionary<string, Dictionary<Command, PlatformKeyBinding>> (stringComparer);
}

return new Dictionary<string, Dictionary<Command, PlatformKeyBinding>> ();
}

// AOT-safe: use the source-generated JSON serializer to create empty dictionary instances.
// This avoids Activator.CreateInstance, whose target constructors are trimmed by the AOT linker
// for closed generic dictionary types not otherwise statically reachable.
// Only used when no comparer is needed — Deserialize("{}") always creates a default-comparer instance.
if (comparer is null)
// Always attempted regardless of comparer — in AOT, a default-comparer dictionary is preferable
// to a crash from a trimmed constructor.
try
{
try
JsonTypeInfo? jsonTypeInfo = TuiSerializerContext.Instance.GetTypeInfo (dictType);

if (jsonTypeInfo is not null)
{
JsonTypeInfo? jsonTypeInfo = ConfigurationManager.SerializerContext.GetTypeInfo (dictType);
IDictionary? result = JsonSerializer.Deserialize ("{}", jsonTypeInfo) as IDictionary;

if (jsonTypeInfo is not null)
if (result is not null)
{
IDictionary? result = JsonSerializer.Deserialize ("{}", jsonTypeInfo) as IDictionary;

if (result is not null)
{
return result;
}
return result;
}
}
catch (InvalidOperationException)
{
// JSON serializer context may not be initialized — fall through to reflective construction.
}
}
catch (InvalidOperationException)
{
// JSON serializer context may not be initialized — fall through to reflective construction.
}

try
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Configuration/DictionaryJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ JsonSerializerOptions options
{
string key = reader.GetString ();
reader.Read ();
object value = JsonSerializer.Deserialize (ref reader, typeof (T), ConfigurationManager.SerializerContext);
object value = JsonSerializer.Deserialize (ref reader, typeof (T), TuiSerializerContext.Instance);
dictionary.Add (key, (T)value);
}
}
Expand All @@ -56,7 +56,7 @@ public override void Write (Utf8JsonWriter writer, Dictionary<string, T> value,

//writer.WriteString (item.Key, item.Key);
writer.WritePropertyName (item.Key);
JsonSerializer.Serialize (writer, item.Value, typeof (T), ConfigurationManager.SerializerContext);
JsonSerializer.Serialize (writer, item.Value, typeof (T), TuiSerializerContext.Instance);
writer.WriteEndObject ();
}

Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Configuration/SchemeJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public override Scheme Read (ref Utf8JsonReader reader, Type typeToConvert, Json
reader.Read ();

// Make sure attributes are marked as explicitly set when deserialized
object? attrObj = JsonSerializer.Deserialize (ref reader, ConfigurationManager.SerializerContext.Attribute);
object? attrObj = JsonSerializer.Deserialize (ref reader, TuiSerializerContext.Instance.Attribute);

if (attrObj is not Attribute attribute)
{
Expand Down
2 changes: 2 additions & 0 deletions Terminal.Gui/Configuration/SchemeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - SchemeManager still uses ConfigurationManager/ConfigProperty/ThemeScope internally during transition

namespace Terminal.Gui.Configuration;

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions Terminal.Gui/Configuration/Scope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

#pragma warning disable CS0618 // Obsolete - Scope uses ConfigProperty/ConfigurationPropertyAttribute internally

namespace Terminal.Gui.Configuration;

/// <summary>
Expand Down
Loading
Loading