WinIO `getChar` `NoBuffering` v buffering, inconsistency in key press interpretation (Windows Terminal)
Summary
I am not sure where in the Haskell ecosystem this problem falls. I am hoping that somebody like @Phyx can help, if only to point me in the right direction to help me help myself.
Using WinIO (ghc-options: -with-rtsopts=--io-manager=native
, with GHC 9.0.2, on Windows 11 with Windows Terminal), the basic problem is inconsistent behaviour of getChar
if NoBuffering
is set, in terms of interpreting what has been emitted to the console standard input. See under 'steps to reproduce' for the details.
Steps to reproduce
By way of background, if the 'ANSI' code \ESC[6n
('report cursor position') is sent to, and recognised on, console output, a report like \ESC[55;1R
is then emitted by the terminal software to the console input stream (where 55 is the cursor position row and 1 is the cursor position column, in this example). On Unix-like operating systems, package ansi-terminal
can recover that information from the input stream by turning off buffering and then using getChar
. On Windows, historically, that has not worked, because of #806 (closed). The workaround has been to use the Windows Console API to access directly, and interpret, the stream of 'key press' events put into the console's input buffer.
WinIO has fixed that GHC issue 806, so I have been experimenting with this simple code:
import Control.Monad (replicateM_)
import System.IO (BufferMode (..), hSetBuffering, stdin)
import Text.Printf (printf)
import System.Console.ANSI.Codes (reportCursorPositionCode) -- from package ansi-terminal
main :: IO ()
main = do
putStrLn reportCursorPositionCode
hSetBuffering stdin NoBuffering -- <- turn off buffering
replicateM_ 8 $ do
c <- getChar
printf "%s %x; " (show c) c
putStr "\n"
If you do not turn off buffering, then (once the user has pressed the ENTER key to cause the input stream to be processed), the output is something like this (as expected). Each character in the input stream is recognised as such. The first line is the echoing of the input stream to the output stream:
^[[55;1R
'\ESC' 1b; '[' 5b; '5' 35; '5' 35; ';' 3b; '1' 31; 'R' 52; '\n' a;
However, if you do turn off buffering (as in the code sample above), then (after waiting for a couple of key presses from the user - say ENTER) the output is something like this:
'\23323' 5b1b; '\13621' 3535; '\12603' 313b; 'R' 52; '\NUL' 0; '\NUL' 0; '\NUL' 0; '\r' d;
It looks to me that the 'key presses' that have been emitted to the input stream by the terminal are now being interpreted as 16 bit words. The 1b
5b
has been understood to be a single character 5b1b
, the 35
35
as 3535
, the 3b
31
as 313b
, and so on.
I am assuming the problem must fall in the Haskell ecosystem, because I am assuming that Windows Terminal is emitting the same information to the console's input stream in both cases.
Expected behavior
I don't expect the BufferingMode
of stdin
to change how getChar
interprets 'key press' events in the console's input stream. I was hoping that with GHC issue 806 fixed, getChar
would behave on Windows like it does on Unix-like operating systems.
Environment
- GHC 9.0.2 (I am using
stack
with resolverlts-19.6
) - Windows 11 (Microsoft Windows [Version 10.0.22000.613])
- 64 bit
- Windows Terminal 1.12.10983.0