Skip to content

Commit 51dda7e

Browse files
tznindBDisp
andauthored
Fixes #3947 Adds Fake driver and fixes fluent tests (iteration-zero) (#4225)
* Consider width2 chars that are not IsBmp * Apply same fix in WindowsDriver * Explicitly use type of local variable * Revert changes to WindowsDriver * Assume we are running in a terminal that supports true color by default unless user explicitly forces 16 * Switch to SetAttribute and WriteConsole instead of WriteConsoleOutput for 16 color mode * Fix some cursor issues (WIP) * Remove concept of 'dirty rows' from v2 as its never actually used * Remove damageRegion as it does nothing * Make string builder to console writing simpler * Radically simplify Write method * Simplify conditional logic * Simplify restoring cursor position * Reference local variable for console buffer * Reduce calls to ConsoleWrite by accumulating till attribute changes * When resizing v2 16 color mode on windows, recreate the back buffer to match its size * Fixes for VTS enabled * Fix _lastSize never being assigned * Fixes VTS for Force16Colors * Fixes force16Colors in VTS * Fixes escape sequences always echoing in non-VTS * Force Force16Colors in non-VTS. It have a bug in adding a newline in the last line * WIP Add base class for NetOutput * Abstract away how we change attribute * WIP - Make WindowsOutput use base class * WIP working to fix set cursor position * Remove commented out code * Fixes legacy output mode * Fixes size with no alt buffer supported on VTS and size restore after maximized. * Fix set cursor which also fixes the broken surrogate pairs * Add force parameter * Fixes an issue that only happens with Windows Terminal when paste surrogate pairs by press Ctrl+V * In Windows escape sequences must be sent during the lifetime of the console which is created in input handle * Ensure flush the input buffer before reset the console * Flush input buffer before reset console in v2win * Fixes issue in v2net not being refreshing the menu bar at start * Only force layout and draw on size changed. * Fix v2net issue not draw first line by forcing set cursor position * Set _lastCursorPosition nullable and remove bool force from set cursor position * Remove force parameter * Add v2 version of fake driver attribute * Make direct replacement and wire up window resizing events * Update casts to use V2 fake driver instead * Adjust interfaces to expose less internals * Fix not raising iteration event in v2 * WIP investigate what it takes to do resize and redraw using TextAlignment_Centered as example * Sketch adding component factory * Create relevant fake component factories * Add window size monitor into factory * Fake size monitor injecting * Add helper for faking console resize in AutoInitShutdown tests * Fix size setting in FakeDriverV2 * Switch to new method * Fix IsLegacy becoming false when using blank constructor * Fix for Ready not being raised when showing same top twice also fixes garbage collection issue if running millions of top levels * Fix tests * Remove auto init * Restore conditional compilation stuff * Restore 'if running unit tests' logic * Check only for the output being specific classes for the suppression * Fix ShadowView blowing up with index out of bounds error * Fix resize in fluent tests * Fix for people using Iteration call directly * Fix more calls to iteration to use AutoInitShutdownAttribute.RunIteration (); * Add comment * Remove assumption that Run with prior view not disposed should throw * Fix timings in Dialog_Opened_From_Another_Dialog * Fix Zero_Buttons_Works * Standardize and fix Button_IsDefault_True_Return_His_Index_On_Accepting * Fix iteration counts on MessageBoxTests * Fix WizartTests and DrawTests_Ruler * Implement SendKeys into ConsoleDriverFacade * Fix SendKeys in console driver facade such that FileDialogTests works Fix when Clip is null in popover * Add missing dispose call to test * Fix support for Esc in facade SendKeys * Fix AutocompleteTests * Fix various tests * Replace LayoutAndDraw with run iteration * Fix draw issues * fix draw order * Fix run iteration calls * Fix unit tests * Fix SendKeys in facade. * Manipulate upper and lower cases. * Add IsValidInput method to the interface. * Fix SendKeys scenario * Fixes surrogate pairs in the label * Make tests more sensible - they are testing draw functionality. Callbacks do not need to happen in Iteration method * Fix tests and harden cleanup in AutoInitShutdownAttribute v2 lifecycle dispose * Delete extra create input call * Fix mocks and order of exceptions thrown in Run when things are not initialized * Revert use of `MapConsoleKeyInfoToKeyCode` * Ignore casing as it is not what test is really about * Clear application top and top levels before each auto init shutdown test * Fix for unstable tests * Restore actually working SendKeys code * option to pass logger in fluent ctor * restore ToArray * Fix SendKeys method and add extension to unit test * Leverage the EscSeqUtils.MapConsoleKeyInfo method to avoid duplicate code * Remove unnecessary hack * Using only KeyCode for rKeys * Recover modifier keys in surrogate pairs * Reformat * Remove iteration limit for benchmarking in v2 * remove iteration delay to identify bugs * Remove nudge to unique key and make Then run on UI thread * fix fluid assertions * Ensure UI operations all happen on UI thread * Add explicit error for WaitIteration during an invoke * Remove timeout added for debug * Catch failing asserts better * Fix screenshot * Fix null ref * Fix race condition in processing input * Test fixing * Standardize asserts * Remove calls to layout and draw, remove pointless lock and enable reading Cancelled from Dialog even if it is disposed * fix bad merge * Make logs access threadsafe * add extra wait to remove race between iteration end and assert * Code cleanup * Remove test for crash on access Cancelled after dispose as this is no longer a restriction * Change resize console to run on UI thread - fixing race condition with redrawing * Restore original frame rate after test * Restore nudge to unique key * Code Cleanup * Fix for cascading failures when an assert fails in a specific test * fix for bad merge * Address PR feedback * Move classes to seperate files and add xmldoc * xml doc warnings * More xml comments docs * Fix spelling --------- Co-authored-by: BDisp <bd.bdisp@gmail.com>
1 parent 00aaefb commit 51dda7e

