ansi-terminal icon indicating copy to clipboard operation
ansi-terminal copied to clipboard

Surprising behavior of getTerminalSize

Open Bodigrim opened this issue 4 months ago • 9 comments

The documentation for getTerminalSize says https://github.com/UnkindPartition/ansi-terminal/blob/16d8d3bfa5a0b4473f0968371a5f32a93f5f3f62/ansi-terminal/src/System/Console/ANSI.hs#L957-L961

My understanding of this was that if the preconditions do not hold (either terminal does not support cursor movement or stdin is not available), getTerminalSize :: IO (Maybe (Int, Int)) will return Nothing. I was surprised to learn that this is not the case.

Namely, running main = getTerminalSize >>= print with redirected stdin fails with an IO exception:

$ ./GetTerminalSize.hs < /dev/null
GetTerminalSize.hs: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.IOException:

<stdin>: hWaitForInput: end of file

HasCallStack backtrace:
  ioException, called at libraries/ghc-internal/src/GHC/Internal/IO/Handle/Internals.hs:353:11 in ghc-internal:GHC.Internal.IO.Handle.Internals

Is it intended? Not the end of the world, but I feel like getTerminalSize could have caught the exception and return Nothing instead of passing the bucket to a caller.

Bodigrim avatar Aug 01 '25 21:08 Bodigrim

Short of catching IO exceptions, getTerminalSize could have checked hIsTerminalDevice stdin. One can argue that other functions in this library do not check hIsTerminalDevice stdout, leaving it to a caller, so checking stdin is also caller's resposibility. Which is not unreasonable, but emitting ANSI codes to a non-terminal stdout does not throw an exception, while getTerminalSize (if preconditions do not hold) does.

Bodigrim avatar Aug 02 '25 09:08 Bodigrim

My understanding of this was that if the preconditions do not hold (either terminal does not support cursor movement or stdin is not available), getTerminalSize :: IO (Maybe (Int, Int)) will return Nothing. I was surprised to learn that this is not the case.

The document explicitly says

Use 'System.IO.hIsTerminalDevice' to test if 'stdin' is connected to a terminal.

So from what I understand this behaves exactly as documented.

@Bodigrim am I missing something?

sol avatar Aug 04 '25 09:08 sol

So from what I understand this behaves exactly as documented.

I would expect getTerminalSize to return Nothing if preconditions are not satisfied instead of throwing an IO exception. This is what is not clear from the documentation.

Also I argue in https://github.com/UnkindPartition/ansi-terminal/issues/178#issuecomment-3146379856 that getTerminalSize should actually check isTerminalDevice stdin itself. It's a cheap thing to do and defuses a foot gun. A user might have never tested their program with stdin redirected, so the IO expection can be discovered only after a release.

Bodigrim avatar Aug 04 '25 19:08 Bodigrim

I need to look at getTerminalSize more closely, full stop, as something is up with ansi-terminal-example on Windows 11, namely:

  • #182

mpilgrem avatar Aug 04 '25 21:08 mpilgrem

With #182 now fixed:

@Bodigrim, the original intent was the user would have tested stdin themselves, because they would want to do that only once, not every time they used getTerminalSize. I am sorry if the combination of the type signature and the Haddock documentation was ambiguous. I can fix the documentation. The final line:

For a different approach, one that does not use control character sequences and works when 'stdin' is redirected, see the terminal-size package.

was meant as a contrast.

mpilgrem avatar Aug 04 '25 23:08 mpilgrem

@Bodigrim, the original intent was the user would have tested stdin themselves, because they would want to do that only once, not every time they used getTerminalSize.

Checking isTerminalDevice stdin is cheap, most likely way faster than anything else getTerminalSize does; and I hardly imagine a user running getTerminalSize in a hot loop, so from my perspective convenience of eliminating a source of IO exceptions beats raw performance in this case. But fair enough, a documentation clarification might be helpful for future users.

Bodigrim avatar Aug 04 '25 23:08 Bodigrim

Related issues/pull requests:

  • #124
  • #140 (by @Bodigrim !)

mpilgrem avatar Aug 05 '25 07:08 mpilgrem

Wow, I have absolutely no recollection of it! %)

Bodigrim avatar Aug 17 '25 17:08 Bodigrim

@mpilgrem did you come to any conclusion? Now that it's documented I'm easy both ways, just would appreciate to have a decision.

Bodigrim avatar Aug 31 '25 23:08 Bodigrim