Get Terminal width Haskell

时光毁灭记忆、已成空白 提交于 2019-12-05 22:00:17

问题


How to get the width of the terminal in Haskell?

Things I tried

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

This only has to work unix.

Thanks


回答1:


If you don't want a dependency on ncurses, here's a wrapper of the appropriate ioctl() request using the FFI, based on the accepted answer of 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)

This uses the hsc2hs preprocessor to figure out the correct constants and offsets based on the C headers rather than hardcoding them. I think it's packaged with either GHC or the Haskell Platform, so chances are you'll have it already.

If you're using Cabal, you can add TermSize.hs to your .cabal file and it'll automatically know how to generate it from TermSize.hsc. Otherwise, you can run hsc2hs TermSize.hsc manually to generate a .hs file which you can then compile with GHC.




回答2:


You could use hcurses. Once you've initialized the library, you can use scrSize to get the number of rows and columns on the screen.

To use System.Posix.IOCtl, you have to define a data type to represent the TIOCGWINSZ request, which fills in the following structure:

struct winsize {
    unsigned short ws_row;
    unsigned short ws_col;
    unsigned short ws_xpixel;   /* unused */
    unsigned short ws_ypixel;   /* unused */
};

You'll need to define a Haskell data type to hold this information, and make it an instance of 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
                           }

Now, you need to create a dummy data type to represent your request:

data TIOCGWINSZ = TIOCGWINSZ

Finally, you need to make your request type an instance of IOControl, and associate it with the Winsize data type.

instance IOControl TIOCGWINSZ Winsize where
  ioctlReq _ = ??

You will need to replace the ?? with the constant represented by TIOCGWINSZ in your header files (0x5413 on my system).

Now, you are ready to issue the ioctl. This command does not care about the input data, so you want to use the ioctl' form:

main = do { ws <- ioctl' 1 TIOCGWINSZ
          ; putStrLn $ "My terminal is " ++ show (ws_col ws) ++ " columns wide"
          }

Note that the 1 refers to STDOUT.

Phew!




回答3:


Since you need this only on Unix, I'd recommend:

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

And then doing a little-bit of parsing of the output. This may not be 100% portable, but I do believe you can provide resize with arguments (check out -u in particular), so you'll get fairly consistent output.



来源:https://stackoverflow.com/questions/12806053/get-terminal-width-haskell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!