fix: Use POSIX API to compute tty width (#2332)

On some systems, TERM is set to a value that doesn't have a valid
terminfo entry. Rather than hackily fall back on a value for TERM that
appears to work in most contexts (TERM=dumb) but which isn't guaranteed
anywhere to be valid, use proper POSIX ioctls to get the tty width.

This has the added bonus of also working on Windows.

In fact, we already settled on computing the terminal size in this way
in hledger-lib, so this commit centralizes the choice of the logic
there.

Also added a note for alternative methods and their tradeoffs, in case
this turns out to be fragile on some systems.
This commit is contained in:
gesh 2025-02-26 20:27:47 +02:00 committed by Simon Michael
parent 1ca90065db
commit a24c39f13f
3 changed files with 24 additions and 27 deletions

View File

@ -413,8 +413,30 @@ hasOutputFile = do
-- Terminal size
-- [NOTE: Alternative methods of getting the terminal size]
-- terminal-size uses the TIOCGWINSZ ioctl to get the window size on Unix
-- systems, which may not be completely portable according to people in
-- #linux@liberachat.
--
-- If this turns out to be the case, supplementary coverage can be given by
-- using the terminfo package.
--
-- Conversely, terminfo on its own is not a full solution, firstly because it
-- only works on Unix (not Windows), and secondly since in some scenarios (eg
-- stripped-down build systems) the terminfo database may be limited and lack
-- the correct entries. (A hack that sometimes works but which isn't robust
-- enough to be relied upon is to set TERM=dumb -- while this advice does appear
-- in some places, it's not guaranteed to work)
--
-- In any case, $LINES/$COLUMNS should not be used as a source for the terminal
-- size -- that is a bashism, and in particular they aren't updated as the
-- terminal is resized.
--
-- See #2332 for details
-- | An alternative to ansi-terminal's getTerminalSize, based on
-- the more robust-looking terminal-size package.
--
-- Tries to get stdout's terminal's current height and width.
getTerminalHeightWidth :: IO (Maybe (Int,Int))
getTerminalHeightWidth = fmap (fmap unwindow) size

View File

@ -107,9 +107,7 @@ import String.ANSI
import System.Console.CmdArgs hiding (Default,def)
import System.Console.CmdArgs.Explicit
import System.Console.CmdArgs.Text
#ifndef mingw32_HOST_OS
import System.Console.Terminfo
#endif
import Hledger.Utils.IO (getTerminalWidth)
import System.Directory
import System.Environment
import System.Exit (exitSuccess)
@ -618,13 +616,7 @@ rawOptsToCliOpts rawopts = do
let iopts = rawOptsToInputOpts day usecolor postingaccttags rawopts
rspec <- either error' pure $ rawOptsToReportSpec day usecolor rawopts -- PARTIAL:
mcolumns <- readMay <$> getEnvSafe "COLUMNS"
mtermwidth <-
#ifdef mingw32_HOST_OS
return Nothing
#else
(`getCapability` termColumns) <$> setupTermFromEnv
-- XXX Throws a SetupTermError if the terminfo database could not be read, should catch
#endif
mtermwidth <- getTerminalWidth
let availablewidth = NE.head $ NE.fromList $ catMaybes [mcolumns, mtermwidth, Just defaultWidth] -- PARTIAL: fromList won't fail because non-null list
return defcliopts {
rawopts_ = rawopts

View File

@ -98,11 +98,6 @@ flag debug
manual: True
default: False
flag terminfo
description: On POSIX systems, build with the terminfo lib for detecting terminal width
manual: False
default: True
flag threaded
description: Build with support for multithreaded execution
manual: False
@ -195,9 +190,6 @@ library
, utility-ht >=0.0.13
, wizards >=1.0
default-language: Haskell2010
if (!(os(windows))) && (flag(terminfo))
build-depends:
terminfo
if (flag(debug))
cpp-options: -DDEBUG
@ -247,9 +239,6 @@ executable hledger
, utility-ht >=0.0.13
, wizards >=1.0
default-language: Haskell2010
if (!(os(windows))) && (flag(terminfo))
build-depends:
terminfo
if (flag(debug))
cpp-options: -DDEBUG
if flag(threaded)
@ -300,9 +289,6 @@ test-suite unittest
, utility-ht >=0.0.13
, wizards >=1.0
default-language: Haskell2010
if (!(os(windows))) && (flag(terminfo))
build-depends:
terminfo
if (flag(debug))
cpp-options: -DDEBUG
@ -353,8 +339,5 @@ benchmark bench
, wizards >=1.0
buildable: False
default-language: Haskell2010
if (!(os(windows))) && (flag(terminfo))
build-depends:
terminfo
if (flag(debug))
cpp-options: -DDEBUG