-
Notifications
You must be signed in to change notification settings - Fork 689
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #3767. Allowing any driver to request ANSI escape sequence with immediate response. #3768
base: v2_develop
Are you sure you want to change the base?
Fixes #3767. Allowing any driver to request ANSI escape sequence with immediate response. #3768
Conversation
To me his feels really brittle. Its what I was trying to explain in other thread. I can see its possible just dangerous:
I still think we should update the existing input processing loop(s) e.g. I think we could make a blocking public facing method even with that approach. Just need to think about it some more. Maybe we can use async and user can just call |
This is a minor problem because we are not requesting ANSI escape sequences all the time, like the one that are setting the colors when are writing to the console.
My bad, sorry. I created the
Done. Thanks.
You are right. Thanks for call my attention. I added a flag into the
But the immediate reply will still not prevail. When I found the current solution I never like how it's working because in some situations I wanted the response in the request method, to do some actions that depended on the response, but without success. I found it useless.
I think it won't block because I first stopped the mouse move reports which will decrease significantly the number of chars to read. You can use your approach as well because my PR doesn't break it, only changed from string to the class I created but still use the same behavior. The only disadvantage is that your approach only work with the If you want to test put these line in the var response1 = AnsiEscapeSequenceRequest.ExecuteAnsiRequest (EscSeqUtils.CSI_SendDeviceAttributes);
var response2 = AnsiEscapeSequenceRequest.ExecuteAnsiRequest (EscSeqUtils.CSI_RequestCursorPositionReport);
var request = EscSeqUtils.CSI_RequestCursorPositionReport;
AnsiEscapeSequenceResponse response3 = null;
request.ResponseReceived += (s, e) => response3 = e;
var response4 = AnsiEscapeSequenceRequest.ExecuteAnsiRequest (request);
Debug.Assert (response3 == response4); |
This work by @BDisp enables us to detect sixel support on demand
What is Value? I cant understand the comment. My example has 61...
Can we also please have a helper property called |
I can confirm that this works and have added it into a new branch of the main sixel branch I am working on. See tznind#169 |
Value is the number that appear at the beginning of the reply after the CSI. See https://terminalguide.namepad.de/seq/csi_st-18/ and https://terminalguide.namepad.de/seq/csi_st-15/. Both requests use the 't' as terminator and the reply also returns 't' as terminator. What distinguish each other are the Value. The prior returns 8 and the last returns 5.
It's not enough to check the error with |
Yes please either add I started with this ugliness: if (string.IsNullOrWhiteSpace (darResponse.Error) && !string.IsNullOrWhiteSpace (darResponse.Response))
{
// it worked
}
|
@tig please can you take a look at this when you get the chance? Currently I am working on 2 sixel branches (one with this merged in). If its going in right direction I will just collapse and work on only 1 branch. |
Cool thanks, although I meant for tryparse as an example of the pattern only. It should probably still be same name e.g. TryExecuteAnsi... |
Ops I misunderstood. Changed on 31dbae7. |
Got a strange error in
See error
|
I can't reproduce the error as you said but I can reproduce it by running the |
I have a fix to this. But I need your opinion, if the request terminator is empty and the response is successfully, it's need to write to the error property "Terminator request is empty." or ignore it? If I write to the error, the response will not return |
Terminator request is mandatory to stopping read when it's found. So, it'll return false and the user will check what he does wrong. |
I think maybe we are terminating our ASCII processing on the 'key down' of the response terminator and the 'key up' is still coming through from driver? Could be an oddity of the console, this is the 2022 community edition visual studio out of the box console. |
Do you are using the updated branch after the latest fixes? When I said the crash is due the lack of terminator, I referred the one that is on the request and not in the response.
What causes this exception is because the key read is a 'c' that the
It's a possibility.
It's a nor normal behavior of the |
This code is already quite complicated and coupled to the specific drivers e.g. switch statement on Driver Type. Maybe we can reuse the existing message pumps that the drivers are already running and move the new code to a Parser pattern? Each driver would then have its own pump but reuse something like this. We wouldn't stop the pumps we would just add an extra 'filtering' stage to the existing input processing pipeline? I can have a go at this unless you can see reasons why this won't work and better decouple concerns? /// <summary>
/// Driver agnostic parser that you can stream console output to as part of normal
/// input processing. Spots ansi escape sequenes as they pass through stream and
/// matches them to the current outstanding <see cref="AnsiEscapeSequenceRequest"/>
/// </summary>
public class AnsiRequestParser ()
{
[CanBeNull]
private AnsiEscapeSequenceRequest currentRequest;
StringBuilder currentResponse = new StringBuilder ();
private bool areInEscapeSequence = false;
Queue<AnsiEscapeSequenceRequest> outstandingRequests = new Queue<AnsiEscapeSequenceRequest> ();
/// <summary>
/// Sends the given <paramref name="request"/> to the terminal or queues
/// it if there is already an outstanding request.
/// </summary>
/// <param name="request"></param>
public void QueueRequest (AnsiEscapeSequenceRequest request)
{
if (currentRequest == null)
{
SendRequest (request);
}
else
{
outstandingRequests.Enqueue (request);
}
}
private void SendRequest (AnsiEscapeSequenceRequest request)
{
currentRequest = request;
Console.Write ("... the request");
}
/// <summary>
/// Takes the given key read from console input stream. Returns true
/// if input should be considered 'handled' (is part of an ongoing
/// ANSI response code)
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool Process (char key)
{
// Existing code in this PR for parsing
// If completes request
currentRequest = null;
// send next from queue
return true;
// We have not read an Esc so we are not in a response
return false;
}
} |
Have tried and get no difference in behaviour. I think you are right and that it is the mixing of Console
Not sure... this is why I was wanting to go for the parser approach as it is much easier to test. And each driver can concern itself with its own message pump oddities. I guess an alternative would be to try and add Read and Write methods to |
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
Outdated
Show resolved
Hide resolved
9366998
to
803dbef
Compare
Get an issue running this code with the new Windows Terminal Preview (the one that has sixel support). Seems the response you read is just full of I have tested in both 1.22.2702.0 and 1.22.2362.0 |
In which driver? I think I know why with my last refactor. |
There is no relationship between both. Only we have an idea for later the user choice one of them to send requests.
That I know of, there is no dependencies on each other, but if one is merged first the other will have much work to do to implement and adapt each other. Mine have much more driver changes which will I think will help some iterations. |
These are basically 2 ways of solving the same problem
The first is harder to use for api of our library because it is based on events. The second is blocking so user gets answer right away but is imo harder to maintain/test and may be less stable. @BDisp please correct me if I misunderstood your implementation. We could try and support both or pick one. Either way it needs a ton of testing on old terminals too because if you fumble an escape code and it ends up going through input loop as a keypress then users view just closes and app exits. Or escape codes start going into text boxes etc Also needs tested with things like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR with these comments and a bunch of code clean up inbound.
Please look for new // TODO: and // QUESTION: in the code.
This is really, really, really great stuff!!!
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
Outdated
Show resolved
Hide resolved
Terminal.Gui/ConsoleDrivers/AnsiEscapeSequence/AnsiEscapeSequenceRequest.cs
Show resolved
Hide resolved
/// </summary> | ||
/// <param name="ansiRequest">The <see cref="AnsiEscapeSequenceRequest"/> object.</param> | ||
/// <returns>The request response.</returns> | ||
public abstract string WriteAnsiRequest (AnsiEscapeSequenceRequest ansiRequest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be virtual with a default implementation that does the common stuff?
Looking at the implementations of this method, there is TONs of duplicated code.
We should figure out how to find just the things that are unique to each driver and
create more fine-grained APIs to handle those.
Review comments above and a PR: |
This isn't now true because it doesn't force process the input loop outside of its main iteration anymore. All drivers now uses their own input loop to get the requests responses. So, any key or mouse event are always handle during the process. But initially I was using the method you described but not now.
Doesn't blocking anymore. The request is always returned, with a valid or not valid response, but always return. I admit that can have some delay when a lot of requests are constantly sent, but I think there is an reasonable response.
I already done above.
I admit that I concentrated all my unit tests to the
I agree. But I think we can use both implementations using different classes, like now, and the user instantiate the one he want, mine with event or yours with action. |
…ll-drivers Code Review of gui-cs#3768
Great. Thanks. |
This makes no sense to me. There is only one "user" of these classes: Us, the maintainers of TG. We should not have two implementations of the same thing. |
But we was think about was using a public flag or enum for the user choose the type of request. If the user choose AnsiParser then the drivers will initialize the appropriate class and the returning response. So, only TG handle that. |
|
||
_waitAnsiResponse.Set (); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is in all 3 drivers. It looks like you block 100ms to let the sent request get processed and then 'force run' the input stream for a bit, then look for a response?
Is it possible for you to explain in some pseudocode how it is working?
This PR is enormous and has so much in it that isn't related to ansi response send/capture it's hard to understand what is going on.
I've tried to standardise input processing with AnsiParser to replace NetEvents and similar code in the other drivers. But now all the drivers have huge changes in input handling its going to be difficult to retro fit.
I don't think we need 2 solutions to ansi send. I'd be happy if we just did
- get rid of all the locking instead use BlockingCollections
- standardise the parsing of request across all 3 drivers into a single class (i.e. `AnsiRequestParser).
We can still do the blocking on response but the class that identifies responses in input streams needs to be centralised into a single class
I will change by branch to draft in the meantime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is in all 3 drivers. It looks like you block 100ms to let the sent request get processed and then 'force run' the input stream for a bit, then look for a response?
Is it possible for you to explain in some pseudocode how it is working?
It's a ManualResetEventSlim like it's used in the MainLoop that wait to be signaled. It only wait until has a response or until two retries and return immediately.
This PR is enormous and has so much in it that isn't related to ansi response send/capture it's hard to understand what is going on.
I really refactored the CursesDriver
to leverage using non blocking input, like I did in the WindowsDriver
.
I've tried to standardise input processing with AnsiParser to replace NetEvents and similar code in the other drivers. But now all the drivers have huge changes in input handling its going to be difficult to retro fit.
I think that isn't very difficult to implement, beside the changes.
I don't think we need 2 solutions to ansi send. I'd be happy if we just did
- get rid of all the locking instead use BlockingCollections
I'm open on that but I need some explanation of that. I thought using the ManualResetEventSlim
was a good idea, but I would like to know about alternatives, of course.
- standardise the parsing of request across all 3 drivers into a single class (i.e. AnsiRequestParser).
The NetDriver
and the CursesDriver
relies in the EscSeqUtil.DecodeEscSeq
method because they are handling key and mouse events, but the WindowsDriver
relies on the WindowsConsole.ReadConsoleInput
because any other key that it isn't a normal key or a mouse event, is handled in this method.
We can still do the blocking on response but the class that identifies responses in input streams needs to be centralised into a single class
Well due to my above explanation NetDriver
and the CursesDriver
relies in the EscSeqUtil.DecodeEscSeq
and the WindowsDriver
relies on the WindowsConsole.ReadConsoleInput
. So, I don't know if they can be in a single class. But I understand your concern.
Why would we ask/force app developers to have to understand all this? Is there some clear use-case where developer would pick one vs. the other? |
The user may understand if he want an immediate response or a later response which can be read in another thread than the current thread. That only thoughts. |
Please provide an actual use-case, not an abstract concept. If you can't then there's no reason to provide choice. TG apps should never care about any of this stuff. It should be completely hidden from app developers. |
I'll discussed this with @tznind to see how this will be needed. For now I can't answers because we still are trying pruning this. |
Thank you very much. I did much more changes. Please say what you think about, please. |
Fixes
Proposed Changes/Todos
Pull Request checklist:
CTRL-K-D
to automatically reformat your files before committing.dotnet test
before commit///
style comments)