mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Fixes #3692++ - Rearchitects drivers (#3837)
This commit is contained in:
50
docfx/docs/ansiparser.md
Normal file
50
docfx/docs/ansiparser.md
Normal 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
123
docfx/docs/logging.md
Normal 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)
|
||||
Reference in New Issue
Block a user