Skip to content

Commit c88c772

Browse files
authored
Fixes #3692++ - Rearchitects drivers (#3837)
1 parent 3a240ec commit c88c772

File tree

101 files changed

+7662
-658
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+7662
-658
lines changed

Terminal.Gui/Application/Application.Initialization.cs

+36-29
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ public static partial class Application // Initialization (Init/Shutdown)
3737
/// </param>
3838
[RequiresUnreferencedCode ("AOT")]
3939
[RequiresDynamicCode ("AOT")]
40-
public static void Init (IConsoleDriver? driver = null, string? driverName = null) { InternalInit (driver, driverName); }
40+
public static void Init (IConsoleDriver? driver = null, string? driverName = null)
41+
{
42+
if (driverName?.StartsWith ("v2") ?? false)
43+
{
44+
ApplicationImpl.ChangeInstance (new ApplicationV2 ());
45+
}
46+
47+
ApplicationImpl.Instance.Init (driver, driverName);
48+
}
4149

4250
internal static int MainThreadId { get; set; } = -1;
4351

@@ -94,19 +102,7 @@ internal static void InternalInit (
94102

95103
AddKeyBindings ();
96104

97-
// Start the process of configuration management.
98-
// Note that we end up calling LoadConfigurationFromAllSources
99-
// multiple times. We need to do this because some settings are only
100-
// valid after a Driver is loaded. In this case we need just
101-
// `Settings` so we can determine which driver to use.
102-
// Don't reset, so we can inherit the theme from the previous run.
103-
string previousTheme = Themes?.Theme ?? string.Empty;
104-
Load ();
105-
if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
106-
{
107-
ThemeManager.SelectedTheme = previousTheme;
108-
}
109-
Apply ();
105+
InitializeConfigurationManagement ();
110106

111107
// Ignore Configuration for ForceDriver if driverName is specified
112108
if (!string.IsNullOrEmpty (driverName))
@@ -166,12 +162,28 @@ internal static void InternalInit (
166162

167163
SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
168164

169-
SupportedCultures = GetSupportedCultures ();
170165
MainThreadId = Thread.CurrentThread.ManagedThreadId;
171166
bool init = Initialized = true;
172167
InitializedChanged?.Invoke (null, new (init));
173168
}
174169

170+
internal static void InitializeConfigurationManagement ()
171+
{
172+
// Start the process of configuration management.
173+
// Note that we end up calling LoadConfigurationFromAllSources
174+
// multiple times. We need to do this because some settings are only
175+
// valid after a Driver is loaded. In this case we need just
176+
// `Settings` so we can determine which driver to use.
177+
// Don't reset, so we can inherit the theme from the previous run.
178+
string previousTheme = Themes?.Theme ?? string.Empty;
179+
Load ();
180+
if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
181+
{
182+
ThemeManager.SelectedTheme = previousTheme;
183+
}
184+
Apply ();
185+
}
186+
175187
internal static void SubscribeDriverEvents ()
176188
{
177189
ArgumentNullException.ThrowIfNull (Driver);
@@ -226,20 +238,7 @@ internal static void UnsubscribeDriverEvents ()
226238
/// up (Disposed)
227239
/// and terminal settings are restored.
228240
/// </remarks>
229-
public static void Shutdown ()
230-
{
231-
// TODO: Throw an exception if Init hasn't been called.
232-
233-
bool wasInitialized = Initialized;
234-
ResetState ();
235-
PrintJsonErrors ();
236-
237-
if (wasInitialized)
238-
{
239-
bool init = Initialized;
240-
InitializedChanged?.Invoke (null, new (in init));
241-
}
242-
}
241+
public static void Shutdown () => ApplicationImpl.Instance.Shutdown ();
243242

244243
/// <summary>
245244
/// Gets whether the application has been initialized with <see cref="Init"/> and not yet shutdown with <see cref="Shutdown"/>.
@@ -258,4 +257,12 @@ public static void Shutdown ()
258257
/// Intended to support unit tests that need to know when the application has been initialized.
259258
/// </remarks>
260259
public static event EventHandler<EventArgs<bool>>? InitializedChanged;
260+
261+
/// <summary>
262+
/// Raises the <see cref="InitializedChanged"/> event.
263+
/// </summary>
264+
internal static void OnInitializedChanged (object sender, EventArgs<bool> e)
265+
{
266+
Application.InitializedChanged?.Invoke (sender,e);
267+
}
261268
}

Terminal.Gui/Application/Application.Keyboard.cs

-2
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ internal static void AddKeyBindings ()
177177
return true;
178178
}
179179
);
180-
181180
AddCommand (
182181
Command.Suspend,
183182
static () =>
@@ -187,7 +186,6 @@ internal static void AddKeyBindings ()
187186
return true;
188187
}
189188
);
190-
191189
AddCommand (
192190
Command.NextTabStop,
193191
static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));

