    {-# LANGUAGE CPP #-}
    {-# OPTIONS_HADDOCK show-extensions #-}

    -- |
    -- Module      :  Yi.Buffer.Region
    -- License     :  GPL-2
    -- Maintainer  :  yi-devel@googlegroups.com
    -- Stability   :  experimental
    -- Portability :  portable
    --
    -- This module defines buffer operation on regions

    module Yi.Buffer.Region
      ( module Yi.Region
      , swapRegionsB
      , deleteRegionB
      , replaceRegionB
      , readRegionB
      , mapRegionB
      , modifyRegionB
      , winRegionB
      , inclusiveRegionB
      , blockifyRegion
      , joinLinesB
      , concatLinesB
      , linesOfRegionB
      ) where

    import           Control.Monad       (when)
    import           Data.Char           (isSpace)
    import           Data.List           (sort)
    import           Yi.Buffer.Misc
    import           Yi.Region
    import           Yi.Rope             (YiString)
    import qualified Yi.Rope             as R (YiString, cons, dropWhile, filter
                                              , lines, lines', map, null, length)
    import           Yi.String           (overInit)
    import           Yi.Utils            (SemiNum ((~-)))
    import           Yi.Window           (winRegion)

    winRegionB :: BufferM Region
    winRegionB :: BufferM Region
    winRegionB = (Window -> Region) -> BufferM Region
    forall a. (Window -> a) -> BufferM a
    askWindow Window -> Region
    winRegion

    -- | Delete an arbitrary part of the buffer
    deleteRegionB :: Region -> BufferM ()
    deleteRegionB :: Region -> BufferM ()
    deleteRegionB Region
    r = Direction -> Int -> Point -> BufferM ()
    deleteNAt (Region -> Direction
    regionDirection Region
    r) (Size -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Region -> Point
    regionEnd Region
    r Point -> Point -> Size
    forall absolute relative.
    SemiNum absolute relative =>
    absolute -> absolute -> relative
    ~- Region -> Point
    regionStart Region
    r)) (Region -> Point
    regionStart Region
    r)

    readRegionB :: Region -> BufferM YiString
    readRegionB :: Region -> BufferM YiString
    readRegionB Region
    r = Int -> Point -> BufferM YiString
    nelemsB (Point -> Int
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Region -> Point
    regionEnd Region
    r Point -> Point -> Point
    forall a. Num a => a -> a -> a
    - Point
    i)) Point
    i
        where i :: Point
    i = Region -> Point
    regionStart Region
    r

    -- | Replace a region with a given rope.
    replaceRegionB :: Region -> YiString -> BufferM ()
    replaceRegionB :: Region -> YiString -> BufferM ()
    replaceRegionB Region
    r YiString
    s = do
      Region -> BufferM ()
    deleteRegionB Region
    r
      YiString -> Point -> BufferM ()
    insertNAt YiString
    s (Point -> BufferM ()) -> Point -> BufferM ()
    forall a b. (a -> b) -> a -> b
    $ Region -> Point
    regionStart Region
    r

    -- | Map the given function over the characters in the region.
    mapRegionB :: Region -> (Char -> Char) -> BufferM ()
    mapRegionB :: Region -> (Char -> Char) -> BufferM ()
    mapRegionB Region
    r Char -> Char
    f = do
      text <- Region -> BufferM YiString
    readRegionB Region
    r
      replaceRegionB r (R.map f text)

    -- | Swap the content of two Regions
    swapRegionsB :: Region -> Region -> BufferM ()
    swapRegionsB :: Region -> Region -> BufferM ()
    swapRegionsB Region
    r Region
    r'
        | Region -> Point
    regionStart Region
    r Point -> Point -> Bool
    forall a. Ord a => a -> a -> Bool
    > Region -> Point
    regionStart Region
    r' = Region -> Region -> BufferM ()
    swapRegionsB Region
    r' Region
    r
        | Bool
    otherwise = do w0 <- Region -> BufferM YiString
    readRegionB Region
    r
                         w1 <- readRegionB r'
                         replaceRegionB r' w0
                         replaceRegionB r  w1

    -- | Modifies the given region according to the given
    -- string transformation function
    modifyRegionB :: (R.YiString -> R.YiString)
                     -- ^ The string modification function
                  -> Region
                     -- ^ The region to modify
                  -> BufferM ()
    modifyRegionB :: (YiString -> YiString) -> Region -> BufferM ()
    modifyRegionB YiString -> YiString
    f Region
    region = YiString -> YiString
    f (YiString -> YiString) -> BufferM YiString -> BufferM YiString
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Region -> BufferM YiString
    readRegionB Region
    region BufferM YiString -> (YiString -> BufferM ()) -> BufferM ()
    forall a b. BufferM a -> (a -> BufferM b) -> BufferM b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Region -> YiString -> BufferM ()
    replaceRegionB Region
    region

    -- | Extend the right bound of a region to include it.
    inclusiveRegionB :: Region -> BufferM Region
    inclusiveRegionB :: Region -> BufferM Region
    inclusiveRegionB Region
    r =
              if Region -> Point
    regionStart Region
    r Point -> Point -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Region -> Point
    regionEnd Region
    r
                  then Point -> Point -> Region
    mkRegion (Region -> Point
    regionStart Region
    r) (Point -> Region) -> BufferM Point -> BufferM Region
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Point -> BufferM Point
    pointAfterCursorB (Region -> Point
    regionEnd Region
    r)
                  else Point -> Point -> Region
    mkRegion (Point -> Point -> Region)
    -> BufferM Point -> BufferM (Point -> Region)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Point -> BufferM Point
    pointAfterCursorB (Region -> Point
    regionStart Region
    r) BufferM (Point -> Region) -> BufferM Point -> BufferM Region
    forall a b. BufferM (a -> b) -> BufferM a -> BufferM b
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
    <*> Point -> BufferM Point
    forall a. a -> BufferM a
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Region -> Point
    regionEnd Region
    r)

    -- | See a region as a block/rectangular region,
    -- since regions are represented by two point, this returns
    -- a list of small regions form this block region.
    blockifyRegion :: Region -> BufferM [Region]
    blockifyRegion :: Region -> BufferM [Region]
    blockifyRegion Region
    r = BufferM [Region] -> BufferM [Region]
    forall a. BufferM a -> BufferM a
    savingPointB (BufferM [Region] -> BufferM [Region])
    -> BufferM [Region] -> BufferM [Region]
    forall a b. (a -> b) -> a -> b
    $ do
      [lowCol, highCol] <- [Int] -> [Int]
    forall a. Ord a => [a] -> [a]
    sort ([Int] -> [Int]) -> BufferM [Int] -> BufferM [Int]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> (Point -> BufferM Int) -> [Point] -> BufferM [Int]
    forall (t :: * -> *) (m :: * -> *) a b.
    (Traversable t, Monad m) =>
    (a -> m b) -> t a -> m (t b)
    forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
    mapM Point -> BufferM Int
    colOf [Region -> Point
    regionStart Region
    r, Region -> Point
    regionEnd Region
    r]
      startLine <- lineOf $ regionStart r
      endLine   <- lineOf $ regionEnd r
      when (startLine > endLine) $ fail "blockifyRegion: impossible"
      mapM (\Int
    line -> Point -> Point -> Region
    mkRegion (Point -> Point -> Region)
    -> BufferM Point -> BufferM (Point -> Region)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Int -> Int -> BufferM Point
    pointOfLineColB Int
    line Int
    lowCol
                              BufferM (Point -> Region) -> BufferM Point -> BufferM Region
    forall a b. BufferM (a -> b) -> BufferM a -> BufferM b
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
    <*> Int -> Int -> BufferM Point
    pointOfLineColB Int
    line (Int
    1 Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    highCol))
           [startLine..endLine]

    -- | Joins lines in the region with a single space, skipping any empty
    -- lines.
    joinLinesB :: Region -> BufferM ()
    joinLinesB :: Region -> BufferM ()
    joinLinesB = BufferM () -> BufferM ()
    forall a. BufferM a -> BufferM a
    savingPointB (BufferM () -> BufferM ())
    -> (Region -> BufferM ()) -> Region -> BufferM ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (YiString -> YiString) -> Region -> BufferM ()
    modifyRegionB YiString -> YiString
    g'
      where
        g' :: YiString -> YiString
    g' = (YiString -> YiString) -> YiString -> YiString
    overInit ((YiString -> YiString) -> YiString -> YiString)
    -> (YiString -> YiString) -> YiString -> YiString
    forall a b. (a -> b) -> a -> b
    $ [YiString] -> YiString
    forall a. Monoid a => [a] -> a
    mconcat ([YiString] -> YiString)
    -> (YiString -> [YiString]) -> YiString -> YiString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [YiString] -> [YiString]
    pad ([YiString] -> [YiString])
    -> (YiString -> [YiString]) -> YiString -> [YiString]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . YiString -> [YiString]
    R.lines

        pad :: [R.YiString] -> [R.YiString]
        pad :: [YiString] -> [YiString]
    pad [] = []
        pad (YiString
    x:[YiString]
    xs) = YiString
    x YiString -> [YiString] -> [YiString]
    forall a. a -> [a] -> [a]
    : (YiString -> YiString) -> [YiString] -> [YiString]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((YiString -> YiString) -> YiString -> YiString
    skip (Char -> YiString -> YiString
    R.cons Char
    ' ' (YiString -> YiString)
    -> (YiString -> YiString) -> YiString -> YiString
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Char -> Bool) -> YiString -> YiString
    R.dropWhile Char -> Bool
    isSpace)) [YiString]
    xs

        skip :: (YiString -> YiString) -> YiString -> YiString
    skip YiString -> YiString
    g YiString
    x = if YiString -> Bool
    R.null YiString
    x then YiString
    x else YiString -> YiString
    g YiString
    x

    -- | Concatenates lines in the region preserving the trailing newline
    -- if any.
    concatLinesB :: Region -> BufferM ()
    concatLinesB :: Region -> BufferM ()
    concatLinesB = BufferM () -> BufferM ()
    forall a. BufferM a -> BufferM a
    savingPointB (BufferM () -> BufferM ())
    -> (Region -> BufferM ()) -> Region -> BufferM ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (YiString -> YiString) -> Region -> BufferM ()
    modifyRegionB ((YiString -> YiString) -> YiString -> YiString
    overInit ((YiString -> YiString) -> YiString -> YiString)
    -> (YiString -> YiString) -> YiString -> YiString
    forall a b. (a -> b) -> a -> b
    $ (Char -> Bool) -> YiString -> YiString
    R.filter (Char -> Char -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Char
    '\n'))

    -- | Gets the lines of a region (as a region), preserving newlines. Thus the
    -- resulting list of regions is a partition of the original region.
    --
    -- The direction of the region is preserved and all smaller regions will
    -- retain that direction.
    --
    -- Note that regions should never be empty, so it would be odd for this to
    -- return an empty list...
    linesOfRegionB :: Region -> BufferM [Region]
    linesOfRegionB :: Region -> BufferM [Region]
    linesOfRegionB Region
    region = do
        let start :: Point
    start = Region -> Point
    regionStart Region
    region
            direction :: Direction
    direction = Region -> Direction
    regionDirection Region
    region
        ls <- YiString -> [YiString]
    R.lines' (YiString -> [YiString]) -> BufferM YiString -> BufferM [YiString]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Region -> BufferM YiString
    readRegionB Region
    region
        return $ case ls of
            [] -> []
            (YiString
    l:[YiString]
    ls') -> let initialRegion :: Region
    initialRegion = Direction -> Point -> Point -> Region
    mkRegion' Direction
    direction Point
    start (Point
    start Point -> Point -> Point
    forall a. Num a => a -> a -> a
    + Int -> Point
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (YiString -> Int
    R.length YiString
    l))
                       in (Region -> YiString -> Region) -> Region -> [YiString] -> [Region]
    forall b a. (b -> a -> b) -> b -> [a] -> [b]
    scanl Region -> YiString -> Region
    nextRegion Region
    initialRegion [YiString]
    ls'

    -- | Given some text and the previous region, finds the next region
    -- (used for implementing linesOfRegionB, not generally useful)
    nextRegion :: Region -> R.YiString -> Region
    nextRegion :: Region -> YiString -> Region
    nextRegion Region
    r YiString
    l = Direction -> Point -> Point -> Region
    mkRegion' (Region -> Direction
    regionDirection Region
    r) (Region -> Point
    regionEnd Region
    r) (Region -> Point
    regionEnd Region
    r Point -> Point -> Point
    forall a. Num a => a -> a -> a
    + Point
    len)
        where len :: Point
    len = Int -> Point
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral (Int -> Point) -> Int -> Point
    forall a b. (a -> b) -> a -> b
    $ YiString -> Int
    R.length YiString
    l