{-# 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 []
        [Region]
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 (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Region -> BufferM [Region]
linesOfRegionB
        Maybe SearchExp
regex <- if YiString -> Bool
R.null YiString
from
                    then EditorM (Maybe SearchExp)
getRegexE
                    else Maybe SearchExp -> EditorM (Maybe SearchExp)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe SearchExp -> EditorM (Maybe SearchExp))
-> (YiString -> Maybe SearchExp)
-> YiString
-> EditorM (Maybe SearchExp)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String -> Maybe SearchExp)
-> (SearchExp -> Maybe SearchExp)
-> Either String SearchExp
-> Maybe SearchExp
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Maybe SearchExp -> String -> Maybe SearchExp
forall a b. a -> b -> a
const Maybe SearchExp
forall a. Maybe a
Nothing) SearchExp -> Maybe SearchExp
forall a. a -> Maybe a
Just) 
                            (Either String SearchExp -> Maybe SearchExp)
-> (YiString -> Either String SearchExp)
-> YiString
-> Maybe SearchExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [SearchOption] -> String -> Either String SearchExp
makeSearchOptsM [SearchOption]
opts (String -> Either String SearchExp)
-> (YiString -> String) -> YiString -> Either String SearchExp
forall b c a. (b -> c) -> (a -> b) -> a -> c
. YiString -> String
R.toString (YiString -> EditorM (Maybe SearchExp))
-> YiString -> EditorM (Maybe SearchExp)
forall a b. (a -> b) -> a -> b
$ YiString
from
        case Maybe SearchExp
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
    [Region]
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'
    YiString -> Int -> Bool -> [Region] -> EditorM ()
substituteMatch YiString
to Int
0 Bool
False [Region]
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)
mapM (([Region] -> [Region]) -> BufferM [Region] -> BufferM [Region]
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'
    Int
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 :: Int
diff = YiString -> Int
R.length YiString
to Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
len
        tex :: Text
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 Bool
autoAll
        then do 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 Region
m' YiString
to
                YiString -> Int -> Bool -> [Region] -> EditorM ()
substituteMatch YiString
to (Int
co Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
diff) Bool
True [Region]
ms
        else EditorM BufferRef -> EditorM ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (EditorM BufferRef -> EditorM ())
-> (Keymap -> EditorM BufferRef) -> Keymap -> EditorM ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> KeymapEndo -> EditorM BufferRef
spawnMinibufferE Text
tex (KeymapEndo -> EditorM BufferRef)
-> (Keymap -> KeymapEndo) -> Keymap -> EditorM BufferRef
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Keymap -> KeymapEndo
forall a b. a -> b -> a
const (Keymap -> EditorM ()) -> Keymap -> EditorM ()
forall a b. (a -> b) -> a -> b
$ YiString -> Int -> Int -> Region -> [Region] -> Keymap
askKeymap YiString
to Int
co (Int
co Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
diff) Region
m [Region]
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 (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 (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)