Bug 2/30: Dotnet CLI ANSI Escape Codes
Bug 2 is dotnet/command-line-api/Incorrect ANSI escape codes. The last one was embarrassingly easy, but this one is more like real work. I decided that having had one success on this codebase I may as well see if there was anything else I could help to fix.
The Windows terminal (or as Microsoft call it the
Command Prompt, accessed in
System.Console) is one of those things that most sensible programmers try to avoid. Unlike Unix-based systems, where the terminal is a core part of the operating system that users often need to interact with, the Windows terminal is a clunky old DOS throwback that can even have good programmers scratching their heads (think
.bat files). However, in recent years there has been quite a resurgence in the command line application and the terminal is probably getting more use than ever.
I don’t want to get sidetracked into terminals, although it is worth saying that the traditional
Command Prompt that has been around since forever has pretty much been replaced in practical usage by PowerShell; which is now (as of 7.x) pretty good, cross platform, and a dream to integrate with.
This bug concerns the escape sequences that are used to move the cursor in a terminal. The bug states that some of escape sequences in the API do not match the ANSI specification.
Fixing the Bug
The main challenge here is that the code that is supposedly not working has no example usage in the sample code and is undocumented (so business as usual then).
The solution does contain a number of sample applications and one of these is called
RenderingPlayground, which has a number of examples of using the API to render fancy things to the terminal. I decided that a good first step would be to try to add a new example to this application that used this part of the API, as without a concrete example of the API in use it would be impossible to determine whether the current API worked and whether any changes that I made resolved any issues that I found. Doing this would also help other programmers see how to use the API, so providing a level of documentation that didn’t previously exist. A programmer should always endeavour to make life easier for other programmers when possible.
When fixing a bug my first instinct is to stay as close to the bug’s remit as possible and avoid mission creep. However, when I come across a bit of code that has clearly never been tested I generally widen the remit somewhat to do as much due diligence on it as I can to ensure that it is fit for purpose. I felt that this was not code that could realistically be tested by asserting against a console instance itself. I never say never, but currently this kind of infrastructure is not available in this project and it would require changes to the CI pipeline to support this type of testing. So in my opinion the best testing that I could deliver would be to provide sample code that demonstrated all of the functionality in this part of the API and then to add unit tests that locked the ANSI escape sequences into the test suite.
After a bit of tinkering the example that I added to the existing sample application looked something like this:
Having done this I found that most of the control sequences worked as expected:
- Two errors that had been reported were in fact reported erroneously; the existing implementation was correct;
- One that had been reported was indeed incorrect and the fix provided was correct and;
- Two that had been reported were incorrect and the fix provided was incorrect.
I’ve now made what I believe are the correct modifications to the API, which can be seen working in the solution itself, and have added unit tests to assert this behaviour.
This was a fun bug to work on as it is a bit off the beaten track and not entirely straightforward. I learned a few things about the terminal that I didn’t know before. Actually I think this is the first time I’ve ever built a console based UI (why would you right?!).
The PR can be found here.
Boom! The PR got accepted first time. Considering the size of this commit I’m pretty happy with that.
Two down, 28 to go 😄
Yes, it seems a bit strange that what you need to write to is the output stream, but it makes sense if you think about it. The user’s input arrives on the input stream and the output stream is what gets rendered into the terminal; the terminal reads and interprets the output, not the input! ↩︎