File tree

85 files changed

+1787
-1134
lines changed

Some content is hidden

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

85 files changed

+1787
-1134
lines changed

Examples/UICatalog/Scenarios/SendKeys.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System.Text;
22

33
namespace UICatalog.Scenarios;
44

@@ -39,7 +39,7 @@ public override void Main ()
3939

4040
txtResult.KeyDown += (s, e) =>
4141
{
42-
rKeys += (char)e.KeyCode;
42+
rKeys += e.ToString ();
4343

4444
if (!IsShift && e.IsShift)
4545
{
@@ -81,17 +81,15 @@ void ProcessInput ()
8181

8282
foreach (char r in txtInput.Text)
8383
{
84-
ConsoleKey ck = char.IsLetter (r)
85-
? (ConsoleKey)char.ToUpper (r)
86-
: (ConsoleKey)r;
84+
ConsoleKeyInfo consoleKeyInfo = EscSeqUtils.MapConsoleKeyInfo (new (r, ConsoleKey.None, false, false, false));
8785

8886
Application.Driver?.SendKeys (
89-
r,
90-
ck,
91-
ckbShift.CheckedState == CheckState.Checked,
92-
ckbAlt.CheckedState == CheckState.Checked,
93-
ckbControl.CheckedState == CheckState.Checked
94-
);
87+
r,
88+
consoleKeyInfo.Key,
89+
ckbShift.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Shift) != 0,
90+
ckbAlt.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Alt) != 0,
91+
ckbControl.CheckedState == CheckState.Checked || (consoleKeyInfo.Modifiers & ConsoleModifiers.Control) != 0
92+
);
9593
}
9694

9795
lblShippedKeys.Text = rKeys;

Terminal.Gui/App/Application.Run.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ internal static void LayoutAndDrawImpl (bool forceDraw = false)
460460
/// <summary>This event is raised on each iteration of the main loop.</summary>
461461
/// <remarks>See also <see cref="Timeout"/></remarks>
462462
public static event EventHandler<IterationEventArgs>? Iteration;
463-
463+
464464
/// <summary>The <see cref="MainLoop"/> driver for the application</summary>
465465
/// <value>The main loop.</value>
466466
internal static MainLoop? MainLoop { get; set; }
@@ -618,4 +618,8 @@ public static void End (RunState runState)
618618

619619
LayoutAndDraw (true);
620620
}
621+
internal static void RaiseIteration ()
622+
{
623+
Iteration?.Invoke (null, new ());
624+
}
621625
}

Terminal.Gui/App/Application.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ public static partial class Application
5151
/// </summary>
5252
public static ITimedEvents? TimedEvents => ApplicationImpl.Instance?.TimedEvents;
5353

54+
/// <summary>
55+
/// Maximum number of iterations of the main loop (and hence draws)
56+
/// to allow to occur per second. Defaults to <see cref="DefaultMaximumIterationsPerSecond"/>> which is a 40ms sleep
57+
/// after iteration (factoring in how long iteration took to run).
58+
/// <remarks>Note that not every iteration draws (see <see cref="View.NeedsDraw"/>).
59+
/// Only affects v2 drivers.</remarks>
60+
/// </summary>
61+
public static ushort MaximumIterationsPerSecond = DefaultMaximumIterationsPerSecond;
62+
63+
/// <summary>
64+
/// Default value for <see cref="MaximumIterationsPerSecond"/>
65+
/// </summary>
66+
public const ushort DefaultMaximumIterationsPerSecond = 25;
67+
5468
/// <summary>
5569
/// Gets a string representation of the Application as rendered by <see cref="Driver"/>.
5670
/// </summary>

