    {-# language OverloadedStrings #-}

    module Yi.Keymap.Vim.Substitution
        ( substituteE
        , substituteConfirmE
        , repeatSubstitutionE
        , repeatSubstitutionFlaglessE
        ) where

    import Control.Monad (void)
    import Data.Monoid
    import Yi.MiniBuffer
    import Yi.Keymap (Keymap)
    import qualified Yi.Rope as R
    import Yi.Regex
    import Yi.Buffer
    import Yi.Editor
    import Yi.Search
    import Yi.Keymap.Keys (char, choice, (?>>!))
    import Yi.Keymap.Vim.Common
    import Yi.Keymap.Vim.StateUtils

    substituteE :: Substitution -> BufferM Region -> EditorM ()
    substituteE :: Substitution -> BufferM Region -> EditorM ()
    substituteE s :: Substitution
    s@(Substitution YiString
    from YiString
    to Bool
    global Bool
    caseInsensitive Bool
    confirm) BufferM Region
    regionB = do
            let opts :: [SearchOption]
    opts = if Bool
    caseInsensitive then [SearchOption
    IgnoreCase] else []
            lines' <- BufferM [Region] -> EditorM [Region]
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM [Region] -> EditorM [Region])
    -> BufferM [Region] -> EditorM [Region]
    forall a b. (a -> b) -> a -> b
    $ BufferM Region
    regionB BufferM Region -> (Region -> BufferM [Region]) -> BufferM [Region]
    forall a b. BufferM a -> (a -> BufferM b) -> BufferM b
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Region -> BufferM [Region]
    linesOfRegionB
            regex <- if R.null from
                        then getRegexE
                        else return . (either (const Nothing) Just)
                                . makeSearchOptsM opts . R.toString $ from
            case regex of
                Maybe SearchExp
    Nothing -> Text -> EditorM ()
    forall (m :: * -> *). MonadEditor m => Text -> m ()
    printMsg Text
    "No previous search pattern"
                Just SearchExp
    regex' -> do
                    Substitution -> EditorM ()
    saveSubstitutionE Substitution
    s
                    if Bool
    confirm
                    then SearchExp -> YiString -> Bool -> [Region] -> EditorM ()
    substituteConfirmE SearchExp
    regex' YiString
    to Bool
    global [Region]
    lines'
                    else do
                        BufferM () -> EditorM ()
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM () -> EditorM ()) -> BufferM () -> EditorM ()
    forall a b. (a -> b) -> a -> b
    $ do
                            -- We need to reverse the lines' here so that replacing
                            -- does not effect the regions in question.
                            (Region -> BufferM ()) -> [Region] -> BufferM ()
    forall (t :: * -> *) (m :: * -> *) a b.
    (Foldable t, Monad m) =>
    (a -> m b) -> t a -> m ()
    mapM_ (BufferM Int -> BufferM ()
    forall (f :: * -> *) a. Functor f => f a -> f ()
    void (BufferM Int -> BufferM ())
    -> (Region -> BufferM Int) -> Region -> BufferM ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . SearchExp -> YiString -> Bool -> Region -> BufferM Int
    searchAndRepRegion0 SearchExp
    regex' YiString
    to Bool
    global) ([Region] -> [Region]
    forall a. [a] -> [a]
    reverse [Region]
    lines')
                            BufferM ()
    moveToSol

    -- | Run substitution in confirm mode
    substituteConfirmE :: SearchExp -> R.YiString -> Bool -> [Region] -> EditorM ()
    substituteConfirmE :: SearchExp -> YiString -> Bool -> [Region] -> EditorM ()
    substituteConfirmE SearchExp
    regex YiString
    to Bool
    global [Region]
    lines' = do
        -- TODO This highlights all matches, even in non-global mode
        -- and could potentially be classified as a bug. Fixing requires
        -- changing the regex highlighting api.
        SearchExp -> EditorM ()
    setRegexE SearchExp
    regex
        regions <- BufferM [Region] -> EditorM [Region]
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM [Region] -> EditorM [Region])
    -> BufferM [Region] -> EditorM [Region]
    forall a b. (a -> b) -> a -> b
    $ SearchExp -> Bool -> [Region] -> BufferM [Region]
    findMatches SearchExp
    regex Bool
    global [Region]
    lines'
        substituteMatch to 0 False regions

    -- | All matches to replace under given flags
    findMatches :: SearchExp -> Bool -> [Region] -> BufferM [Region]
    findMatches :: SearchExp -> Bool -> [Region] -> BufferM [Region]
    findMatches SearchExp
    regex Bool
    global [Region]
    lines' = do
        let f :: [a] -> [a]
    f = if Bool
    global then [a] -> [a]
    forall a. a -> a
    id else Int -> [a] -> [a]
    forall a. Int -> [a] -> [a]
    take Int
    1
        [[Region]] -> [Region]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat ([[Region]] -> [Region]) -> BufferM [[Region]] -> BufferM [Region]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> (Region -> BufferM [Region]) -> [Region] -> BufferM [[Region]]
    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 (([Region] -> [Region]) -> BufferM [Region] -> BufferM [Region]
    forall a b. (a -> b) -> BufferM a -> BufferM b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap [Region] -> [Region]
    forall a. [a] -> [a]
    f (BufferM [Region] -> BufferM [Region])
    -> (Region -> BufferM [Region]) -> Region -> BufferM [Region]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . SearchExp -> Region -> BufferM [Region]
    regexRegionB SearchExp
    regex) [Region]
    lines'

    -- | Runs a list of matches using itself as a continuation
    substituteMatch :: R.YiString -> Int -> Bool -> [Region] -> EditorM ()
    substituteMatch :: YiString -> Int -> Bool -> [Region] -> EditorM ()
    substituteMatch YiString
    _ Int
    _ Bool
    _ [] = EditorM ()
    resetRegexE
    substituteMatch YiString
    to Int
    co Bool
    autoAll (Region
    m:[Region]
    ms) = do
        let m' :: Region
    m' = Int -> Region -> Region
    offsetRegion Int
    co Region
    m
        BufferM () -> EditorM ()
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM () -> EditorM ())
    -> (Point -> BufferM ()) -> Point -> EditorM ()
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Point -> BufferM ()
    moveTo (Point -> EditorM ()) -> Point -> EditorM ()
    forall a b. (a -> b) -> a -> b
    $ Region -> Point
    regionStart Region
    m'
        len <- BufferM Int -> EditorM Int
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM Int -> EditorM Int) -> BufferM Int -> EditorM Int
    forall a b. (a -> b) -> a -> b
    $ YiString -> Int
    R.length (YiString -> Int) -> BufferM YiString -> BufferM Int
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Region -> BufferM YiString
    readRegionB Region
    m'
        let diff = YiString -> Int
    R.length YiString
    to Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    len
            tex = Text
    "replace with " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> YiString -> Text
    R.toText YiString
    to Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    " (y/n/a/q)?"
        if autoAll
            then do withCurrentBuffer $ replaceRegionB m' to
                    substituteMatch to (co + diff) True ms
            else void . spawnMinibufferE tex . const $ askKeymap to co (co + diff) m ms

    -- | Offsets a region (to account for a region prior being modified)
    offsetRegion :: Int -> Region -> Region
    offsetRegion :: Int -> Region -> Region
    offsetRegion Int
    k Region
    reg = Point -> Point -> Region
    mkRegion (Region -> Point
    regionStart Region
    reg Point -> Point -> Point
    forall a. Num a => a -> a -> a
    + Point
    k') (Region -> Point
    regionEnd Region
    reg Point -> Point -> Point
    forall a. Num a => a -> a -> a
    + Point
    k')
        where k' :: Point
    k' = Int -> Point
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral Int
    k

    -- | Actual choices during confirm mode.
    askKeymap :: R.YiString -> Int -> Int -> Region -> [Region] -> Keymap
    askKeymap :: YiString -> Int -> Int -> Region -> [Region] -> Keymap
    askKeymap YiString
    to Int
    co Int
    co' Region
    m [Region]
    ms = [Keymap] -> Keymap
    forall (m :: * -> *) w e a.
    (MonadInteract m w e, MonadFail m) =>
    [m a] -> m a
    choice [
          Char -> Event
    char Char
    'n' Event -> EditorM () -> Keymap
    forall (m :: * -> *) a x.
    (MonadInteract m Action Event, YiAction a x, Show x) =>
    Event -> a -> m ()
    ?>>! EditorM ()
    cleanUp EditorM () -> EditorM () -> EditorM ()
    forall a b. EditorM a -> EditorM b -> EditorM b
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> YiString -> Int -> Bool -> [Region] -> EditorM ()
    substituteMatch YiString
    to Int
    co Bool
    False [Region]
    ms
        , Char -> Event
    char Char
    'a' Event -> EditorM () -> Keymap
    forall (m :: * -> *) a x.
    (MonadInteract m Action Event, YiAction a x, Show x) =>
    Event -> a -> m ()
    ?>>! do EditorM ()
    cleanUp
                           EditorM ()
    replace
                           YiString -> Int -> Bool -> [Region] -> EditorM ()
    substituteMatch YiString
    to Int
    co' Bool
    True [Region]
    ms
        , Char -> Event
    char Char
    'y' Event -> EditorM () -> Keymap
    forall (m :: * -> *) a x.
    (MonadInteract m Action Event, YiAction a x, Show x) =>
    Event -> a -> m ()
    ?>>! do EditorM ()
    cleanUp
                           EditorM ()
    replace
                           YiString -> Int -> Bool -> [Region] -> EditorM ()
    substituteMatch YiString
    to Int
    co' Bool
    False [Region]
    ms
        , Char -> Event
    char Char
    'q' Event -> EditorM () -> Keymap
    forall (m :: * -> *) a x.
    (MonadInteract m Action Event, YiAction a x, Show x) =>
    Event -> a -> m ()
    ?>>! EditorM ()
    cleanUp EditorM () -> EditorM () -> EditorM ()
    forall a b. EditorM a -> EditorM b -> EditorM b
    forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
    >> EditorM ()
    resetRegexE
        ]
        where cleanUp :: EditorM ()
    cleanUp = EditorM ()
    closeBufferAndWindowE
              replace :: EditorM ()
    replace = BufferM () -> EditorM ()
    forall (m :: * -> *) a. MonadEditor m => BufferM a -> m a
    withCurrentBuffer (BufferM () -> EditorM ()) -> BufferM () -> EditorM ()
    forall a b. (a -> b) -> a -> b
    $ Region -> YiString -> BufferM ()
    replaceRegionB (Int -> Region -> Region
    offsetRegion Int
    co Region
    m) YiString
    to

    repeatSubstitutionFlaglessE :: Substitution -> EditorM ()
    repeatSubstitutionFlaglessE :: Substitution -> EditorM ()
    repeatSubstitutionFlaglessE (Substitution YiString
    from YiString
    to Bool
    _ Bool
    _ Bool
    _) =
        Substitution -> BufferM Region -> EditorM ()
    substituteE (YiString -> YiString -> Bool -> Bool -> Bool -> Substitution
    Substitution YiString
    from YiString
    to Bool
    False Bool
    False Bool
    False) (TextUnit -> BufferM Region
    regionOfB TextUnit
    Line)

    repeatSubstitutionE :: Substitution -> EditorM ()
    repeatSubstitutionE :: Substitution -> EditorM ()
    repeatSubstitutionE Substitution
    s = Substitution -> BufferM Region -> EditorM ()
    substituteE Substitution
    s (TextUnit -> BufferM Region
    regionOfB TextUnit
    Line)