Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 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
da3a262
Merge origin/develop into copilot/replace-cm-with-mec
Copilot Jun 28, 2026
349db3c
Address PR #5411 CR feedback: flat-key MEC binding + theme-event leak
tig Jun 28, 2026
416c873
Fix obsolete ConfigurationManager usage in build-app.md snippet
tig Jun 28, 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 @@ -4,7 +4,9 @@

global using Attribute = Terminal.Gui.Drawing.Attribute;
global using Color = Terminal.Gui.Drawing.Color;
#pragma warning disable CS0618 // Obsolete - ConfigurationManager alias and ConfigurationPropertyAttribute still used internally during transition
global using CM = Terminal.Gui.Configuration.ConfigurationManager;
#pragma warning restore CS0618
global using Terminal.Gui.App;
global using Terminal.Gui.Testing;
global using Terminal.Gui.Time;
Expand All @@ -22,6 +24,8 @@
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 ();
}
Comment thread
tig marked this conversation as resolved.
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>
{ }
2 changes: 2 additions & 0 deletions Terminal.Gui/Configuration/AttributeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - JSON converter still uses ConfigurationManager.SerializerContext during transition

namespace Terminal.Gui.Configuration;

/// <summary>Json converter from the <see cref="Attribute"/> class.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - JSON converter still uses ConfigurationManager.SerializerContext during transition

namespace Terminal.Gui.Configuration;

internal class ConcurrentDictionaryJsonConverter<T> : JsonConverter<ConcurrentDictionary<string, T>>
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
3 changes: 3 additions & 0 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
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: 30 additions & 15 deletions Terminal.Gui/Configuration/DeepCloner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

#pragma warning disable CS0618 // Obsolete - DeepCloner still uses ConfigurationManager.SerializerContext during transition

namespace Terminal.Gui.Configuration;

/// <summary>
Expand Down Expand Up @@ -390,30 +392,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 = ConfigurationManager.SerializerContext.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
2 changes: 2 additions & 0 deletions Terminal.Gui/Configuration/DictionaryJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - JSON converter still uses ConfigurationManager.SerializerContext during transition

namespace Terminal.Gui.Configuration;

internal class DictionaryJsonConverter<T> : JsonConverter<Dictionary<string, T>>
Expand Down
2 changes: 2 additions & 0 deletions Terminal.Gui/Configuration/SchemeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable CS0618 // Obsolete - JSON converter still uses ConfigurationManager.SerializerContext during transition

namespace Terminal.Gui.Configuration;

// ReSharper disable StringLiteralTypo
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
4 changes: 2 additions & 2 deletions Terminal.Gui/Configuration/ScopeJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;

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

namespace Terminal.Gui.Configuration;

/// <summary>
Expand Down Expand Up @@ -140,8 +142,6 @@ public override TScopeT Read (ref Utf8JsonReader reader, Type typeToConvert, Jso
else
{
// Unknown property
// TODO: To support forward compatibility, we should just ignore unknown properties?
// TODO: Eg if we read an unknown property, it's possible that the property was added in a later version
throw new JsonException ($"{propertyName}: Unknown property name.");
}
}
Expand Down
23 changes: 23 additions & 0 deletions Terminal.Gui/Configuration/Settings/ApplicationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Terminal.Gui.Configuration;

/// <summary>
/// Settings POCO for application-level configuration (SettingsScope).
/// These correspond to the static properties on <see cref="Application"/> and related classes.
/// </summary>
public class ApplicationSettings
{
/// <summary>Gets or sets the application model (FullScreen or Inline).</summary>
public AppModel AppModel { get; set; } = AppModel.FullScreen;

/// <summary>Gets or sets the driver to force (e.g., "windows", "dotnet", "ansi"). Empty means auto-detect.</summary>
public string ForceDriver { get; set; } = string.Empty;

/// <summary>Gets or sets whether the mouse is disabled.</summary>
public bool IsMouseDisabled { get; set; }

/// <summary>
/// The static facade instance. Always contains the current effective values.
/// Updated by the MEC binding at <see cref="IApplication"/> initialization.
/// </summary>
public static ApplicationSettings Defaults { get; set; } = new ();
}
19 changes: 19 additions & 0 deletions Terminal.Gui/Configuration/Settings/ButtonSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Terminal.Gui.Configuration;

/// <summary>
/// Settings POCO for <see cref="Views.Button"/> visual defaults (ThemeScope).
/// </summary>
public class ButtonSettings
{
/// <summary>Gets or sets the default shadow style for buttons.</summary>
public ShadowStyles DefaultShadow { get; set; } = ShadowStyles.Opaque;

/// <summary>Gets or sets the default mouse highlight states for buttons.</summary>
public MouseState DefaultMouseHighlightStates { get; set; } = MouseState.In | MouseState.Pressed | MouseState.PressedOutside;

/// <summary>
/// The static facade instance. Always contains the current effective values.
/// Updated by the MEC binding at <see cref="IApplication"/> initialization.
/// </summary>
public static ButtonSettings Defaults { get; set; } = new ();
}
16 changes: 16 additions & 0 deletions Terminal.Gui/Configuration/Settings/CharMapSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Terminal.Gui.Configuration;

/// <summary>
/// Settings POCO for <see cref="Views.CharMap"/> defaults (ThemeScope).
/// </summary>
public class CharMapSettings
{
/// <summary>Gets or sets the default cursor style for character map views.</summary>
public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBlock;

/// <summary>
/// The static facade instance. Always contains the current effective values.
/// Updated by the MEC binding at <see cref="IApplication"/> initialization.
/// </summary>
public static CharMapSettings Defaults { get; set; } = new ();
}
Loading
Loading