Merge pull request #3845 from tig/v2-CM-Editor

Redesign of CM Editor
This commit is contained in:
Tig
2024-11-24 11:26:27 -07:00
committed by GitHub
9 changed files with 191 additions and 189 deletions

View File

@@ -22,35 +22,35 @@ public enum ConfigLocations
/// </summary>
Default = 0b_0000_0001,
/// <summary>
/// Global settings in the current directory (e.g. <c>./.tui/config.json</c>).
/// </summary>
GlobalCurrent = 0b_0000_0010,
/// <summary>
/// Global settings in the home directory (e.g. <c>~/.tui/config.json</c>).
/// </summary>
GlobalHome = 0b_0000_0100,
/// <summary>
/// App resources (e.g. <c>MyApp.Resources.config.json</c>).
/// </summary>
AppResources = 0b_0000_1000,
/// <summary>
/// App settings in the current directory (e.g. <c>./.tui/MyApp.config.json</c>).
/// </summary>
AppCurrent = 0b_0001_0000,
/// <summary>
/// App settings in the home directory (e.g. <c>~/.tui/MyApp.config.json</c>).
/// </summary>
AppHome = 0b_0010_0000,
AppResources = 0b_0000_0010,
/// <summary>
/// Settings in the <see cref="ConfigurationManager.RuntimeConfig"/> static property.
/// </summary>
Runtime = 0b_0100_0000,
Runtime = 0b_0000_0100,
/// <summary>
/// Global settings in the current directory (e.g. <c>./.tui/config.json</c>).
/// </summary>
GlobalCurrent = 0b_0000_1000,
/// <summary>
/// Global settings in the home directory (e.g. <c>~/.tui/config.json</c>).
/// </summary>
GlobalHome = 0b_0001_0000,
/// <summary>
/// App settings in the current directory (e.g. <c>./.tui/MyApp.config.json</c>).
/// </summary>
AppCurrent = 0b_0010_0000,
/// <summary>
/// App settings in the home directory (e.g. <c>~/.tui/MyApp.config.json</c>).
/// </summary>
AppHome = 0b_0100_0000,
/// <summary>This constant is a combination of all locations</summary>
All = 0b_1111_1111

View File

@@ -223,7 +223,7 @@ public static class ConfigurationManager
/// <summary>
/// Gets or sets the in-memory config.json. See <see cref="ConfigLocations.Runtime"/>.
/// </summary>
public static string? RuntimeConfig { get; set; }
public static string? RuntimeConfig { get; set; } = """{ }""";
/// <summary>
/// Loads all settings found in the configuration storage locations (<see cref="ConfigLocations"/>). Optionally, resets
@@ -250,16 +250,6 @@ public static class ConfigurationManager
Reset ();
}
if (Locations.HasFlag (ConfigLocations.GlobalCurrent))
{
Settings?.Update ($"./.tui/{_configFilename}");
}
if (Locations.HasFlag (ConfigLocations.GlobalHome))
{
Settings?.Update ($"~/.tui/{_configFilename}");
}
if (Locations.HasFlag (ConfigLocations.AppResources))
{
string? embeddedStylesResourceName = Assembly.GetEntryAssembly ()
@@ -272,22 +262,33 @@ public static class ConfigurationManager
embeddedStylesResourceName = _configFilename;
}
Settings?.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!);
}
if (Locations.HasFlag (ConfigLocations.AppCurrent))
{
Settings?.Update ($"./.tui/{AppName}.{_configFilename}");
}
if (Locations.HasFlag (ConfigLocations.AppHome))
{
Settings?.Update ($"~/.tui/{AppName}.{_configFilename}");
Settings?.UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!, ConfigLocations.AppResources);
}
if (Locations.HasFlag (ConfigLocations.Runtime) && !string.IsNullOrEmpty (RuntimeConfig))
{
Settings?.Update (RuntimeConfig, "ConfigurationManager.Memory");
Settings?.Update (RuntimeConfig, "ConfigurationManager.RuntimeConfig", ConfigLocations.Runtime);
}
if (Locations.HasFlag (ConfigLocations.GlobalCurrent))
{
Settings?.Update ($"./.tui/{_configFilename}", ConfigLocations.GlobalCurrent);
}
if (Locations.HasFlag (ConfigLocations.GlobalHome))
{
Settings?.Update ($"~/.tui/{_configFilename}", ConfigLocations.GlobalHome);
}
if (Locations.HasFlag (ConfigLocations.AppCurrent))
{
Settings?.Update ($"./.tui/{AppName}.{_configFilename}", ConfigLocations.AppCurrent);
}
if (Locations.HasFlag (ConfigLocations.AppHome))
{
Settings?.Update ($"~/.tui/{AppName}.{_configFilename}", ConfigLocations.AppHome);
}
ThemeManager.SelectedTheme = Settings!["Theme"].PropertyValue as string ?? "Default";
@@ -358,7 +359,8 @@ public static class ConfigurationManager
{
Settings.UpdateFromResource (
typeof (ConfigurationManager).Assembly,
$"Terminal.Gui.Resources.{_configFilename}"
$"Terminal.Gui.Resources.{_configFilename}",
ConfigLocations.Default
);
}

