如何在Haskell中获取终端的宽度?
我尝试过以下方法:
我尝试过以下方法:
System.Posix.IOCtl (could not figure out how to get it to work)
这只需要在Unix系统上运行。
谢谢。
System.Posix.IOCtl (could not figure out how to get it to work)
这只需要在Unix系统上运行。
谢谢。
ioctl()
请求的包装器,基于C语言中获取终端宽度的问题的接受答案。
TermSize.hsc
{-# LANGUAGE ForeignFunctionInterface #-}
module TermSize (getTermSize) where
import Foreign
import Foreign.C.Error
import Foreign.C.Types
#include <sys/ioctl.h>
#include <unistd.h>
-- Trick for calculating alignment of a type, taken from
-- http://www.haskell.org/haskellwiki/FFICookBook#Working_with_structs
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
-- The ws_xpixel and ws_ypixel fields are unused, so I've omitted them here.
data WinSize = WinSize { wsRow, wsCol :: CUShort }
instance Storable WinSize where
sizeOf _ = (#size struct winsize)
alignment _ = (#alignment struct winsize)
peek ptr = do
row <- (#peek struct winsize, ws_row) ptr
col <- (#peek struct winsize, ws_col) ptr
return $ WinSize row col
poke ptr (WinSize row col) = do
(#poke struct winsize, ws_row) ptr row
(#poke struct winsize, ws_col) ptr col
foreign import ccall "sys/ioctl.h ioctl"
ioctl :: CInt -> CInt -> Ptr WinSize -> IO CInt
-- | Return current number of (rows, columns) of the terminal.
getTermSize :: IO (Int, Int)
getTermSize =
with (WinSize 0 0) $ \ws -> do
throwErrnoIfMinus1 "ioctl" $
ioctl (#const STDOUT_FILENO) (#const TIOCGWINSZ) ws
WinSize row col <- peek ws
return (fromIntegral row, fromIntegral col)
这里使用hsc2hs
预处理器来根据C头文件找出正确的常量和偏移量,而不是硬编码它们。我认为它已经被打包到GHC或Haskell平台中了,所以你可能已经有了它。
如果你正在使用Cabal,你可以将TermSize.hs
添加到你的.cabal
文件中,它会自动从TermSize.hsc
生成。否则,您可以手动运行hsc2hs TermSize.hsc
来生成一个.hs
文件,然后用GHC编译它。
scrSize
函数获取屏幕上的行数和列数。System.Posix.IOCtl
模块,你需要定义一个数据类型来表示TIOCGWINSZ
请求,这将填充以下结构体:struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel; /* unused */
unsigned short ws_ypixel; /* unused */
};
您需要定义一个Haskell数据类型来保存这些信息,并将其实例化为Storable
:
{-# LANGUAGE RecordWildCards #-}
import Foreign.Storable
import Foreign.Ptr
import Foreign.C
data Winsize = Winsize { ws_row :: CUShort
, ws_col :: CUShort
, ws_xpixel :: CUShort
, ws_ypixel :: CUShort
}
instance Storable Winsize where
sizeOf _ = 8
alignment _ = 2
peek p = do { ws_row <- peekByteOff p 0
; ws_col <- peekByteOff p 2
; ws_xpixel <- peekByteOff p 4
; ws_ypixel <- peekByteOff p 6
; return $ Winsize {..}
}
poke p Winsize {..} = do { pokeByteOff p 0 ws_row
; pokeByteOff p 2 ws_col
; pokeByteOff p 4 ws_xpixel
; pokeByteOff p 6 ws_ypixel
}
data TIOCGWINSZ = TIOCGWINSZ
最后,您需要将请求类型设置为IOControl
的实例,并将其与Winsize
数据类型相关联。
instance IOControl TIOCGWINSZ Winsize where
ioctlReq _ = ??
??
替换为常量TIOCGWINSZ
(在我的系统上为0x5413
)。ioctl
命令。此命令不关心输入数据,因此您需要使用ioctl'
形式:main = do { ws <- ioctl' 1 TIOCGWINSZ
; putStrLn $ "My terminal is " ++ show (ws_col ws) ++ " columns wide"
}
ioctl
调用中的 0
是指 STDIN,因此如果 STDIN 被重定向,则此调用将失败。假设获取终端宽度的目的是为了格式化输出,则查询 STDOUT 可能更好。 - hammar因为您只需要在Unix上使用,我建议:
resizeOutput <- readProcess "/usr/X11/bin/resize" [] ""
然后对输出进行一些解析。这可能不是100%可移植的,但我相信您可以向 resize
提供参数(特别是查看 -u
),因此您将获得相当一致的输出。
/usr/X11/bin/resize
在Arch上甚至不存在^^ - xeruf