【问题标题】:Get Terminal width Haskell获取终端宽度 Haskell
【发布时间】:2012-09-30 03:21:18
【问题描述】:

如何在 Haskell 中获取终端的宽度?

我尝试过的事情

System.Posix.IOCtl (could not figure out how to get it to work) 

这只适用于unix。

谢谢

【问题讨论】:

    标签: haskell console functional-programming terminal ioctl


    【解决方案1】:

    由于您只在 Unix 上需要这个,我建议:

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

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

    【讨论】:

    • 命令/usr/X11/bin/resize 甚至不存在于arch ^^
    【解决方案2】:

    您可以使用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。

    呼!

    【讨论】:

    • 这不是有点矫枉过正吗?有没有更简单的?
    • 注意:ioctl' 调用中的 0 指的是 STDIN,因此如果 STDIN 被重定向,则会失败。假设获取终端宽度的目标是格式化输出,那么查询 STDOUT 可能会更好。
    • 好点。我已经更新了我的答案,但你的答案更加强大。我希望我能给你超过 1 个赞成票;如果塞满了有用的信息,你的答案!
    【解决方案3】:

    如果您不想依赖 ncurses,这里是使用 FFI 的适当 ioctl() 请求的包装器,基于 Getting terminal width in 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 preprocessor 根据 C 标头计算出正确的常量和偏移量,而不是硬编码它们。我认为它与 GHC 或 Haskell 平台一起打包,所以你很可能已经拥有它。

    如果您使用的是 Cabal,您可以将 TermSize.hs 添加到您的 .cabal 文件中,它会自动知道如何从 TermSize.hsc 生成它。否则,您可以手动运行 hsc2hs TermSize.hsc 以生成 .hs 文件,然后您可以使用 GHC 编译该文件。

    【讨论】:

      猜你喜欢
      • 2010-11-04
      • 2015-07-31
      • 2012-01-21
      • 2015-04-09
      • 1970-01-01
      • 2011-01-05
      • 2018-10-02
      • 2017-09-16
      • 1970-01-01
      相关资源
      最近更新 更多