View File

@@ -27,7 +27,7 @@ namespace Terminal.Gui;
public class SettingsScope : Scope<SettingsScope>
{
/// <summary>The list of paths to the configuration files.</summary>
public List<string> Sources = new ();
public Dictionary<ConfigLocations, string> Sources { get; } = new ();
/// <summary>Points to our JSON schema.</summary>
[JsonInclude]
@@ -37,9 +37,10 @@ public class SettingsScope : Scope<SettingsScope>
/// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
/// <param name="stream">Json document to update the settings with.</param>
/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
/// <param name="location">Location</param>
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public SettingsScope? Update (Stream stream, string source)
public SettingsScope? Update (Stream stream, string source, ConfigLocations location)
{
// Update the existing settings with the new settings.
try
@@ -47,9 +48,9 @@ public class SettingsScope : Scope<SettingsScope>
Update ((SettingsScope)JsonSerializer.Deserialize (stream, typeof (SettingsScope), _serializerOptions)!);
OnUpdated ();
Debug.WriteLine ($"ConfigurationManager: Read configuration from \"{source}\"");
if (!Sources.Contains (source))
if (!Sources.ContainsValue (source))
{
Sources.Add (source);
Sources.Add (location, source);
}
return this;
@@ -68,19 +69,20 @@ public class SettingsScope : Scope<SettingsScope>
}
/// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON file.</summary>
/// <param name="filePath"></param>
/// <param name="filePath">Path to the file.</param>
/// <param name="location">The location</param>
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public SettingsScope? Update (string filePath)
public SettingsScope? Update (string filePath, ConfigLocations location)
{
string realPath = filePath.Replace ("~", Environment.GetFolderPath (Environment.SpecialFolder.UserProfile));
if (!File.Exists (realPath))
{
Debug.WriteLine ($"ConfigurationManager: Configuration file \"{realPath}\" does not exist.");
if (!Sources.Contains (filePath))
if (!Sources.ContainsValue (filePath))
{
Sources.Add (filePath);
Sources.Add (location, filePath);
}
return this;
@@ -95,7 +97,7 @@ public class SettingsScope : Scope<SettingsScope>
try
{
FileStream? stream = File.OpenRead (realPath);
SettingsScope? s = Update (stream, filePath);
SettingsScope? s = Update (stream, filePath, location);
stream.Close ();
stream.Dispose ();
@@ -103,7 +105,7 @@ public class SettingsScope : Scope<SettingsScope>
}
catch (IOException ioe)
{
Debug.WriteLine($"Couldn't open {filePath}. Retrying...: {ioe}");
Debug.WriteLine ($"Couldn't open {filePath}. Retrying...: {ioe}");
Task.Delay (100);
retryCount++;
}
@@ -115,32 +117,33 @@ public class SettingsScope : Scope<SettingsScope>
/// <summary>Updates the <see cref="SettingsScope"/> with the settings in a JSON string.</summary>
/// <param name="json">Json document to update the settings with.</param>
/// <param name="source">The source (filename/resource name) the Json document was read from.</param>
/// <param name="location">The location.</param>
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public SettingsScope? Update (string? json, string source)
public SettingsScope? Update (string? json, string source, ConfigLocations location)
{
//if (string.IsNullOrEmpty (json))
//{
// Debug.WriteLine ($"ConfigurationManager: Configuration file \"{source}\" is empty.");
// return this;
//}
if (string.IsNullOrEmpty (json))
{
return null;
}
var stream = new MemoryStream ();
var writer = new StreamWriter (stream);
writer.Write (json);
writer.Flush ();
stream.Position = 0;
return Update (stream, source);
return Update (stream, source, location);
}
/// <summary>Updates the <see cref="SettingsScope"/> with the settings from a Json resource.</summary>
/// <param name="assembly"></param>
/// <param name="resourceName"></param>
/// <param name="location"></param>
[RequiresUnreferencedCode ("AOT")]
[RequiresDynamicCode ("AOT")]
public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName)
public SettingsScope? UpdateFromResource (Assembly assembly, string resourceName, ConfigLocations location)
{
if (resourceName is null || string.IsNullOrEmpty (resourceName))
if (string.IsNullOrEmpty (resourceName))
{
Debug.WriteLine (
$"ConfigurationManager: Resource \"{resourceName}\" does not exist in \"{assembly.GetName ().Name}\"."
@@ -149,20 +152,13 @@ public class SettingsScope : Scope<SettingsScope>
return this;
}
// BUG: Not trim-compatible
// Not a bug, per se, but it's easily fixable by just loading the file.
// Defaults can just be field initializers for involved types.
using Stream? stream = assembly.GetManifestResourceStream (resourceName)!;
using Stream? stream = assembly.GetManifestResourceStream (resourceName);
if (stream is null)
{
Debug.WriteLine (
$"ConfigurationManager: Failed to read resource \"{resourceName}\" from \"{assembly.GetName ().Name}\"."
);
return this;
return null;
}
return Update (stream, $"resource://[{assembly.GetName ().Name}]/{resourceName}");
return Update (stream, $"resource://[{assembly.GetName ().Name}]/{resourceName}", location);
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -21,9 +22,9 @@ public class ConfigurationEditor : Scenario
HotNormal = new Attribute (Color.Magenta, Color.White)
};
private static Action _editorColorSchemeChanged;
private Shortcut _lenShortcut;
private TileView _tileView;
private static Action? _editorColorSchemeChanged;
private TabView? _tabView;
private Shortcut? _lenShortcut;
[SerializableConfigurationProperty (Scope = typeof (AppScope))]
public static ColorScheme EditorColorScheme
@@ -42,26 +43,15 @@ public class ConfigurationEditor : Scenario
Toplevel top = new ();
_tileView = new TileView (0)
{
Width = Dim.Fill (),
Height = Dim.Fill (1),
Orientation = Orientation.Vertical,
LineStyle = LineStyle.Single,
TabStop = TabBehavior.TabGroup
};
top.Add (_tileView);
_lenShortcut = new Shortcut ()
{
Title = "Len: ",
Title = "",
};
var quitShortcut = new Shortcut ()
{
Key = Application.QuitKey,
Title = $"{Application.QuitKey} Quit",
Title = $"Quit",
Action = Quit
};
@@ -81,35 +71,44 @@ public class ConfigurationEditor : Scenario
var statusBar = new StatusBar ([quitShortcut, reloadShortcut, saveShortcut, _lenShortcut]);
top.Add (statusBar);
_tabView = new ()
{
Width = Dim.Fill (),
Height = Dim.Fill (Dim.Func (() => statusBar.Frame.Height))
};
top.Add (_tabView, statusBar);
top.Loaded += (s, a) =>
{
Open ();
//_tileView.AdvanceFocus (NavigationDirection.Forward, null);
_editorColorSchemeChanged?.Invoke ();
};
_editorColorSchemeChanged += () =>
{
foreach (Tile t in _tileView.Tiles)
{
t.ContentView.ColorScheme = EditorColorScheme;
t.ContentView.SetNeedsDraw ();
}
void OnEditorColorSchemeChanged ()
{
if (Application.Top is { })
{
return;
}
;
};
foreach (ConfigTextView t in _tabView.Subviews.Where (v => v is ConfigTextView).Cast<ConfigTextView> ())
{
t.ColorScheme = EditorColorScheme;
}
}
_editorColorSchemeChanged.Invoke ();
_editorColorSchemeChanged += OnEditorColorSchemeChanged;
Application.Run (top);
_editorColorSchemeChanged -= OnEditorColorSchemeChanged;
top.Dispose ();
Application.Shutdown ();
}
public void Save ()
{
if (_tileView.MostFocused is ConfigTextView editor)
if (Application.Navigation?.GetFocused () is ConfigTextView editor)
{
editor.Save ();
}
@@ -117,56 +116,65 @@ public class ConfigurationEditor : Scenario
private void Open ()
{
var subMenu = new MenuBarItem { Title = "_View" };
foreach (string configFile in ConfigurationManager.Settings.Sources)
foreach (var config in ConfigurationManager.Settings!.Sources)
{
var homeDir = $"{Environment.GetFolderPath (Environment.SpecialFolder.UserProfile)}";
var fileInfo = new FileInfo (configFile.Replace ("~", homeDir));
var fileInfo = new FileInfo (config.Value.Replace ("~", homeDir));
Tile tile = _tileView.InsertTile (_tileView.Tiles.Count);
tile.Title = configFile.StartsWith ("resource://") ? fileInfo.Name : configFile;
var textView = new ConfigTextView
var editor = new ConfigTextView
{
X = 0,
Y = 0,
Title = config.Value.StartsWith ("resource://") ? fileInfo.Name : config.Value,
Width = Dim.Fill (),
Height = Dim.Fill (),
Height = Dim.Fill(),
FileInfo = fileInfo,
Tile = tile
};
tile.ContentView.Add (textView);
Tab tab = new Tab ()
{
View = editor,
DisplayText = config.Key.ToString ()
};
textView.Read ();
_tabView!.AddTab (tab, false);
textView.HasFocusChanged += (s, e) =>
{
if (e.NewValue)
{
_lenShortcut.Title = $"Len:{textView.Text.Length}";
}
};
editor.Read ();
editor.ContentsChanged += (sender, args) =>
{
_lenShortcut!.Title = _lenShortcut!.Title.Replace ("*", "");
if (editor.IsDirty)
{
_lenShortcut!.Title += "*";
}
};
_lenShortcut!.Title = $"{editor.Title}";
}
if (_tileView.Tiles.Count > 2)
{
_tileView.Tiles.ToArray () [1].ContentView.SetFocus ();
}
_tabView!.SelectedTabChanged += (sender, args) =>
{
_lenShortcut!.Title = $"{args.NewTab.View!.Title}";
};
}
private void Quit ()
{
foreach (Tile tile in _tileView.Tiles)
{
var editor = tile.ContentView.Subviews [0] as ConfigTextView;
foreach (ConfigTextView editor in _tabView!.Tabs.Select(v =>
{
if (v.View is ConfigTextView ctv)
{
return ctv;
}
return null;
}).Cast<ConfigTextView> ())
{
if (editor.IsDirty)
{
int result = MessageBox.Query (
"Save Changes",
$"Save changes to {editor.FileInfo.FullName}",
$"Save changes to {editor.FileInfo!.Name}",
"_Yes",
"_No",
"_Cancel"
@@ -189,7 +197,7 @@ public class ConfigurationEditor : Scenario
private void Reload ()
{
if (_tileView.MostFocused is ConfigTextView editor)
if (Application.Navigation?.GetFocused () is ConfigTextView editor)
{
editor.Read ();
}
@@ -199,32 +207,16 @@ public class ConfigurationEditor : Scenario
{
internal ConfigTextView ()
{
ContentsChanged += (s, obj) =>
{
if (IsDirty)
{
if (!Tile.Title.EndsWith ('*'))
{
Tile.Title += '*';
}
else
{
Tile.Title = Tile.Title.TrimEnd ('*');
}
}
};
TabStop = TabBehavior.TabGroup;
}
internal FileInfo FileInfo { get; set; }
internal Tile Tile { get; set; }
internal FileInfo? FileInfo { get; set; }
internal void Read ()
{
Assembly assembly = null;
Assembly? assembly = null;
if (FileInfo.FullName.Contains ("[Terminal.Gui]"))
if (FileInfo!.FullName.Contains ("[Terminal.Gui]"))
{
// Library resources
assembly = typeof (ConfigurationManager).Assembly;
@@ -236,19 +228,27 @@ public class ConfigurationEditor : Scenario
if (assembly != null)
{
string name = assembly
.GetManifestResourceNames ()
.FirstOrDefault (x => x.EndsWith ("config.json"));
using Stream stream = assembly.GetManifestResourceStream (name);
using var reader = new StreamReader (stream);
Text = reader.ReadToEnd ();
ReadOnly = true;
Enabled = true;
string? name = assembly
.GetManifestResourceNames ()
.FirstOrDefault (x => x.EndsWith ("config.json"));
if (!string.IsNullOrEmpty (name))
{
using Stream? stream = assembly.GetManifestResourceStream (name);
using var reader = new StreamReader (stream!);
Text = reader.ReadToEnd ();
ReadOnly = true;
Enabled = true;
}
return;
}
if (!FileInfo.Exists)
if (FileInfo!.FullName.Contains ("RuntimeConfig"))
{
Text = ConfigurationManager.RuntimeConfig!;
} else if (!FileInfo.Exists)
{
// Create empty config file
Text = ConfigurationManager.GetEmptyJson ();
@@ -257,12 +257,17 @@ public class ConfigurationEditor : Scenario
{
Text = File.ReadAllText (FileInfo.FullName);
}
Tile.Title = Tile.Title.TrimEnd ('*');
}
internal void Save ()
{
if (FileInfo!.FullName.Contains ("RuntimeConfig"))
{
ConfigurationManager.RuntimeConfig = Text;
IsDirty = false;
return;
}
if (!Directory.Exists (FileInfo.DirectoryName))
{
// Create dir
@@ -272,7 +277,6 @@ public class ConfigurationEditor : Scenario
using StreamWriter writer = File.CreateText (FileInfo.FullName);
writer.Write (Text);
writer.Close ();
Tile.Title = Tile.Title.TrimEnd ('*');
IsDirty = false;
}
}

View File

@@ -20,10 +20,10 @@
<DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Remove="Scenarios\Editors\Resources\config.json" />
<None Remove="Resources\config.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Scenarios\Editors\Resources\config.json" />
<EmbeddedResource Include="Resources\config.json" />
</ItemGroup>
<ItemGroup>
<None Update="Scenarios\AnimationScenario\Spinning_globe_dark_small.gif" CopyToOutputDirectory="PreserveNewest" />

View File

@@ -189,7 +189,7 @@ public class ConfigurationManagerTests
Updated += ConfigurationManager_Updated;
var fired = false;
void ConfigurationManager_Updated (object? sender, ConfigurationManagerEventArgs obj)
void ConfigurationManager_Updated (object sender, ConfigurationManagerEventArgs obj)
{
fired = true;
}
@@ -285,7 +285,7 @@ public class ConfigurationManagerTests
Updated += ConfigurationManager_Updated;
var fired = false;
void ConfigurationManager_Updated (object? sender, ConfigurationManagerEventArgs obj)
void ConfigurationManager_Updated (object sender, ConfigurationManagerEventArgs obj)
{
fired = true;
}
@@ -522,7 +522,7 @@ public class ConfigurationManagerTests
// Change Base
Stream json = ToStream ();
Settings!.Update (json, "TestConfigurationManagerInitDriver");
Settings!.Update (json, "TestConfigurationManagerInitDriver", ConfigLocations.Runtime);
Dictionary<string, ColorScheme> colorSchemes =
(Dictionary<string, ColorScheme>)Themes [Themes.Theme] ["ColorSchemes"].PropertyValue;
@@ -580,7 +580,7 @@ public class ConfigurationManagerTests
}
}";
Settings!.Update (json, "test");
Settings!.Update (json, "test", ConfigLocations.Runtime);
// AbNormal is not a ColorScheme attribute
json = @"
@@ -603,7 +603,7 @@ public class ConfigurationManagerTests
}
}";
Settings.Update (json, "test");
Settings.Update (json, "test", ConfigLocations.Runtime);
// Modify hotNormal background only
json = @"
@@ -625,9 +625,9 @@ public class ConfigurationManagerTests
}
}";
Settings.Update (json, "test");
Settings.Update (json, "test", ConfigLocations.Runtime);
Settings.Update ("{}}", "test");
Settings.Update ("{}}", "test", ConfigLocations.Runtime);
Assert.NotEqual (0, _jsonErrors.Length);
@@ -663,7 +663,7 @@ public class ConfigurationManagerTests
]
}";
var jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
var jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test", ConfigLocations.Runtime));
Assert.Equal ("Unexpected color name: brownish.", jsonException.Message);
// AbNormal is not a ColorScheme attribute
@@ -687,7 +687,7 @@ public class ConfigurationManagerTests
]
}";
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test", ConfigLocations.Runtime));
Assert.Equal ("Unrecognized ColorScheme Attribute name: AbNormal.", jsonException.Message);
// Modify hotNormal background only
@@ -710,7 +710,7 @@ public class ConfigurationManagerTests
]
}";
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test", ConfigLocations.Runtime));
Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message);
// Unknown property
@@ -719,7 +719,7 @@ public class ConfigurationManagerTests
""Unknown"" : ""Not known""
}";
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test"));
jsonException = Assert.Throws<JsonException> (() => Settings!.Update (json, "test", ConfigLocations.Runtime));
Assert.StartsWith ("Unknown property", jsonException.Message);
Assert.Equal (0, _jsonErrors.Length);
@@ -735,7 +735,7 @@ public class ConfigurationManagerTests
GetHardCodedDefaults ();
Stream stream = ToStream ();
Settings!.Update (stream, "TestConfigurationManagerToJson");
Settings!.Update (stream, "TestConfigurationManagerToJson", ConfigLocations.Runtime);
}
[Fact]
@@ -884,7 +884,7 @@ public class ConfigurationManagerTests
Reset ();
ThrowOnJsonErrors = true;
Settings!.Update (json, "TestConfigurationManagerUpdateFromJson");
Settings!.Update (json, "TestConfigurationManagerUpdateFromJson", ConfigLocations.Runtime);
Assert.Equal (KeyCode.Esc, Application.QuitKey.KeyCode);
Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)Settings ["Application.QuitKey"].PropertyValue)!.KeyCode);

