Fixes #3692++ - Rearchitects drivers (#3837)

This commit is contained in:
Thomas Nind
2025-02-28 19:09:29 +00:00
committed by GitHub
parent 3a240ecbe5
commit c88c772462
101 changed files with 7662 additions and 658 deletions

50
docfx/docs/ansiparser.md Normal file
View File

@@ -0,0 +1,50 @@
# AnsiResponseParser
## Background
Terminals send input to the running process as a stream of characters. In addition to regular characters ('a','b','c' etc), the terminal needs to be able to communicate more advanced concepts ('Alt+x', 'Mouse Moved', 'Terminal resized' etc). This is done through the use of 'terminal sequences'.
All terminal sequences start with Esc (`'\x1B'`) and are then followed by specific characters per the event to which they correspond. For example:
| Input Sequence | Meaning |
|----------------|-------------------------------------------|
| `<Esc>[A` | Up Arrow Key |
| `<Esc>[B` | Down Arrow Key |
| `<Esc>[5~` | Page Up Key |
| `<Esc>[6~` | Page Down Key |
Most sequences begin with what is called a `CSI` which is just `\x1B[` (`<Esc>[`). But some terminals send older sequences such as:
| Input Sequence | Meaning |
|----------------|-------------------------------------------|
| `<Esc>OR` | F3 (SS3 Pattern) |
| `<Esc>OD` | Left Arrow Key (SS3 Pattern) |
| `<Esc>g` | Alt+G (Escape as alt) |
| `<Esc>O` | Alt+Shift+O (Escape as alt)|
When using the windows driver, this is mostly already dealt with automatically by the relevant native APIs (e.g. `ReadConsoleInputW` from kernel32). In contrast the net driver operates in 'raw mode' and processes input almost exclusively using these sequences.
Regardless of the driver used, some escape codes will always be processed e.g. responses to Device Attributes Requests etc.
## Role of AnsiResponseParser
This class is responsible for filtering the input stream to distinguish between regular user key presses and terminal sequences.
## Timing
Timing is a critical component of interpreting the input stream. For example if the stream serves the escape (Esc), the parser must decide whether it's a standalone keypress or the start of a sequence. Similarly seeing the sequence `<Esc>O` could be Alt+Upper Case O, or the beginning of an SS3 pattern for F3 (were R to follow).
Because it is such a critical component, it is abstracted away from the parser itself. Instead the host class (e.g. InputProcessor) must decide when a suitable time has elapsed, after which the `Release` method should be invoked which will forcibly resolve any waiting state.
This can be controlled through the `_escTimeout` field. This approach is consistent with other terminal interfaces e.g. bash.
## State and Held
The parser has 3 states:
| State | Meaning |
|----------------|-------------------------------------------|
| `Normal` | We are encountering regular keys and letting them pass through unprocessed|
| `ExpectingEscapeSequence` | We encountered an `Esc` and are holding it to see if a sequence follows |
| `InResponse` | The `Esc` was followed by more keys that look like they will make a full terminal sequence (hold them all) |
Extensive trace logging is built into the implementation, to allow for reproducing corner cases and/or rare environments. See the logging article for more details on how to set this up.

123
docfx/docs/logging.md Normal file
View File

@@ -0,0 +1,123 @@
# Logging
Logging has come to Terminal.Gui! You can now enable comprehensive logging of the internals of the libray. This can help diagnose issues with specific terminals, keyboard cultures and/or operating system specific issues.
To enable file logging you should set the static property `Logging.Logger` to an instance of `Microsoft.Extensions.Logging.ILogger`. If your program already uses logging you can provide a shared instance or instance from Dependency Injection (DI).
Alternatively you can create a new log to ensure only Terminal.Gui logs appear.
Any logging framework will work (Serilog, NLog, Log4Net etc) but you should ensure you only log to File or UDP etc (i.e. not to console!).
## Worked example with Serilog to file
Here is an example of how to add logging of Terminal.Gui internals to your program using Serilog file log.
Add the Serilog dependencies:
```
dotnet add package Serilog
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Extensions.Logging
```
Create a static helper function to create the logger and store in `Logging.Logger`:
```csharp
Logging.Logger = CreateLogger();
static ILogger CreateLogger()
{
// Configure Serilog to write logs to a file
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose() // Verbose includes Trace and Debug
.WriteTo.File("logs/logfile.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
// Create a logger factory compatible with Microsoft.Extensions.Logging
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddSerilog(dispose: true) // Integrate Serilog with ILogger
.SetMinimumLevel(LogLevel.Trace); // Set minimum log level
});
// Get an ILogger instance
return loggerFactory.CreateLogger("Global Logger");
}
```
This will create logs in your executables directory (e.g.`\bin\Debug\net8.0`).
Example logs:
```
2025-02-15 13:36:48.635 +00:00 [INF] Main Loop Coordinator booting...
2025-02-15 13:36:48.663 +00:00 [INF] Creating NetOutput
2025-02-15 13:36:48.668 +00:00 [INF] Creating NetInput
2025-02-15 13:36:48.671 +00:00 [INF] Main Loop Coordinator booting complete
2025-02-15 13:36:49.145 +00:00 [INF] Run 'MainWindow(){X=0,Y=0,Width=0,Height=0}'
2025-02-15 13:36:49.163 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
2025-02-15 13:36:49.165 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;50;23m'
2025-02-15 13:36:49.166 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True)
2025-02-15 13:36:49.167 +00:00 [INF] Console size changes from '{Width=0, Height=0}' to {Width=120, Height=30}
2025-02-15 13:36:49.859 +00:00 [VRB] AnsiResponseParser releasing '
'
2025-02-15 13:36:49.867 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True)
2025-02-15 13:36:50.857 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True)
2025-02-15 13:36:51.417 +00:00 [VRB] MainWindow triggered redraw (NeedsDraw=True NeedsLayout=True)
2025-02-15 13:36:52.224 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;51;23m'
2025-02-15 13:36:52.226 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;52;23m'
2025-02-15 13:36:52.226 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
2025-02-15 13:36:52.226 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;53;23m'
...
2025-02-15 13:36:52.846 +00:00 [VRB] Mouse Interpreter raising ReportMousePosition
2025-02-15 13:36:52.846 +00:00 [VRB] AnsiResponseParser handled as mouse '[<35;112;4m'
2025-02-15 13:36:54.151 +00:00 [INF] RequestStop ''
2025-02-15 13:36:54.151 +00:00 [VRB] AnsiResponseParser handled as keyboard '[21~'
2025-02-15 13:36:54.225 +00:00 [INF] Input loop exited cleanly
```
## Metrics
If you are finding that the UI is slow or unresponsive - or are just interested in performance metrics. You can see these by instaling the `dotnet-counter` tool and running it for your process.
```
dotnet tool install dotnet-counters --global
```
```
dotnet-counters monitor -n YourProcessName --counters Terminal.Gui
```
Example output:
```
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[Terminal.Gui]
Drain Input (ms)
Percentile
50 0
95 0
99 0
Invokes & Timers (ms)
Percentile
50 0
95 0
99 0
Iteration (ms)
Percentile
50 0
95 1
99 1
Redraws (Count) 9
```
Metrics figures issues such as:
- Your console constantly being refreshed (Redraws)
- You are blocking main thread with long running Invokes / Timeout callbacks (Invokes & Timers)