获取 Haskell 终端宽度

8
如何在Haskell中获取终端的宽度?
我尝试过以下方法:
System.Posix.IOCtl (could not figure out how to get it to work) 

这只需要在Unix系统上运行。

谢谢。


ptyDimensions可以实现这个功能:https://hackage.haskell.org/package/posix-pty-0.2.2/docs/System-Posix-Pty.html#v:ptyDimensions - undefined
3个回答

16
如果您不想依赖ncurses,这里有一个使用FFI的适当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编译它。


9
你可以使用hcurses库。一旦你初始化了该库,你就可以使用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"
          }

请注意,数字1代表标准输出(STDOUT)。
呼...

2
这不是有点过度了吗?难道没有更简单的方法吗? - Joachim Breitner
注意:ioctl 调用中的 0 是指 STDIN,因此如果 STDIN 被重定向,则此调用将失败。假设获取终端宽度的目的是为了格式化输出,则查询 STDOUT 可能更好。 - hammar
1
好观点。我已经更新了我的答案,但是你的答案更加强大。我希望我能给你超过1个赞;你的答案充满了有用的信息! - pat

3

因为您只需要在Unix上使用,我建议:

resizeOutput <- readProcess "/usr/X11/bin/resize" [] ""

然后对输出进行一些解析。这可能不是100%可移植的,但我相信您可以向 resize 提供参数(特别是查看 -u ),因此您将获得相当一致的输出。


命令/usr/X11/bin/resize在Arch上甚至不存在^^ - xeruf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接