Terminal.Gui/Application/Application.Run.cs

+14-129
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,8 @@ internal static bool PositionCursor ()
305305
/// <returns>The created <see cref="Toplevel"/> object. The caller is responsible for disposing this object.</returns>
306306
[RequiresUnreferencedCode ("AOT")]
307307
[RequiresDynamicCode ("AOT")]
308-
public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) { return Run<Toplevel> (errorHandler, driver); }
308+
public static Toplevel Run (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null) =>
309+
ApplicationImpl.Instance.Run (errorHandler, driver);
309310

310311
/// <summary>
311312
/// Runs the application by creating a <see cref="Toplevel"/>-derived object of type <c>T</c> and calling
@@ -331,20 +332,7 @@ internal static bool PositionCursor ()
331332
[RequiresUnreferencedCode ("AOT")]
332333
[RequiresDynamicCode ("AOT")]
333334
public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriver? driver = null)
334-
where T : Toplevel, new()
335-
{
336-
if (!Initialized)
337-
{
338-
// Init() has NOT been called.
339-
InternalInit (driver, null, true);
340-
}
341-
342-
var top = new T ();
343-
344-
Run (top, errorHandler);
345-
346-
return top;
347-
}
335+
where T : Toplevel, new() => ApplicationImpl.Instance.Run<T> (errorHandler, driver);
348336

349337
/// <summary>Runs the Application using the provided <see cref="Toplevel"/> view.</summary>
350338
/// <remarks>
@@ -385,110 +373,31 @@ public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriv
385373
/// rethrows when null).
386374
/// </param>
387375
public static void Run (Toplevel view, Func<Exception, bool>? errorHandler = null)
388-
{
389-
ArgumentNullException.ThrowIfNull (view);
390-
391-
if (Initialized)
392-
{
393-
if (Driver is null)
394-
{
395-
// Disposing before throwing
396-
view.Dispose ();
397-
398-
// This code path should be impossible because Init(null, null) will select the platform default driver
399-
throw new InvalidOperationException (
400-
"Init() completed without a driver being set (this should be impossible); Run<T>() cannot be called."
401-
);
402-
}
403-
}
404-
else
405-
{
406-
// Init() has NOT been called.
407-
throw new InvalidOperationException (
408-
"Init() has not been called. Only Run() or Run<T>() can be used without calling Init()."
409-
);
410-
}
411-
412-
var resume = true;
413-
414-
while (resume)
415-
{
416-
#if !DEBUG
417-
try
418-
{
419-
#endif
420-
resume = false;
421-
RunState runState = Begin (view);
422-
423-
// If EndAfterFirstIteration is true then the user must dispose of the runToken
424-
// by using NotifyStopRunState event.
425-
RunLoop (runState);
426-
427-
if (runState.Toplevel is null)
428-
{
429-
#if DEBUG_IDISPOSABLE
430-
Debug.Assert (TopLevels.Count == 0);
431-
#endif
432-
runState.Dispose ();
433-
434-
return;
435-
}
436-
437-
if (!EndAfterFirstIteration)
438-
{
439-
End (runState);
440-
}
441-
#if !DEBUG
442-
}
443-
catch (Exception error)
444-
{
445-
if (errorHandler is null)
446-
{
447-
throw;
448-
}
449-
450-
resume = errorHandler (error);
451-
}
452-
#endif
453-
}
454-
}
376+
=> ApplicationImpl.Instance.Run (view, errorHandler);
455377