Terminal.Gui/Drivers/V2/ApplicationV2.cs

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable enable
22
using System.Collections.Concurrent;
3+
using System.ComponentModel;
34
using System.Diagnostics;
45
using System.Diagnostics.CodeAnalysis;
56
using Microsoft.Extensions.Logging;
@@ -12,10 +13,7 @@ namespace Terminal.Gui.Drivers;
1213
/// </summary>
1314
public class ApplicationV2 : ApplicationImpl
1415
{
15-
private readonly Func<INetInput> _netInputFactory;
16-
private readonly Func<IConsoleOutput> _netOutputFactory;
17-
private readonly Func<IWindowsInput> _winInputFactory;
18-
private readonly Func<IConsoleOutput> _winOutputFactory;
16+
private readonly IComponentFactory? _componentFactory;
1917
private IMainLoopCoordinator? _coordinator;
2018
private string? _driverName;
2119

@@ -24,29 +22,20 @@ public class ApplicationV2 : ApplicationImpl
2422
/// <inheritdoc/>
2523
public override ITimedEvents TimedEvents => _timedEvents;
2624

25+
internal IMainLoopCoordinator? Coordinator => _coordinator;
26+
2727
/// <summary>
2828
/// Creates anew instance of the Application backend. The provided
2929
/// factory methods will be used on Init calls to get things booted.
3030
/// </summary>
31-
public ApplicationV2 () : this (
32-
() => new NetInput (),
33-
() => new NetOutput (),
34-
() => new WindowsInput (),
35-
() => new WindowsOutput ()
36-
)
37-
{ }
38-
39-
internal ApplicationV2 (
40-
Func<INetInput> netInputFactory,
41-
Func<IConsoleOutput> netOutputFactory,
42-
Func<IWindowsInput> winInputFactory,
43-
Func<IConsoleOutput> winOutputFactory
44-
)
31+
public ApplicationV2 ()
32+
{
33+
IsLegacy = false;
34+
}
35+
36+
internal ApplicationV2 (IComponentFactory componentFactory)
4537
{
46-
_netInputFactory = netInputFactory;
47-
_netOutputFactory = netOutputFactory;
48-
_winInputFactory = winInputFactory;
49-
_winOutputFactory = winOutputFactory;
38+
_componentFactory = componentFactory;
5039
IsLegacy = false;
5140
}
5241

@@ -92,8 +81,8 @@ private void CreateDriver (string? driverName)
9281
{
9382
PlatformID p = Environment.OSVersion.Platform;
9483

95-
bool definetlyWin = driverName?.Contains ("win") ?? false;
96-
bool definetlyNet = driverName?.Contains ("net") ?? false;
84+
bool definetlyWin = (driverName?.Contains ("win") ?? false )|| _componentFactory is IComponentFactory<WindowsConsole.InputRecord>;
85+
bool definetlyNet = (driverName?.Contains ("net") ?? false ) || _componentFactory is IComponentFactory<ConsoleKeyInfo>;
9786

9887
if (definetlyWin)
9988
{
@@ -125,27 +114,44 @@ private IMainLoopCoordinator CreateWindowsSubcomponents ()
125114
ConcurrentQueue<WindowsConsole.InputRecord> inputBuffer = new ();
126115
MainLoop<WindowsConsole.InputRecord> loop = new ();
127116

128-
return new MainLoopCoordinator<WindowsConsole.InputRecord> (
129-
_timedEvents,
130-
_winInputFactory,
117+
IComponentFactory<WindowsConsole.InputRecord> cf;
118+
119+
if (_componentFactory != null)
120+
{
121+
cf = (IComponentFactory<WindowsConsole.InputRecord>)_componentFactory;
122+
}
123+
else
124+
{
125+
cf = new WindowsComponentFactory ();
126+
}
127+
128+
return new MainLoopCoordinator<WindowsConsole.InputRecord> (_timedEvents,
131129
inputBuffer,
132-
new WindowsInputProcessor (inputBuffer),
133-
_winOutputFactory,
134-
loop);
130+
loop,
131+
cf);
135132
}
136133

137134
private IMainLoopCoordinator CreateNetSubcomponents ()
138135
{
139136
ConcurrentQueue<ConsoleKeyInfo> inputBuffer = new ();
140137
MainLoop<ConsoleKeyInfo> loop = new ();
141138

139+
IComponentFactory<ConsoleKeyInfo> cf;
140+
141+
if (_componentFactory != null)
142+
{
143+
cf = (IComponentFactory<ConsoleKeyInfo>)_componentFactory;
144+
}
145+
else
146+
{
147+
cf = new NetComponentFactory ();
148+
}
149+
142150
return new MainLoopCoordinator<ConsoleKeyInfo> (
143151
_timedEvents,
144-
_netInputFactory,
145152
inputBuffer,
146-
new NetInputProcessor (inputBuffer),
147-
_netOutputFactory,
148-
loop);
153+
loop,
154+
cf);
149155
}
150156

151157
/// <inheritdoc/>
@@ -171,6 +177,12 @@ public override void Run (Toplevel view, Func<Exception, bool>? errorHandler = n
171177
throw new NotInitializedException (nameof (Run));
172178
}
173179

180+
if (Application.Driver == null)
181+
{
182+
// See Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws
183+
throw new InvalidOperationException ("Driver was inexplicably null when trying to Run view");
184+
}
185+
174186
Application.Top = view;
175187

176188
RunState rs = Application.Begin (view);
@@ -258,4 +270,4 @@ public override void LayoutAndDraw (bool forceDraw)
258270
Application.Top?.SetNeedsDraw();
259271
Application.Top?.SetNeedsLayout ();
260272
}
261-
}
273+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#nullable enable
2+
using System.Collections.Concurrent;
3+
4+
namespace Terminal.Gui.Drivers;
5+
6+
/// <summary>
7+
/// Abstract base class implementation of <see cref="IComponentFactory{T}"/>
8+
/// </summary>
9+
/// <typeparam name="T"></typeparam>
10+
public abstract class ComponentFactory<T> : IComponentFactory<T>
11+
{
12+
/// <inheritdoc />
13+
public abstract IConsoleInput<T> CreateInput ();
14+
15+
/// <inheritdoc />
16+
public abstract IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
17+
18+
/// <inheritdoc />
19+
public virtual IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer)
20+
{
21+
return new WindowSizeMonitor (consoleOutput, outputBuffer);
22+
}
23+
24+
/// <inheritdoc />
25+
public abstract IConsoleOutput CreateOutput ();
26+
}

