System/Console/Haskeline/Backend/WCWidth.hs

    module System.Console.Haskeline.Backend.WCWidth(
                                gsWidth,
                                splitAtWidth,
                                takeWidth,
                                ) where

    -- Certain characters are "wide", i.e. take up two spaces in the terminal.
    -- This module wraps the necessary foreign routines, and also provides some convenience
    -- functions for width-breaking code.

    import System.Console.Haskeline.LineState

    import Data.List
    import Foreign.C.Types

    foreign import ccall unsafe haskeline_mk_wcwidth :: CWchar -> CInt

    wcwidth :: Char -> Int
    wcwidth c = case haskeline_mk_wcwidth $ toEnum $ fromEnum c of
                    -1 -> 0 -- Control characters have zero width.  (Used by the
                            -- "\SOH...\STX" hack in LineState.stringToGraphemes.)
                    w -> fromIntegral w

    gWidth :: Grapheme -> Int
    gWidth g = wcwidth (baseChar g)

    gsWidth :: [Grapheme] -> Int
    gsWidth = foldl' (+) 0 . map gWidth

    -- | Split off the maximal list which is no more than the given width.
    -- returns the width of that list.
    splitAtWidth :: Int -> [Grapheme] -> ([Grapheme],[Grapheme],Int)
    splitAtWidth n xs = case splitAtWidth' n xs of
                            (this,rest,remaining) -> (this,rest,n-remaining)

    -- Returns the amount of unused space in the line.
    splitAtWidth' :: Int -> [Grapheme] -> ([Grapheme],[Grapheme],Int)
    splitAtWidth' w [] = ([],[],w)
    splitAtWidth' w (g:gs)
        | gw > w = ([],g:gs,w)
        | otherwise = (g:gs',gs'',r)
      where
        gw = gWidth g
        (gs',gs'',r) = splitAtWidth' (w-gw) gs

    -- Returns the longest prefix less than or equal to the given width
    -- plus the width of that list.
    takeWidth :: Int -> [Grapheme] -> ([Grapheme],Int)
    takeWidth n gs = case splitAtWidth n gs of
                        (gs',_,len) -> (gs',len)