packages feed

stylish-haskell-0.2.0.0: src/StylishHaskell/Editor.hs

--------------------------------------------------------------------------------
-- | This module provides you with a line-based editor. It's main feature is
-- that you can specify multiple changes at the same time, e.g.:
--
-- > [deleteLine 3, changeLine 4 ["Foo"]]
--
-- when this is evaluated, we take into account that 4th line will become the
-- 3rd line before it needs changing.
module StylishHaskell.Editor
    ( Change
    , applyChanges

    , change
    , changeLine
    , delete
    , deleteLine
    , insert
    ) where


--------------------------------------------------------------------------------
import           StylishHaskell.Block


--------------------------------------------------------------------------------
-- | Changes the lines indicated by the 'Block' into the given 'Lines'
data Change a = Change
    { changeBlock :: Block a
    , changeLines :: [a]
    } deriving (Eq, Show)


--------------------------------------------------------------------------------
moveChange :: Int -> Change a -> Change a
moveChange offset (Change block ls) = Change (moveBlock offset block) ls


--------------------------------------------------------------------------------
-- | Number of additional lines introduced when a change is made.
changeExtraLines :: Change a -> Int
changeExtraLines (Change block ls) = length ls - blockLength block


--------------------------------------------------------------------------------
applyChanges :: [Change a] -> [a] -> [a]
applyChanges changes
    | overlapping blocks = error $
        "StylishHaskell.Editor.applyChanges: " ++
        "refusing to make overlapping changes"
    | otherwise          = go 1 changes
  where
    blocks = map changeBlock changes

    go _ []                ls = ls
    go n (ch : chs) ls =
        -- Divide the remaining lines into:
        --
        -- > pre
        -- > old  (lines that are affected by the change)
        -- > post
        --
        -- And generate:
        --
        -- > pre
        -- > new
        -- > (recurse)
        --
        let block      = changeBlock ch
            (pre, ls') = splitAt (blockStart block - n) ls
            (_, post)  = splitAt (blockLength block) ls'
            extraLines = changeExtraLines ch
            chs'       = map (moveChange extraLines) chs
            n'         = blockStart block + blockLength block + extraLines
        in pre ++ (changeLines ch) ++ go n' chs' post


--------------------------------------------------------------------------------
-- | Change a block of lines for some other lines
change :: Block a -> [a] -> Change a
change = Change


--------------------------------------------------------------------------------
-- | Change a single line for some other lines
changeLine :: Int -> [a] -> Change a
changeLine start = change (Block start start)


--------------------------------------------------------------------------------
-- | Delete a block of lines
delete :: Block a -> Change a
delete block = Change block []


--------------------------------------------------------------------------------
-- | Delete a single line
deleteLine :: Int -> Change a
deleteLine start = delete (Block start start)


--------------------------------------------------------------------------------
-- | Insert something /before/ the given lines
insert :: Int -> [a] -> Change a
insert start = Change (Block start (start - 1))