Terminal.Gui/Drivers/V2/ConsoleDriverFacade.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ internal class ConsoleDriverFacade<T> : IConsoleDriver, IConsoleDriverFacade
1414
public event EventHandler<SizeChangedEventArgs> SizeChanged;
1515

1616
public IInputProcessor InputProcessor { get; }
17+
public IOutputBuffer OutputBuffer => _outputBuffer;
18+
19+
public IWindowSizeMonitor WindowSizeMonitor { get; }
20+
1721

1822
public ConsoleDriverFacade (
1923
IInputProcessor inputProcessor,
@@ -36,7 +40,8 @@ IWindowSizeMonitor windowSizeMonitor
3640
MouseEvent?.Invoke (s, e);
3741
};
3842

39-
windowSizeMonitor.SizeChanging += (_, e) => SizeChanged?.Invoke (this, e);
43+
WindowSizeMonitor = windowSizeMonitor;
44+
windowSizeMonitor.SizeChanging += (_,e) => SizeChanged?.Invoke (this, e);
4045

4146
CreateClipboard ();
4247
}
@@ -68,7 +73,7 @@ public Rectangle Screen
6873
{
6974
get
7075
{
71-
if (ConsoleDriver.RunningUnitTests)
76+
if (ConsoleDriver.RunningUnitTests && _output is WindowsOutput or NetOutput)
7277
{
7378
// In unit tests, we don't have a real output, so we return an empty rectangle.
7479
return Rectangle.Empty;
@@ -384,7 +389,15 @@ public Attribute MakeColor (in Color foreground, in Color background)
384389
/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
385390
public void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl)
386391
{
387-
// TODO: implement
392+
ConsoleKeyInfo consoleKeyInfo = new (keyChar, key, shift, alt, ctrl);
393+
394+
Key k = EscSeqUtils.MapKey (consoleKeyInfo);
395+
396+
if (InputProcessor.IsValidInput (k, out k))
397+
{
398+
InputProcessor.OnKeyDown (k);
399+
InputProcessor.OnKeyUp (k);
400+
}
388401
}
389402

390403
/// <summary>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#nullable enable
2+
using System.Collections.Concurrent;
3+
4+
namespace Terminal.Gui.Drivers;
5+
6+
/// <summary>
7+
/// Base untyped interface for <see cref="IComponentFactory{T}"/> for methods that are not templated on low level
8+
/// console input type.
9+
/// </summary>
10+
public interface IComponentFactory
11+
{
12+
/// <summary>
13+
/// Create the <see cref="IConsoleOutput"/> class for the current driver implementation i.e. the class responsible for
14+
/// rendering <see cref="IOutputBuffer"/> into the console.
15+
/// </summary>
16+
/// <returns></returns>
17+
IConsoleOutput CreateOutput ();
18+
}
19+
20+
/// <summary>
21+
/// Creates driver specific subcomponent classes (<see cref="IConsoleInput{T}"/>, <see cref="IInputProcessor"/> etc) for a
22+
/// <see cref="IMainLoopCoordinator"/>.
23+
/// </summary>
24+
/// <typeparam name="T"></typeparam>
25+
public interface IComponentFactory<T> : IComponentFactory
26+
{
27+
/// <summary>
28+
/// Create <see cref="IConsoleInput{T}"/> class for the current driver implementation i.e. the class responsible for reading
29+
/// user input from the console.
30+
/// </summary>
31+
/// <returns></returns>
32+
IConsoleInput<T> CreateInput ();
33+
34+
/// <summary>
35+
/// Creates the <see cref="InputProcessor{T}"/> class for the current driver implementation i.e. the class responsible for
36+
/// translating raw console input into Terminal.Gui common event <see cref="Key"/> and <see cref="MouseEventArgs"/>.
37+
/// </summary>
38+
/// <param name="inputBuffer"></param>
39+
/// <returns></returns>
40+
IInputProcessor CreateInputProcessor (ConcurrentQueue<T> inputBuffer);
41+
42+
/// <summary>
43+
/// Creates <see cref="IWindowSizeMonitor"/> class for the current driver implementation i.e. the class responsible for
44+
/// reporting the current size of the terminal window.
45+
/// </summary>
46+
/// <param name="consoleOutput"></param>
47+
/// <param name="outputBuffer"></param>
48+
/// <returns></returns>
49+
IWindowSizeMonitor CreateWindowSizeMonitor (IConsoleOutput consoleOutput, IOutputBuffer outputBuffer);
50+
}

Terminal.Gui/Drivers/V2/IConsoleDriverFacade.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,16 @@ public interface IConsoleDriverFacade
1010
/// e.g. <see cref="ConsoleKeyInfo"/> into <see cref="Key"/> events
1111
/// and detecting and processing ansi escape sequences.
1212
/// </summary>
13-
public IInputProcessor InputProcessor { get; }
13+
IInputProcessor InputProcessor { get; }
14+
15+
/// <summary>
16+
/// Describes the desired screen state. Data source for <see cref="IConsoleOutput"/>.
17+
/// </summary>
18+
IOutputBuffer OutputBuffer { get; }
19+
20+
/// <summary>
21+
/// Interface for classes responsible for reporting the current
22+
/// size of the terminal window.
23+
/// </summary>
24+
IWindowSizeMonitor WindowSizeMonitor { get; }
1425
}

Terminal.Gui/Drivers/V2/IInputProcessor.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,15 @@ public interface IInputProcessor
5858
/// </summary>
5959
/// <returns></returns>
6060
public IAnsiResponseParser GetParser ();
61+
62+
/// <summary>
63+
/// Handles surrogate pairs in the input stream.
64+
/// </summary>
65+
/// <param name="key">The key from input.</param>
66+
/// <param name="result">Get the surrogate pair or the key.</param>
67+
/// <returns>
68+
/// <see langword="true"/> if the result is a valid surrogate pair or a valid key, otherwise
69+
/// <see langword="false"/>.
70+
/// </returns>
71+
bool IsValidInput (Key key, out Key result);
6172
}

Terminal.Gui/Drivers/V2/IMainLoop.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,14 @@ public interface IMainLoop<T> : IDisposable
4848
/// <param name="inputBuffer"></param>
4949
/// <param name="inputProcessor"></param>
5050
/// <param name="consoleOutput"></param>
51-
void Initialize (ITimedEvents timedEvents, ConcurrentQueue<T> inputBuffer, IInputProcessor inputProcessor, IConsoleOutput consoleOutput);
51+
/// <param name="componentFactory"></param>
52+
void Initialize (
53+
ITimedEvents timedEvents,
54+
ConcurrentQueue<T> inputBuffer,
55+
IInputProcessor inputProcessor,
56+
IConsoleOutput consoleOutput,
57+
IComponentFactory<T> componentFactory
58+
);
5259

5360
/// <summary>
5461
/// Perform a single iteration of the main loop then blocks for a fixed length

0 commit comments

Comments
 (0)