View File

@@ -23,7 +23,7 @@ public class SettingsScopeTests
}
""";
Settings!.Update (json, "test");
Settings!.Update (json, "test", ConfigLocations.Runtime);
// assert
Assert.Equal (Key.Q.WithCtrl, (Key)Settings ["Application.QuitKey"].PropertyValue);

View File

@@ -12,19 +12,19 @@ Settings that will apply to all applications (global settings) reside in files n
Settings are applied using the following precedence (higher precedence settings overwrite lower precedence settings):
1. @Terminal.Gui.ConfigLocations.Runtime - Settings stored in the @Terminal.Gui.ConfigurationManager.RuntimeConfig static property --- Hightest precedence.
1. @Terminal.Gui.ConfigLocations.Default - Default settings in the Terminal.Gui assembly -- Lowest precedence.
2. @Terminal.Gui.ConfigLocations.AppHome - App-specific settings in the users's home directory (`~/.tui/appname.config.json`).
2. @Terminal.Gui.ConfigLocations.Runtime - Settings stored in the @Terminal.Gui.ConfigurationManager.RuntimeConfig static property.
3. @Terminal.Gui.ConfigLocations.AppCurrent - App-specific settings in the directory the app was launched from (`./.tui/appname.config.json`).
3. @Terminal.Gui.ConfigLocations.AppResources - App settings in app resources (`Resources/config.json`).
4. @Terminal.Gui.ConfigLocations.AppResources - App settings in app resources (`Resources/config.json`).
4. @Terminal.Gui.ConfigLocations.AppHome - App-specific settings in the users's home directory (`~/.tui/appname.config.json`).
5. @Terminal.Gui.ConfigLocations.GlobalHome - Global settings in the the user's home directory (`~/.tui/config.json`).
5. @Terminal.Gui.ConfigLocations.AppCurrent - App-specific settings in the directory the app was launched from (`./.tui/appname.config.json`).
6. @Terminal.Gui.ConfigLocations.GlobalCurrent - Global settings in the directory the app was launched from (`./.tui/config.json`).
6. @Terminal.Gui.ConfigLocations.GlobalHome - Global settings in the the user's home directory (`~/.tui/config.json`).
7. @Terminal.Gui.ConfigLocations.Default - Default settings in the Terminal.Gui assembly -- Lowest precedence.
7. @Terminal.Gui.ConfigLocations.GlobalCurrent - Global settings in the directory the app was launched from (`./.tui/config.json`) --- Hightest precedence.
The `UI Catalog` application provides an example of how to use the [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) class to load and save configuration files. The `Configuration Editor` scenario provides an editor that allows users to edit the configuration files. UI Catalog also uses a file system watcher to detect changes to the configuration files to tell [`ConfigurationManager`](~/api/Terminal.Gui.ConfigurationManager.yml) to reload them; allowing users to change settings without having to restart the application.