{-# 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
  YiString
text <- Region -> BufferM YiString
readRegionB Region
r
  Region -> YiString -> BufferM ()
replaceRegionB Region
r ((Char -> Char) -> YiString -> YiString
R.map Char -> Char
f YiString
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 YiString
w0 <- Region -> BufferM YiString
readRegionB Region
r
                     YiString
w1 <- Region -> BufferM YiString
readRegionB Region
r'
                     Region -> YiString -> BufferM ()
replaceRegionB Region
r' YiString
w0
                     Region -> YiString -> BufferM ()
replaceRegionB Region
r  YiString
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 (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 (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Point -> BufferM Point
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
  [Int
lowCol, Int
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)
mapM Point -> BufferM Int
colOf [Region -> Point
regionStart Region
r, Region -> Point
regionEnd Region
r]
  Int
startLine <- Point -> BufferM Int
lineOf (Point -> BufferM Int) -> Point -> BufferM Int
forall a b. (a -> b) -> a -> b
$ Region -> Point
regionStart Region
r
  Int
endLine   <- Point -> BufferM Int
lineOf (Point -> BufferM Int) -> Point -> BufferM Int
forall a b. (a -> b) -> a -> b
$ Region -> Point
regionEnd Region
r
  Bool -> BufferM () -> BufferM ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
startLine Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
endLine) (BufferM () -> BufferM ()) -> BufferM () -> BufferM ()
forall a b. (a -> b) -> a -> b
$ String -> BufferM ()
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"blockifyRegion: impossible"
  (Int -> BufferM Region) -> [Int] -> BufferM [Region]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
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 (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))
       [Int
startLine..Int
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 (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
    [YiString]
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
    [Region] -> BufferM [Region]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Region] -> BufferM [Region]) -> [Region] -> BufferM [Region]
forall a b. (a -> b) -> a -> b
$ case [YiString]
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