diff --git a/examples/ted/EditorSettings.cs b/examples/ted/EditorSettings.cs
index 71b7a806..04d85a1f 100644
--- a/examples/ted/EditorSettings.cs
+++ b/examples/ted/EditorSettings.cs
@@ -1,54 +1,123 @@
+using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
+using Microsoft.Extensions.Configuration;
using Terminal.Gui.App;
using Terminal.Gui.Configuration;
namespace Ted;
+#pragma warning disable CS0618 // Keep legacy CM attributes until Terminal.Gui fully removes CM.
+
///
-/// ted's persisted editor settings. These are real Terminal.Gui configuration properties
-/// (, ), so
-/// is the single authority for reading them: enabling
-/// CM (see Program.cs) loads ~/.tui/ted.config.json and applies the values to these
-/// static properties. ted does no parsing of its own.
+/// ted's persisted editor settings. Microsoft.Extensions.Configuration is the primary read path:
+/// startup loads ~/.tui/ted.config.json and applies the values to these static properties
+/// before is constructed. Legacy CM attributes are retained only so older
+/// Terminal.Gui builds can still apply the previous format.
///
-/// is hand-rolled only because Terminal.Gui exposes no API for
-/// writing a user config file. It emits the exact shape CM reads: app-defined
-/// () properties live nested under a top-level
-/// "AppSettings" object, keyed DeclaringType.PropertyName. Other top-level keys
-/// a user may have added (e.g. "Theme") are preserved; JSONC comments are not.
-/// Any legacy flat root-level "EditorSettings.*" keys from the pre-CM format are
-/// dropped on save (migration).
+/// writes the MEC-native shape:
+/// "EditorSettings": { "WordWrap": true }. Other top-level keys a user may have added
+/// are preserved; JSONC comments are not. Legacy flat root-level
+/// "EditorSettings.*" keys and old CM "AppSettings" entries are dropped on save
+/// once migrated.
///
///
internal static class EditorSettings
{
+ internal const string SectionName = "EditorSettings";
+
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool LineNumbers { get; set; } = true;
+ public static bool LineNumbers
+ {
+ get => Defaults.LineNumbers;
+ set => Defaults.LineNumbers = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool FoldIndicators { get; set; } = true;
+ public static bool FoldIndicators
+ {
+ get => Defaults.FoldIndicators;
+ set => Defaults.FoldIndicators = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool WordWrap { get; set; }
+ public static bool WordWrap
+ {
+ get => Defaults.WordWrap;
+ set => Defaults.WordWrap = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool ShowTabs { get; set; }
+ public static bool ShowTabs
+ {
+ get => Defaults.ShowTabs;
+ set => Defaults.ShowTabs = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static int IndentSize { get; set; } = 4;
+ public static int IndentSize
+ {
+ get => Defaults.IndentSize;
+ set => Defaults.IndentSize = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool ConvertTabsToSpaces { get; set; } = true;
+ public static bool ConvertTabsToSpaces
+ {
+ get => Defaults.ConvertTabsToSpaces;
+ set => Defaults.ConvertTabsToSpaces = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool AutoIndent { get; set; } = true;
+ public static bool AutoIndent
+ {
+ get => Defaults.AutoIndent;
+ set => Defaults.AutoIndent = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool Scrollbars { get; set; } = true;
+ public static bool Scrollbars
+ {
+ get => Defaults.Scrollbars;
+ set => Defaults.Scrollbars = value;
+ }
[ConfigurationProperty (Scope = typeof (AppSettingsScope))]
- public static bool AutoComplete { get; set; }
+ public static bool AutoComplete
+ {
+ get => Defaults.AutoComplete;
+ set => Defaults.AutoComplete = value;
+ }
+
+ internal static EditorSettingsValues Defaults { get; set; } = new ();
+
+ internal static IConfiguration BuildConfiguration (string path)
+ {
+ return new ConfigurationBuilder ()
+ .AddJsonFile (path, true, false)
+ .Build ();
+ }
+
+ internal static void Load (string path)
+ {
+ Apply (BuildConfiguration (path));
+ }
+
+ internal static void Apply (IConfiguration configuration)
+ {
+ ArgumentNullException.ThrowIfNull (configuration);
+
+ EditorSettingsValues settings = new ();
+ ApplyLegacyDottedKeys (configuration, settings);
+ ApplyLegacyDottedKeys (configuration.GetSection ("AppSettings"), settings);
+ configuration.GetSection (SectionName).Bind (settings);
+ Defaults = settings;
+ }
+
+ internal static void ResetDefaults ()
+ {
+ Defaults = new EditorSettingsValues ();
+ }
internal static void Save (string path)
{
@@ -56,37 +125,30 @@ internal static void Save (string path)
{
JsonObject root = ReadRoot (path);
- // Migration: drop legacy flat root-level "EditorSettings.*" keys (the pre-CM
- // hand-rolled format). CM reads ted's settings only from the "AppSettings" object.
- foreach (var legacyKey in root
- .Where (kvp => kvp.Key.StartsWith ("EditorSettings.", StringComparison.Ordinal))
- .Select (kvp => kvp.Key)
- .ToList ())
- {
- root.Remove (legacyKey);
- }
-
- JsonObject appSettings;
+ RemoveLegacyDottedKeys (root);
- if (root["AppSettings"] is JsonObject existing)
+ if (root["AppSettings"] is JsonObject appSettings)
{
- appSettings = existing;
- }
- else
- {
- appSettings = new JsonObject ();
- root["AppSettings"] = appSettings;
+ RemoveLegacyDottedKeys (appSettings);
+
+ if (appSettings.Count == 0)
+ {
+ root.Remove ("AppSettings");
+ }
}
- appSettings["EditorSettings.LineNumbers"] = LineNumbers;
- appSettings["EditorSettings.FoldIndicators"] = FoldIndicators;
- appSettings["EditorSettings.WordWrap"] = WordWrap;
- appSettings["EditorSettings.ShowTabs"] = ShowTabs;
- appSettings["EditorSettings.IndentSize"] = IndentSize;
- appSettings["EditorSettings.ConvertTabsToSpaces"] = ConvertTabsToSpaces;
- appSettings["EditorSettings.AutoIndent"] = AutoIndent;
- appSettings["EditorSettings.AutoComplete"] = AutoComplete;
- appSettings["EditorSettings.Scrollbars"] = Scrollbars;
+ root[SectionName] = new JsonObject
+ {
+ [nameof (LineNumbers)] = LineNumbers,
+ [nameof (FoldIndicators)] = FoldIndicators,
+ [nameof (WordWrap)] = WordWrap,
+ [nameof (ShowTabs)] = ShowTabs,
+ [nameof (IndentSize)] = IndentSize,
+ [nameof (ConvertTabsToSpaces)] = ConvertTabsToSpaces,
+ [nameof (AutoIndent)] = AutoIndent,
+ [nameof (AutoComplete)] = AutoComplete,
+ [nameof (Scrollbars)] = Scrollbars
+ };
var directory = Path.GetDirectoryName (path);
@@ -137,4 +199,89 @@ private static JsonObject ReadRoot (string path)
return node as JsonObject ?? new JsonObject ();
}
+
+ private static void ApplyLegacyDottedKeys (IConfiguration configuration, EditorSettingsValues settings)
+ {
+ ApplyLegacyBoolean (configuration, nameof (LineNumbers), value => settings.LineNumbers = value);
+ ApplyLegacyBoolean (configuration, nameof (FoldIndicators), value => settings.FoldIndicators = value);
+ ApplyLegacyBoolean (configuration, nameof (WordWrap), value => settings.WordWrap = value);
+ ApplyLegacyBoolean (configuration, nameof (ShowTabs), value => settings.ShowTabs = value);
+ ApplyLegacyInt32 (configuration, nameof (IndentSize), value => settings.IndentSize = value);
+ ApplyLegacyBoolean (configuration, nameof (ConvertTabsToSpaces), value => settings.ConvertTabsToSpaces = value);
+ ApplyLegacyBoolean (configuration, nameof (AutoIndent), value => settings.AutoIndent = value);
+ ApplyLegacyBoolean (configuration, nameof (Scrollbars), value => settings.Scrollbars = value);
+ ApplyLegacyBoolean (configuration, nameof (AutoComplete), value => settings.AutoComplete = value);
+ }
+
+ private static void ApplyLegacyBoolean (IConfiguration configuration, string propertyName, Action apply)
+ {
+ var value = configuration[$"{SectionName}.{propertyName}"];
+
+ if (value is null)
+ {
+ return;
+ }
+
+ if (bool.TryParse (value, out var parsed))
+ {
+ apply (parsed);
+
+ return;
+ }
+
+ Logging.Error ($"EditorSettings.Load: invalid legacy {SectionName}.{propertyName} value '{value}'.");
+ }
+
+ private static void ApplyLegacyInt32 (IConfiguration configuration, string propertyName, Action apply)
+ {
+ var value = configuration[$"{SectionName}.{propertyName}"];
+
+ if (value is null)
+ {
+ return;
+ }
+
+ if (int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed))
+ {
+ apply (parsed);
+
+ return;
+ }
+
+ Logging.Error ($"EditorSettings.Load: invalid legacy {SectionName}.{propertyName} value '{value}'.");
+ }
+
+ private static void RemoveLegacyDottedKeys (JsonObject json)
+ {
+ foreach (var legacyKey in json
+ .Where (kvp => kvp.Key.StartsWith ($"{SectionName}.", StringComparison.Ordinal))
+ .Select (kvp => kvp.Key)
+ .ToList ())
+ {
+ json.Remove (legacyKey);
+ }
+ }
+
+ internal sealed class EditorSettingsValues
+ {
+ public bool LineNumbers { get; set; } = true;
+
+ public bool FoldIndicators { get; set; } = true;
+
+ public bool WordWrap { get; set; }
+
+ public bool ShowTabs { get; set; }
+
+ public int IndentSize { get; set; } = 4;
+
+ public bool ConvertTabsToSpaces { get; set; } = true;
+
+ public bool AutoIndent { get; set; } = true;
+
+ public bool Scrollbars { get; set; } = true;
+
+ public bool AutoComplete { get; set; }
+ }
}
+
+#pragma warning restore CS0618
diff --git a/examples/ted/Program.cs b/examples/ted/Program.cs
index c7130b0c..124ea959 100644
--- a/examples/ted/Program.cs
+++ b/examples/ted/Program.cs
@@ -2,17 +2,14 @@
using Ted;
using Terminal.Gui.App;
-using Terminal.Gui.Configuration;
// ReSharper disable AccessToDisposedClosure
Hosting.ConfigureLogging ();
Hosting.EnableTracing ();
-// ConfigurationManager is the single authority for reading settings: Enable (All) loads
-// ~/.tui/ted.config.json (ConfigLocations.AppHome) and applies the values to the
-// EditorSettings [ConfigurationProperty] statics before TedApp is constructed.
-ConfigurationManager.Enable (ConfigLocations.All);
+// Prefer Terminal.Gui's MEC builder when present; fall back to CM for released Terminal.Gui.
+TerminalGuiConfigurationBootstrap.Apply ();
using IApplication app = Application.Create ();
diff --git a/examples/ted/TerminalGuiConfigurationBootstrap.cs b/examples/ted/TerminalGuiConfigurationBootstrap.cs
new file mode 100644
index 00000000..235de621
--- /dev/null
+++ b/examples/ted/TerminalGuiConfigurationBootstrap.cs
@@ -0,0 +1,15 @@
+using Terminal.Gui.Configuration;
+using Terminal.Gui.Editor.Configuration;
+
+namespace Ted;
+
+internal static class TerminalGuiConfigurationBootstrap
+{
+ internal static void Apply ()
+ {
+ TuiConfigurationBuilder builder = new ("ted");
+ builder.ApplyToStaticFacades ();
+ EditorConfiguration.Apply (builder.Configuration);
+ EditorSettings.Apply (builder.Configuration);
+ }
+}
diff --git a/examples/ted/ted.csproj b/examples/ted/ted.csproj
index 06d5c66b..4016bb0e 100644
--- a/examples/ted/ted.csproj
+++ b/examples/ted/ted.csproj
@@ -26,6 +26,8 @@
+
+
diff --git a/src/Terminal.Gui.Editor/Configuration/EditorConfiguration.cs b/src/Terminal.Gui.Editor/Configuration/EditorConfiguration.cs
new file mode 100644
index 00000000..9501c543
--- /dev/null
+++ b/src/Terminal.Gui.Editor/Configuration/EditorConfiguration.cs
@@ -0,0 +1,93 @@
+using Microsoft.Extensions.Configuration;
+using Terminal.Gui.Input;
+
+namespace Terminal.Gui.Editor.Configuration;
+
+///
+/// Applies Microsoft.Extensions.Configuration values to .
+///
+public static class EditorConfiguration
+{
+ private const string LegacyDefaultKeyBindingsSectionName = "Terminal.Gui.Editor.Editor.DefaultKeyBindings";
+
+ /// The default configuration section for settings.
+ public const string SectionName = "Editor";
+
+ ///
+ /// Applies editor settings from to
+ /// .
+ ///
+ /// The configuration root to read.
+ /// The section containing editor settings.
+ public static void Apply (IConfiguration configuration, string sectionName = SectionName)
+ {
+ ArgumentNullException.ThrowIfNull (configuration);
+ ArgumentException.ThrowIfNullOrWhiteSpace (sectionName);
+
+ EditorSettings settings = new ();
+ ApplyDefaultKeyBindings (configuration.GetSection (LegacyDefaultKeyBindingsSectionName), settings);
+ ApplyDefaultKeyBindings (
+ configuration.GetSection (sectionName).GetSection (nameof (EditorSettings.DefaultKeyBindings)), settings);
+ EditorSettings.Defaults = settings;
+ }
+
+ private static void ApplyDefaultKeyBindings (IConfigurationSection section, EditorSettings settings)
+ {
+ if (!section.Exists ())
+ {
+ return;
+ }
+
+ settings.DefaultKeyBindings ??= [];
+
+ foreach (IConfigurationSection commandSection in section.GetChildren ())
+ {
+ if (!Enum.TryParse (commandSection.Key, true, out Command command))
+ {
+ throw new FormatException ($"Unknown editor command in configuration: '{commandSection.Key}'.");
+ }
+
+ settings.DefaultKeyBindings[command] = ReadPlatformKeyBinding (commandSection);
+ }
+ }
+
+ private static PlatformKeyBinding ReadPlatformKeyBinding (IConfigurationSection section)
+ {
+ return new PlatformKeyBinding
+ {
+ All = ReadKeys (section.GetSection (nameof (PlatformKeyBinding.All))),
+ Windows = ReadKeys (section.GetSection (nameof (PlatformKeyBinding.Windows))),
+ Linux = ReadKeys (section.GetSection (nameof (PlatformKeyBinding.Linux))),
+ Macos = ReadKeys (section.GetSection (nameof (PlatformKeyBinding.Macos)))
+ };
+ }
+
+ private static Key[]? ReadKeys (IConfigurationSection section)
+ {
+ if (!section.Exists ())
+ {
+ return null;
+ }
+
+ List keys = [];
+
+ foreach (IConfigurationSection keySection in section.GetChildren ())
+ {
+ var keyText = keySection.Value;
+
+ if (string.IsNullOrWhiteSpace (keyText))
+ {
+ throw new FormatException ($"Empty key in configuration section '{section.Path}'.");
+ }
+
+ if (!Key.TryParse (keyText, out Key key))
+ {
+ throw new FormatException ($"Invalid key '{keyText}' in configuration section '{section.Path}'.");
+ }
+
+ keys.Add (key);
+ }
+
+ return keys.Count == 0 ? null : keys.ToArray ();
+ }
+}
diff --git a/src/Terminal.Gui.Editor/Configuration/EditorKeyBindingDefaults.cs b/src/Terminal.Gui.Editor/Configuration/EditorKeyBindingDefaults.cs
new file mode 100644
index 00000000..91cbbaf4
--- /dev/null
+++ b/src/Terminal.Gui.Editor/Configuration/EditorKeyBindingDefaults.cs
@@ -0,0 +1,44 @@
+using Terminal.Gui.Input;
+
+namespace Terminal.Gui.Editor.Configuration;
+
+internal static class EditorKeyBindingDefaults
+{
+ internal static Dictionary Create ()
+ {
+ return new Dictionary
+ {
+ [Command.Start] = Bind.All (Key.Home.WithCtrl),
+ [Command.End] = Bind.All (Key.End.WithCtrl),
+ [Command.NewLine] = Bind.All (Key.Enter),
+ [Command.DeleteCharLeft] = Bind.All (Key.Backspace),
+ [Command.DeleteCharRight] = Bind.All (Key.Delete),
+ [Command.Undo] = Bind.All (Key.Z.WithCtrl),
+ [Command.Redo] = Bind.All (Key.Y.WithCtrl, Key.Z.WithCtrl.WithShift),
+ [Command.Cut] = Bind.All (Key.X.WithCtrl),
+ [Command.Copy] = Bind.All (Key.C.WithCtrl),
+ [Command.Paste] = Bind.All (Key.V.WithCtrl),
+ [Command.Collapse] = Bind.All (Key.M.WithCtrl),
+ [Command.InsertTab] = Bind.All (Key.Tab),
+ [Command.Unindent] = Bind.All (Key.Tab.WithShift),
+ [Command.FindNext] = Bind.All (Key.F3),
+ [Command.FindPrevious] = Bind.All (Key.F3.WithShift),
+ [Command.Find] = Bind.All (Key.F.WithCtrl),
+ [Command.Replace] = Bind.All (Key.H.WithCtrl),
+
+ // Vertical multi-caret — VS Code parity (Ctrl+Alt+Up/Down). A PlatformKeyBinding, so a
+ // user whose terminal/WM grabs the chord overrides it via Editor:DefaultKeyBindings config;
+ // no editor-specific fallback chord. macOS uses the same chord pending real-terminal
+ // validation (specs/decisions.md DEC-006).
+ [Command.InsertCaretAbove] = Bind.All (Key.CursorUp.WithCtrl.WithAlt),
+ [Command.InsertCaretBelow] = Bind.All (Key.CursorDown.WithCtrl.WithAlt),
+ [Command.WordLeft] = Bind.All (Key.CursorLeft.WithCtrl),
+ [Command.WordRight] = Bind.All (Key.CursorRight.WithCtrl),
+ [Command.WordLeftExtend] = Bind.All (Key.CursorLeft.WithCtrl.WithShift),
+ [Command.WordRightExtend] = Bind.All (Key.CursorRight.WithCtrl.WithShift),
+ [Command.KillWordLeft] = Bind.All (Key.Backspace.WithCtrl),
+ [Command.KillWordRight] = Bind.All (Key.Delete.WithCtrl),
+ [Command.ToggleOverwrite] = Bind.All (Key.InsertChar)
+ };
+ }
+}
diff --git a/src/Terminal.Gui.Editor/Configuration/EditorSettings.cs b/src/Terminal.Gui.Editor/Configuration/EditorSettings.cs
new file mode 100644
index 00000000..6c088170
--- /dev/null
+++ b/src/Terminal.Gui.Editor/Configuration/EditorSettings.cs
@@ -0,0 +1,22 @@
+using Terminal.Gui.Input;
+
+namespace Terminal.Gui.Editor.Configuration;
+
+///
+/// MEC-friendly settings POCO for defaults.
+///
+public class EditorSettings
+{
+ ///
+ /// Gets or sets editor-specific default key bindings layered on top of
+ /// .
+ ///
+ public Dictionary? DefaultKeyBindings { get; set; } =
+ EditorKeyBindingDefaults.Create ();
+
+ ///
+ /// The static facade instance. Applications update this from Microsoft.Extensions.Configuration
+ /// before constructing instances.
+ ///
+ public static EditorSettings Defaults { get; set; } = new ();
+}
diff --git a/src/Terminal.Gui.Editor/Editor.Commands.cs b/src/Terminal.Gui.Editor/Editor.Commands.cs
index 03f02078..91f2b9df 100644
--- a/src/Terminal.Gui.Editor/Editor.Commands.cs
+++ b/src/Terminal.Gui.Editor/Editor.Commands.cs
@@ -1,6 +1,6 @@
using System.Globalization;
using Terminal.Gui.App;
-using Terminal.Gui.Configuration;
+using Terminal.Gui.Editor.Configuration;
using Terminal.Gui.Editor.Document;
using Terminal.Gui.Editor.Document.Folding;
using Terminal.Gui.Input;
@@ -21,41 +21,11 @@ public partial class Editor
/// Process-wide static. Do not mutate from parallel tests — see Terminal.Gui's same convention
/// on .
///
- [ConfigurationProperty (Scope = typeof (SettingsScope))]
- public new static Dictionary? DefaultKeyBindings { get; set; } = new ()
+ public new static Dictionary? DefaultKeyBindings
{
- [Command.Start] = Bind.All (Key.Home.WithCtrl),
- [Command.End] = Bind.All (Key.End.WithCtrl),
- [Command.NewLine] = Bind.All (Key.Enter),
- [Command.DeleteCharLeft] = Bind.All (Key.Backspace),
- [Command.DeleteCharRight] = Bind.All (Key.Delete),
- [Command.Undo] = Bind.All (Key.Z.WithCtrl),
- [Command.Redo] = Bind.All (Key.Y.WithCtrl, Key.Z.WithCtrl.WithShift),
- [Command.Cut] = Bind.All (Key.X.WithCtrl),
- [Command.Copy] = Bind.All (Key.C.WithCtrl),
- [Command.Paste] = Bind.All (Key.V.WithCtrl),
- [Command.Collapse] = Bind.All (Key.M.WithCtrl),
- [Command.InsertTab] = Bind.All (Key.Tab),
- [Command.Unindent] = Bind.All (Key.Tab.WithShift),
- [Command.FindNext] = Bind.All (Key.F3),
- [Command.FindPrevious] = Bind.All (Key.F3.WithShift),
- [Command.Find] = Bind.All (Key.F.WithCtrl),
- [Command.Replace] = Bind.All (Key.H.WithCtrl),
-
- // Vertical multi-caret — VS Code parity (Ctrl+Alt+Up/Down). A PlatformKeyBinding, so a
- // user whose terminal/WM grabs the chord overrides it via View.ViewKeyBindings config;
- // no editor-specific fallback chord. macOS uses the same chord pending real-terminal
- // validation (specs/decisions.md DEC-006).
- [Command.InsertCaretAbove] = Bind.All (Key.CursorUp.WithCtrl.WithAlt),
- [Command.InsertCaretBelow] = Bind.All (Key.CursorDown.WithCtrl.WithAlt),
- [Command.WordLeft] = Bind.All (Key.CursorLeft.WithCtrl),
- [Command.WordRight] = Bind.All (Key.CursorRight.WithCtrl),
- [Command.WordLeftExtend] = Bind.All (Key.CursorLeft.WithCtrl.WithShift),
- [Command.WordRightExtend] = Bind.All (Key.CursorRight.WithCtrl.WithShift),
- [Command.KillWordLeft] = Bind.All (Key.Backspace.WithCtrl),
- [Command.KillWordRight] = Bind.All (Key.Delete.WithCtrl),
- [Command.ToggleOverwrite] = Bind.All (Key.InsertChar)
- };
+ get => EditorSettings.Defaults.DefaultKeyBindings;
+ set => EditorSettings.Defaults.DefaultKeyBindings = value;
+ }
private void CreateCommandsAndBindings ()
{
diff --git a/src/Terminal.Gui.Editor/Terminal.Gui.Editor.csproj b/src/Terminal.Gui.Editor/Terminal.Gui.Editor.csproj
index e69e06f1..4b3ad1e3 100644
--- a/src/Terminal.Gui.Editor/Terminal.Gui.Editor.csproj
+++ b/src/Terminal.Gui.Editor/Terminal.Gui.Editor.csproj
@@ -16,6 +16,7 @@
+
diff --git a/tests/Terminal.Gui.Editor.ConfigTests/TedConfigurationManagerTests.cs b/tests/Terminal.Gui.Editor.ConfigTests/TedConfigurationManagerTests.cs
index a6cda47e..7708f214 100644
--- a/tests/Terminal.Gui.Editor.ConfigTests/TedConfigurationManagerTests.cs
+++ b/tests/Terminal.Gui.Editor.ConfigTests/TedConfigurationManagerTests.cs
@@ -3,6 +3,8 @@
using Ted;
using Terminal.Gui.Configuration;
using Xunit;
+
+#pragma warning disable CS0618 // This project intentionally quarantines legacy ConfigurationManager coverage.
using static Terminal.Gui.Configuration.ConfigurationManager;
namespace Terminal.Gui.Editor.ConfigTests;
@@ -67,15 +69,9 @@ public void ConfigurationManager_Applies_AppSettings_To_TedApp ()
Disable (true);
// Restore declared defaults so a later CM test in this assembly starts clean.
- EditorSettings.LineNumbers = true;
- EditorSettings.FoldIndicators = true;
- EditorSettings.WordWrap = false;
- EditorSettings.ShowTabs = false;
- EditorSettings.IndentSize = 4;
- EditorSettings.ConvertTabsToSpaces = true;
- EditorSettings.AutoIndent = true;
- EditorSettings.Scrollbars = true;
- EditorSettings.AutoComplete = false;
+ EditorSettings.ResetDefaults ();
}
}
+
+#pragma warning restore CS0618
}
diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/EditorKeyBindingIntegrationTests.cs b/tests/Terminal.Gui.Editor.IntegrationTests/EditorKeyBindingIntegrationTests.cs
index 910ae9eb..2383ef74 100644
--- a/tests/Terminal.Gui.Editor.IntegrationTests/EditorKeyBindingIntegrationTests.cs
+++ b/tests/Terminal.Gui.Editor.IntegrationTests/EditorKeyBindingIntegrationTests.cs
@@ -1,18 +1,19 @@
// CoPilot - claude-sonnet-4-5
-using Terminal.Gui.Configuration;
+using Microsoft.Extensions.Configuration;
+using Terminal.Gui.Editor.Configuration;
using Terminal.Gui.Editor.IntegrationTests.Testing;
using Terminal.Gui.Input;
using Terminal.Gui.Testing;
-using Terminal.Gui.ViewBase;
using Xunit;
+using MecEditorSettings = Terminal.Gui.Editor.Configuration.EditorSettings;
namespace Terminal.Gui.Editor.IntegrationTests;
///
/// Serialisation guard: mutates
-/// and , both
-/// process-wide statics that Terminal.Gui reads during view construction. Using
+/// , a process-wide static that Terminal.Gui reads during view construction.
+/// Using
/// DisableParallelization = true serialises this collection against every other collection.
///
[CollectionDefinition (nameof (KeyBindingIntegrationCollection), DisableParallelization = true)]
@@ -31,8 +32,8 @@ public sealed class KeyBindingIntegrationCollection;
///
/// -
///
-/// Loading a JSON profile via
-/// using the "View.ViewKeyBindings" key.
+/// Loading a Microsoft.Extensions.Configuration profile using the
+/// "Editor:DefaultKeyBindings" section.
///
///
///
@@ -78,15 +79,15 @@ public async Task Override_DefaultKeyBindings_CustomUndoKey_ActuallyUndoes ()
}
///
- /// Proves that loading a JSON profile via
- /// with the "View.ViewKeyBindings" key causes the configured custom key to trigger
+ /// Proves that loading a Microsoft.Extensions.Configuration profile with the
+ /// "Editor:DefaultKeyBindings" section causes the configured custom key to trigger
/// the correct command in a live .
///
/// The test JSON mirrors the structure from the issue:
///
/// {
- /// "View.ViewKeyBindings": {
- /// "Editor": {
+ /// "Editor": {
+ /// "DefaultKeyBindings": {
/// "Undo": { "All": ["Ctrl+U"] }
/// }
/// }
@@ -95,26 +96,20 @@ public async Task Override_DefaultKeyBindings_CustomUndoKey_ActuallyUndoes ()
///
///
[Fact]
- public async Task ConfigurationManager_RuntimeConfig_CustomUndoKey_ActuallyUndoes ()
+ public async Task MecConfiguration_CustomUndoKey_ActuallyUndoes ()
{
- Dictionary>? originalViewKeyBindings = View.ViewKeyBindings;
- var originalRuntimeConfig = ConfigurationManager.RuntimeConfig;
- var wasEnabled = ConfigurationManager.IsEnabled;
+ MecEditorSettings original = MecEditorSettings.Defaults;
try
{
- const string json = """
- {
- "View.ViewKeyBindings": {
- "Editor": {
- "Undo": { "All": ["Ctrl+U"] }
- }
- }
- }
- """;
-
- ConfigurationManager.RuntimeConfig = json;
- ConfigurationManager.Enable (ConfigLocations.Runtime);
+ IConfiguration configuration = new ConfigurationBuilder ()
+ .AddInMemoryCollection (new Dictionary
+ {
+ ["Editor:DefaultKeyBindings:Undo:All:0"] = "Ctrl+U"
+ })
+ .Build ();
+
+ EditorConfiguration.Apply (configuration);
await using AppFixture fx = new (() => new EditorTestHost ("abc"));
fx.Top.Editor.SetFocus ();
@@ -129,13 +124,7 @@ public async Task ConfigurationManager_RuntimeConfig_CustomUndoKey_ActuallyUndoe
}
finally
{
- View.ViewKeyBindings = originalViewKeyBindings;
- ConfigurationManager.RuntimeConfig = originalRuntimeConfig;
-
- if (!wasEnabled)
- {
- ConfigurationManager.Disable (true);
- }
+ MecEditorSettings.Defaults = original;
}
}
}
diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/TedSettingsPersistenceTests.cs b/tests/Terminal.Gui.Editor.IntegrationTests/TedSettingsPersistenceTests.cs
index 7e2ee0e4..f8146ad6 100644
--- a/tests/Terminal.Gui.Editor.IntegrationTests/TedSettingsPersistenceTests.cs
+++ b/tests/Terminal.Gui.Editor.IntegrationTests/TedSettingsPersistenceTests.cs
@@ -28,7 +28,8 @@ public void SaveViewSettings_Creates_ConfigFile_And_Persists_IndentSize ()
InvokeSaveViewSettings (app);
Assert.True (File.Exists (scope.ConfigPath));
- Assert.Contains ("\"EditorSettings.IndentSize\": 7", File.ReadAllText (scope.ConfigPath));
+ JsonObject editorSettings = ReadEditorSettingsSection (scope.ConfigPath);
+ Assert.Equal (7, editorSettings[nameof (EditorSettings.IndentSize)]!.GetValue ());
}
[Fact]
@@ -43,9 +44,8 @@ public void SaveViewSettings_Updates_Existing_IndentSize_Value ()
app.Editor.IndentationSize = 8;
InvokeSaveViewSettings (app);
- var text = File.ReadAllText (scope.ConfigPath);
- Assert.Contains ("\"EditorSettings.IndentSize\": 8", text);
- Assert.DoesNotContain ("\"EditorSettings.IndentSize\": 2", text);
+ JsonObject editorSettings = ReadEditorSettingsSection (scope.ConfigPath);
+ Assert.Equal (8, editorSettings[nameof (EditorSettings.IndentSize)]!.GetValue ());
}
[Fact]
@@ -57,19 +57,93 @@ public void SaveViewSettings_Persists_WordWrap_Changes ()
InvokeSaveViewSettings (app);
Assert.True (File.Exists (scope.ConfigPath));
- Assert.Contains ("\"EditorSettings.WordWrap\": true", File.ReadAllText (scope.ConfigPath));
+ JsonObject editorSettings = ReadEditorSettingsSection (scope.ConfigPath);
+ Assert.True (editorSettings[nameof (EditorSettings.WordWrap)]!.GetValue ());
}
- // NOTE: There is deliberately no "ConfigurationManager applies ted.config.json to the Editor"
- // end-to-end test here. That exercises Terminal.Gui's ConfigurationManager (CM) — not ted code
- // — and CM's [ConfigurationProperty] discovery + load/apply is process-global. In this shared
- // multi-test host the discovery runs (triggered by some earlier test's Application) before the
- // `ted` assembly's EditorSettings type is registered, so a CM round-trip here is inherently
- // order-dependent/flaky (CLAUDE.md: "don't test the framework"; isolate global state). ted's
- // own contract — that Save() writes the exact shape CM requires (nested under "AppSettings",
- // AppSettingsScope) — is covered deterministically by
- // SaveViewSettings_Preserves_Other_TopLevel_Keys_And_Nests_Under_AppSettings. CM's load/apply
- // of AppSettingsScope is covered by Terminal.Gui's own ConfigurationManager tests.
+ [Fact]
+ public void MecSettings_Applies_To_TedApp ()
+ {
+ using ConfigPathScope scope = new ();
+ var configDirectory = Path.GetDirectoryName (scope.ConfigPath);
+ Assert.NotNull (configDirectory);
+ Directory.CreateDirectory (configDirectory);
+ File.WriteAllText (
+ scope.ConfigPath,
+ """
+ {
+ "EditorSettings": {
+ "WordWrap": true,
+ "ShowTabs": true,
+ "LineNumbers": false,
+ "IndentSize": 2
+ }
+ }
+ """);
+
+ EditorSettings.Load (scope.ConfigPath);
+ TedApp app = new ();
+
+ Assert.True (app.Editor.WordWrap);
+ Assert.True (app.Editor.ShowTabs);
+ Assert.False (app.Editor.GutterOptions.HasFlag (GutterOptions.LineNumbers));
+ Assert.Equal (2, app.Editor.IndentationSize);
+ }
+
+ [Fact]
+ public void LegacyCmSettings_Applies_To_TedApp ()
+ {
+ using ConfigPathScope scope = new ();
+ var configDirectory = Path.GetDirectoryName (scope.ConfigPath);
+ Assert.NotNull (configDirectory);
+ Directory.CreateDirectory (configDirectory);
+ File.WriteAllText (
+ scope.ConfigPath,
+ """
+ {
+ "AppSettings": {
+ "EditorSettings.WordWrap": true,
+ "EditorSettings.ShowTabs": true,
+ "EditorSettings.LineNumbers": false,
+ "EditorSettings.IndentSize": 2
+ }
+ }
+ """);
+
+ EditorSettings.Load (scope.ConfigPath);
+ TedApp app = new ();
+
+ Assert.True (app.Editor.WordWrap);
+ Assert.True (app.Editor.ShowTabs);
+ Assert.False (app.Editor.GutterOptions.HasFlag (GutterOptions.LineNumbers));
+ Assert.Equal (2, app.Editor.IndentationSize);
+ }
+
+ [Fact]
+ public void LegacyCmSettings_Ignores_Malformed_Dotted_Values ()
+ {
+ using ConfigPathScope scope = new ();
+ var configDirectory = Path.GetDirectoryName (scope.ConfigPath);
+ Assert.NotNull (configDirectory);
+ Directory.CreateDirectory (configDirectory);
+ File.WriteAllText (
+ scope.ConfigPath,
+ """
+ {
+ "AppSettings": {
+ "EditorSettings.WordWrap": "yes",
+ "EditorSettings.IndentSize": "large"
+ }
+ }
+ """);
+
+ Exception? exception = Record.Exception (() => EditorSettings.Load (scope.ConfigPath));
+
+ Assert.Null (exception);
+ TedApp app = new ();
+ Assert.False (app.Editor.WordWrap);
+ Assert.Equal (4, app.Editor.IndentationSize);
+ }
[Fact]
public async Task ViewMenu_WordWrap_Toggle_Creates_ConfigFile ()
@@ -172,7 +246,7 @@ public async Task ViewMenu_WordWrap_MouseToggle_Creates_ConfigFile ()
public async Task ViewMenu_WordWrap_Toggle_Persists_True ()
{
// Reproduces the user-reported bug: toggling Word Wrap via the View menu
- // should create ted.config.json AND persist "EditorSettings.WordWrap": true.
+ // should create ted.config.json AND persist "WordWrap": true in the MEC settings section.
// Before the fix, a conflicting ValueChanged handler caused a double-toggle
// that reverted WordWrap to false immediately.
using ConfigPathScope scope = new ();
@@ -219,9 +293,9 @@ public async Task ViewMenu_WordWrap_Toggle_Persists_True ()
// Assert: config file created
Assert.True (File.Exists (scope.ConfigPath), "Config file was not created");
- // Assert: config file contains the correct persisted value
- var configContent = File.ReadAllText (scope.ConfigPath);
- Assert.Contains ("\"EditorSettings.WordWrap\": true", configContent);
+ // Assert: config file contains the correct persisted value in the MEC-native section.
+ JsonObject editorSettings = ReadEditorSettingsSection (scope.ConfigPath);
+ Assert.True (editorSettings[nameof (EditorSettings.WordWrap)]!.GetValue ());
}
[Fact]
@@ -237,7 +311,7 @@ public void QuitFile_DoesNotPersist_ViewSettings ()
}
[Fact]
- public void SaveViewSettings_Preserves_Other_TopLevel_Keys_And_Nests_Under_AppSettings ()
+ public void SaveViewSettings_Preserves_Other_TopLevel_Keys_And_Nests_Under_EditorSettings ()
{
using ConfigPathScope scope = new ();
var configDirectory = Path.GetDirectoryName (scope.ConfigPath);
@@ -247,7 +321,7 @@ public void SaveViewSettings_Preserves_Other_TopLevel_Keys_And_Nests_Under_AppSe
// Unrelated top-level data + a legacy flat key (pre-CM format) that must be migrated away.
File.WriteAllText (
scope.ConfigPath,
- "{\n \"Theme\": \"Dark\",\n \"EditorSettings.WordWrap\": false\n}\n");
+ "{\n \"Theme\": \"Dark\",\n \"EditorSettings.WordWrap\": false,\n \"AppSettings\": { \"EditorSettings.ShowTabs\": true }\n}\n");
TedApp app = new ();
app.Editor.WordWrap = true;
@@ -257,12 +331,35 @@ public void SaveViewSettings_Preserves_Other_TopLevel_Keys_And_Nests_Under_AppSe
var text = File.ReadAllText (scope.ConfigPath);
JsonNode root = JsonNode.Parse (text)!;
- // Unrelated key preserved; legacy flat key migrated away; ted settings nested under
- // "AppSettings" (the shape ConfigurationManager reads for AppSettingsScope).
+ // Unrelated key preserved; legacy keys migrated away; ted settings nested under
+ // "EditorSettings" (the MEC-native shape).
Assert.Equal ("Dark", (string?)root["Theme"]);
Assert.Null (root["EditorSettings.WordWrap"]);
+ Assert.Null (root["AppSettings"]);
+ JsonNode editorSettings = Assert.IsType (root["EditorSettings"]);
+ Assert.True ((bool)editorSettings["WordWrap"]!);
+ }
+
+ [Fact]
+ public void SaveViewSettings_Preserves_NonTed_AppSettings_Keys ()
+ {
+ using ConfigPathScope scope = new ();
+ var configDirectory = Path.GetDirectoryName (scope.ConfigPath);
+ Assert.NotNull (configDirectory);
+ Directory.CreateDirectory (configDirectory);
+
+ File.WriteAllText (
+ scope.ConfigPath,
+ "{\n \"AppSettings\": { \"OtherApp.Enabled\": true, \"EditorSettings.ShowTabs\": true }\n}\n");
+
+ TedApp app = new ();
+ InvokeSaveViewSettings (app);
+
+ JsonNode root = JsonNode.Parse (File.ReadAllText (scope.ConfigPath))!;
JsonNode appSettings = Assert.IsType (root["AppSettings"]);
- Assert.True ((bool)appSettings["EditorSettings.WordWrap"]!);
+
+ Assert.True ((bool)appSettings["OtherApp.Enabled"]!);
+ Assert.Null (appSettings["EditorSettings.ShowTabs"]);
}
[Fact]
@@ -307,10 +404,11 @@ public void Save_Appends_Before_Real_Brace_Not_Comment_Brace ()
InvokeSaveViewSettings (app);
var text = File.ReadAllText (scope.ConfigPath);
- // The inserted key should be valid JSON — not inside the comment
- Assert.Contains ("\"EditorSettings.WordWrap\": true", text);
+ // The inserted key should be valid JSON — not inside the comment.
+ JsonObject editorSettings = ReadEditorSettingsSection (scope.ConfigPath);
+ Assert.True (editorSettings[nameof (EditorSettings.WordWrap)]!.GetValue ());
// Config should still be parseable: the real closing brace should come after our insertion
- var wordWrapPos = text.IndexOf ("\"EditorSettings.WordWrap\"", StringComparison.Ordinal);
+ var wordWrapPos = text.IndexOf ("\"WordWrap\"", StringComparison.Ordinal);
var lastRealBrace = text.LastIndexOf ('}');
// The comment line's } may still exist, but our key must be before the real object close
Assert.True (wordWrapPos < lastRealBrace, "WordWrap key should appear before the root closing brace");
@@ -334,6 +432,13 @@ private static void InvokeSaveViewSettings (TedApp app)
saveViewSettings.Invoke (app, null);
}
+ private static JsonObject ReadEditorSettingsSection (string path)
+ {
+ JsonNode root = JsonNode.Parse (File.ReadAllText (path))!;
+
+ return Assert.IsType (root[EditorSettings.SectionName]);
+ }
+
private sealed class ConfigPathScope : IDisposable
{
private readonly string? _existingConfigContent;
@@ -363,18 +468,7 @@ internal ConfigPathScope ()
public void Dispose ()
{
- // Explicitly restore the EditorSettings statics to their declared defaults. CM is the
- // read authority and may have mutated them at app startup elsewhere; reset so this
- // serialized collection stays deterministic regardless of CM internals.
- EditorSettings.LineNumbers = true;
- EditorSettings.FoldIndicators = true;
- EditorSettings.WordWrap = false;
- EditorSettings.ShowTabs = false;
- EditorSettings.IndentSize = 4;
- EditorSettings.ConvertTabsToSpaces = true;
- EditorSettings.AutoIndent = true;
- EditorSettings.Scrollbars = true;
- EditorSettings.AutoComplete = false;
+ EditorSettings.ResetDefaults ();
if (_hadExistingConfig)
{
diff --git a/tests/Terminal.Gui.Editor.IntegrationTests/Terminal.Gui.Editor.IntegrationTests.csproj b/tests/Terminal.Gui.Editor.IntegrationTests/Terminal.Gui.Editor.IntegrationTests.csproj
index df11379a..dd8af3aa 100644
--- a/tests/Terminal.Gui.Editor.IntegrationTests/Terminal.Gui.Editor.IntegrationTests.csproj
+++ b/tests/Terminal.Gui.Editor.IntegrationTests/Terminal.Gui.Editor.IntegrationTests.csproj
@@ -8,6 +8,7 @@
+
diff --git a/tests/Terminal.Gui.Editor.Tests/EditorKeyBindingConfigTests.cs b/tests/Terminal.Gui.Editor.Tests/EditorKeyBindingConfigTests.cs
index 1bda413c..668ff7b1 100644
--- a/tests/Terminal.Gui.Editor.Tests/EditorKeyBindingConfigTests.cs
+++ b/tests/Terminal.Gui.Editor.Tests/EditorKeyBindingConfigTests.cs
@@ -1,16 +1,18 @@
// CoPilot - claude-sonnet-4-5
-using Terminal.Gui.Configuration;
+using Microsoft.Extensions.Configuration;
+using Terminal.Gui.Editor.Configuration;
using Terminal.Gui.Input;
using Terminal.Gui.ViewBase;
using Xunit;
+using MecEditorSettings = Terminal.Gui.Editor.Configuration.EditorSettings;
namespace Terminal.Gui.Editor.Tests;
///
/// Serialisation guard: mutates
-/// and , both
-/// process-wide statics that Terminal.Gui reads during view construction. Running these tests
+/// , a process-wide static that Terminal.Gui reads during view construction.
+/// Running these tests
/// concurrently with other tests that create instances would corrupt those
/// instances' .
/// DisableParallelization = true serialises this collection against every other collection
@@ -33,10 +35,8 @@ public sealed class KeyBindingConfigCollection;
///
/// -
///
-/// with the
-/// "View.ViewKeyBindings" JSON key — the standard Terminal.Gui per-view
-/// override mechanism that works for any view type including external-assembly
-/// views such as .
+/// Microsoft.Extensions.Configuration with the "Editor" section — the
+/// MEC path for external-assembly view defaults such as .
///
///
///
@@ -242,17 +242,15 @@ public void Override_DefaultKeyBindings_ExistingEditor_IsNotAffected ()
}
///
- /// Proves that loading a JSON keybinding profile via
- /// using the standard Terminal.Gui
- /// "View.ViewKeyBindings" key updates the instance's key
- /// bindings. This is the CM-native path for per-view binding configuration.
+ /// Proves that loading a keybinding profile via Microsoft.Extensions.Configuration updates
+ /// the instance's key bindings.
///
///
/// The JSON key format is:
///
/// {
- /// "View.ViewKeyBindings": {
- /// "Editor": {
+ /// "Editor": {
+ /// "DefaultKeyBindings": {
/// "Cut": { "All": ["Ctrl+W"] },
/// "Copy": { "All": ["Ctrl+Shift+C"] },
/// "Undo": { "All": ["Ctrl+Z"] }
@@ -264,33 +262,22 @@ public void Override_DefaultKeyBindings_ExistingEditor_IsNotAffected ()
/// config→Editor pipeline works.
///
[Fact]
- public void ConfigurationManager_RuntimeConfig_ViewKeyBindings_UpdatesEditorBindings ()
+ public void MecConfiguration_EditorDefaultKeyBindings_UpdatesEditorBindings ()
{
- Dictionary>? originalViewKeyBindings = View.ViewKeyBindings;
- var originalRuntimeConfig = ConfigurationManager.RuntimeConfig;
- var wasEnabled = ConfigurationManager.IsEnabled;
+ MecEditorSettings original = MecEditorSettings.Defaults;
try
{
- const string json = """
- {
- "View.ViewKeyBindings": {
- "Editor": {
- "Cut": { "All": ["Ctrl+W"] },
- "Copy": { "All": ["Ctrl+Shift+C"] },
- "Undo": { "All": ["Ctrl+Z"] }
- }
- }
- }
- """;
-
- ConfigurationManager.RuntimeConfig = json;
- ConfigurationManager.Enable (ConfigLocations.Runtime);
-
- // View.ViewKeyBindings must have been populated for the "Editor" type.
- Assert.NotNull (View.ViewKeyBindings);
- Assert.True (View.ViewKeyBindings!.ContainsKey ("Editor"),
- "ViewKeyBindings must contain an entry for 'Editor'");
+ IConfiguration configuration = new ConfigurationBuilder ()
+ .AddInMemoryCollection (new Dictionary
+ {
+ ["Editor:DefaultKeyBindings:Cut:All:0"] = "Ctrl+W",
+ ["Editor:DefaultKeyBindings:Copy:All:0"] = "Ctrl+Shift+C",
+ ["Editor:DefaultKeyBindings:Undo:All:0"] = "Ctrl+Z"
+ })
+ .Build ();
+
+ EditorConfiguration.Apply (configuration);
// A new Editor must include the configured bindings.
Editor editor = new ();
@@ -301,14 +288,32 @@ public void ConfigurationManager_RuntimeConfig_ViewKeyBindings_UpdatesEditorBind
}
finally
{
- // Always restore global state, even when the test fails.
- View.ViewKeyBindings = originalViewKeyBindings;
- ConfigurationManager.RuntimeConfig = originalRuntimeConfig;
+ MecEditorSettings.Defaults = original;
+ }
+ }
- if (!wasEnabled)
- {
- ConfigurationManager.Disable (true);
- }
+ [Fact]
+ public void MecConfiguration_LegacyDefaultKeyBindings_UpdatesEditorBindings ()
+ {
+ MecEditorSettings original = MecEditorSettings.Defaults;
+
+ try
+ {
+ IConfiguration configuration = new ConfigurationBuilder ()
+ .AddInMemoryCollection (new Dictionary
+ {
+ ["Terminal.Gui.Editor.Editor.DefaultKeyBindings:Cut:All:0"] = "Ctrl+W"
+ })
+ .Build ();
+
+ EditorConfiguration.Apply (configuration);
+
+ Editor editor = new ();
+ Assert.Contains (Key.W.WithCtrl, editor.KeyBindings.GetAllFromCommands (Command.Cut));
+ }
+ finally
+ {
+ MecEditorSettings.Defaults = original;
}
}
}
diff --git a/tests/Terminal.Gui.Editor.Tests/Terminal.Gui.Editor.Tests.csproj b/tests/Terminal.Gui.Editor.Tests/Terminal.Gui.Editor.Tests.csproj
index fb206f65..198f321d 100644
--- a/tests/Terminal.Gui.Editor.Tests/Terminal.Gui.Editor.Tests.csproj
+++ b/tests/Terminal.Gui.Editor.Tests/Terminal.Gui.Editor.Tests.csproj
@@ -8,6 +8,7 @@
+