456378
/// <summary>Adds a timeout to the application.</summary>
457379
/// <remarks>
458380
/// When time specified passes, the callback will be invoked. If the callback returns true, the timeout will be
459381
/// reset, repeating the invocation. If it returns false, the timeout will stop and be removed. The returned value is a
460382
/// token that can be used to stop the timeout by calling <see cref="RemoveTimeout(object)"/>.
461383
/// </remarks>
462-
public static object? AddTimeout (TimeSpan time, Func<bool> callback)
463-
{
464-
return MainLoop?.AddTimeout (time, callback) ?? null;
465-
}
384+
public static object? AddTimeout (TimeSpan time, Func<bool> callback) => ApplicationImpl.Instance.AddTimeout (time, callback);
466385

467386
/// <summary>Removes a previously scheduled timeout</summary>
468387
/// <remarks>The token parameter is the value returned by <see cref="AddTimeout"/>.</remarks>
469388
/// Returns
470-
/// <c>true</c>
389+
/// <see langword="true"/>
471390
/// if the timeout is successfully removed; otherwise,
472-
/// <c>false</c>
391+
/// <see langword="false"/>
473392
/// .
474393
/// This method also returns
475-
/// <c>false</c>
394+
/// <see langword="false"/>
476395
/// if the timeout is not found.
477-
public static bool RemoveTimeout (object token) { return MainLoop?.RemoveTimeout (token) ?? false; }
396+
public static bool RemoveTimeout (object token) => ApplicationImpl.Instance.RemoveTimeout (token);
478397

479398
/// <summary>Runs <paramref name="action"/> on the thread that is processing events</summary>
480399
/// <param name="action">the action to be invoked on the main processing thread.</param>
481-
public static void Invoke (Action action)
482-
{
483-
MainLoop?.AddIdle (
484-
() =>
485-
{
486-
action ();
487-
488-
return false;
489-
}
490-
);
491-
}
400+
public static void Invoke (Action action) => ApplicationImpl.Instance.Invoke (action);
492401

493402
// TODO: Determine if this is really needed. The only code that calls WakeUp I can find
494403
// is ProgressBarStyles, and it's not clear it needs to.
@@ -517,8 +426,7 @@ public static void LayoutAndDraw (bool forceDraw = false)
517426

518427
View.SetClipToScreen ();
519428
View.Draw (TopLevels, neededLayout || forceDraw);
520-
View.SetClipToScreen ();
521-
429+
View.SetClipToScreen ();
522430
Driver?.Refresh ();
523431
}
524432

@@ -528,7 +436,7 @@ public static void LayoutAndDraw (bool forceDraw = false)
528436

529437
/// <summary>The <see cref="MainLoop"/> driver for the application</summary>
530438
/// <value>The main loop.</value>
531-
internal static MainLoop? MainLoop { get; private set; }
439+
internal static MainLoop? MainLoop { get; set; }
532440

533441
/// <summary>
534442
/// Set to true to cause <see cref="End"/> to be called after the first iteration. Set to false (the default) to
@@ -612,31 +520,8 @@ public static bool RunIteration (ref RunState state, bool firstIteration = false
612520
/// property on the currently running <see cref="Toplevel"/> to false.
613521
/// </para>
614522
/// </remarks>
615-
public static void RequestStop (Toplevel? top = null)
616-
{
617-
if (top is null)
618-
{
619-
top = Top;
620-
}
621-
622-
if (!top!.Running)
623-
{
624-
return;
625-
}
626-
627-
var ev = new ToplevelClosingEventArgs (top);
628-
top.OnClosing (ev);
629-
630-
if (ev.Cancel)
631-
{
632-
return;
633-
}
634-
635-
top.Running = false;
636-
OnNotifyStopRunState (top);
637-
}
638-
639-
private static void OnNotifyStopRunState (Toplevel top)
523+
public static void RequestStop (Toplevel? top = null) => ApplicationImpl.Instance.RequestStop (top);
524+
internal static void OnNotifyStopRunState (Toplevel top)
640525
{
641526
if (EndAfterFirstIteration)
642527
{

0 commit comments

Comments
 (0)