module Yi.Search (
setRegexE,
resetRegexE,
getRegexE,
SearchMatch,
SearchResult(..),
SearchOption(..),
doSearch,
searchInit,
continueSearch,
makeSimpleSearch,
searchReplaceRegionB,
searchReplaceSelectionB,
replaceString,
searchAndRepRegion,
searchAndRepRegion0,
searchAndRepUnit,
isearchInitE,
isearchIsEmpty,
isearchAddE,
isearchPrevE,
isearchNextE,
isearchWordE,
isearchHistory,
isearchDelE,
isearchCancelE,
isearchFinishE,
isearchCancelWithE,
isearchFinishWithE,
qrNext,
qrReplaceAll,
qrReplaceOne,
qrFinish
) where
import Control.Applicative ((<$>))
import Control.Lens (assign)
import Control.Monad (void, when)
import Data.Binary (Binary, get, put)
import Data.Char (isAlpha, isUpper)
import Data.Default (Default, def)
import Data.Maybe (listToMaybe)
import Data.Monoid ((<>))
import qualified Data.Text as T (Text, any, break, empty, length, null, takeWhile, unpack)
import qualified Data.Text.Encoding as E (decodeUtf8, encodeUtf8)
import Data.Typeable (Typeable)
import Yi.Buffer
import Yi.Editor
import Yi.History (historyFinishGen, historyMoveGen, historyStartGen)
import Yi.Regex
import qualified Yi.Rope as R (YiString, null, toString, toText)
import Yi.Search.Internal (getRegexE, resetRegexE, setRegexE)
import Yi.String (showT)
import Yi.Types (YiVariable)
import Yi.Utils (fst3)
import Yi.Window (Window)
type SearchMatch = Region
data SearchResult = PatternFound
| PatternNotFound
| SearchWrapped
deriving Eq
doSearch :: Maybe String
-> [SearchOption]
-> Direction
-> EditorM SearchResult
doSearch (Just re) fs d = searchInit re d fs >>= withCurrentBuffer . continueSearch
doSearch Nothing _ d = do
mre <- getRegexE
case mre of
Nothing -> fail "No previous search pattern"
Just r -> withCurrentBuffer (continueSearch (r,d))
searchInit :: String -> Direction -> [SearchOption] -> EditorM (SearchExp, Direction)
searchInit re d fs = do
let Right c_re = makeSearchOptsM fs re
setRegexE c_re
assign searchDirectionA d
return (c_re,d)
continueSearch :: (SearchExp, Direction) -> BufferM SearchResult
continueSearch (c_re, dir) = do
mp <- savingPointB $ do
moveB Character dir
rs <- regexB dir c_re
moveB Document (reverseDir dir)
ls <- regexB dir c_re
return $ listToMaybe $ fmap Right rs ++ fmap Left ls
maybe (return ()) (moveTo . regionStart . either id id) mp
return $ f mp
where
f (Just (Right _)) = PatternFound
f (Just (Left _)) = SearchWrapped
f Nothing = PatternNotFound
searchReplaceRegionB :: R.YiString
-> R.YiString
-> Region
-> BufferM Int
searchReplaceRegionB from to =
searchAndRepRegion0 (makeSimpleSearch from) to True
searchReplaceSelectionB :: R.YiString
-> R.YiString
-> BufferM Int
searchReplaceSelectionB from to =
getSelectRegionB >>= searchReplaceRegionB from to
replaceString :: R.YiString -> R.YiString -> BufferM Int
replaceString a b = regionOfB Document >>= searchReplaceRegionB a b
searchAndRepRegion0 :: SearchExp -> R.YiString -> Bool -> Region -> BufferM Int
searchAndRepRegion0 c_re str globally region = do
mp <- (if globally then id else take 1) <$> regexRegionB c_re region
let mp' = mayReverse (reverseDir $ regionDirection region) mp
mapM_ (`replaceRegionB` str) mp'
return (length mp)
searchAndRepRegion :: R.YiString -> R.YiString -> Bool -> Region -> EditorM Bool
searchAndRepRegion s str globally region = case R.null s of
False -> return False
True -> do
let c_re = makeSimpleSearch s
setRegexE c_re
assign searchDirectionA Forward
withCurrentBuffer $ (/= 0) <$> searchAndRepRegion0 c_re str globally region
searchAndRepUnit :: R.YiString -> R.YiString -> Bool -> TextUnit -> EditorM Bool
searchAndRepUnit re str g unit =
withCurrentBuffer (regionOfB unit) >>= searchAndRepRegion re str g
newtype Isearch = Isearch [(T.Text, Region, Direction)]
deriving (Typeable, Show)
instance Binary Isearch where
put (Isearch ts) = put (map3 E.encodeUtf8 ts)
get = Isearch . map3 E.decodeUtf8 <$> get
map3 :: (a -> d) -> [(a, b, c)] -> [(d, b, c)]
map3 _ [] = []
map3 f ((a, b, c):xs) = (f a, b, c) : map3 f xs
instance Default Isearch where
def = Isearch []
instance YiVariable Isearch
isearchInitE :: Direction -> EditorM ()
isearchInitE dir = do
historyStartGen iSearch
p <- withCurrentBuffer pointB
resetRegexE
putEditorDyn (Isearch [(T.empty ,mkRegion p p, dir)])
printMsg "I-search: "
isearchIsEmpty :: EditorM Bool
isearchIsEmpty = do
Isearch s <- getEditorDyn
return . not . T.null . fst3 $ head s
isearchAddE :: T.Text -> EditorM ()
isearchAddE inc = isearchFunE (<> inc)
makeSimpleSearch :: R.YiString -> SearchExp
makeSimpleSearch s = se
where Right se = makeSearchOptsM [QuoteRegex] (R.toString s)
makeISearch :: T.Text -> SearchExp
makeISearch s = case makeSearchOptsM opts (T.unpack s) of
Left _ -> SearchExp (T.unpack s) emptyRegex emptyRegex []
Right search -> search
where opts = QuoteRegex : if T.any isUpper s then [] else [IgnoreCase]
isearchFunE :: (T.Text -> T.Text) -> EditorM ()
isearchFunE fun = do
Isearch s <- getEditorDyn
let (previous,p0,direction) = head s
current = fun previous
srch = makeISearch current
printMsg $ "I-search: " <> current
setRegexE srch
prevPoint <- withCurrentBuffer pointB
matches <- withCurrentBuffer $ do
moveTo $ regionStart p0
when (direction == Backward) $
moveN $ T.length current
regexB direction srch
let onSuccess p = do withCurrentBuffer $ moveTo (regionEnd p)
putEditorDyn $ Isearch ((current, p, direction) : s)
case matches of
(p:_) -> onSuccess p
[] -> do matchesAfterWrap <- withCurrentBuffer $ do
case direction of
Forward -> moveTo 0
Backward -> do
bufferLength <- sizeB
moveTo bufferLength
regexB direction srch
case matchesAfterWrap of
(p:_) -> onSuccess p
[] -> do withCurrentBuffer $ moveTo prevPoint
putEditorDyn $ Isearch ((current, p0, direction) : s)
printMsg $ "Failing I-search: " <> current
isearchDelE :: EditorM ()
isearchDelE = do
Isearch s <- getEditorDyn
case s of
(_:(text,p,dir):rest) -> do
withCurrentBuffer $
moveTo $ regionEnd p
putEditorDyn $ Isearch ((text,p,dir):rest)
setRegexE $ makeISearch text
printMsg $ "I-search: " <> text
_ -> return ()
isearchHistory :: Int -> EditorM ()
isearchHistory delta = do
Isearch ((current,_p0,_dir):_) <- getEditorDyn
h <- historyMoveGen iSearch delta (return current)
isearchFunE (const h)
isearchPrevE :: EditorM ()
isearchPrevE = isearchNext0 Backward
isearchNextE :: EditorM ()
isearchNextE = isearchNext0 Forward
isearchNext0 :: Direction -> EditorM ()
isearchNext0 newDir = do
Isearch ((current,_p0,_dir):_rest) <- getEditorDyn
if T.null current
then isearchHistory 1
else isearchNext newDir
isearchNext :: Direction -> EditorM ()
isearchNext direction = do
Isearch ((current, p0, _dir) : rest) <- getEditorDyn
withCurrentBuffer $ moveTo (regionStart p0 + startOfs)
mp <- withCurrentBuffer $
regexB direction (makeISearch current)
case mp of
[] -> do
endPoint <- withCurrentBuffer $ do
moveTo (regionEnd p0)
sizeB
printMsg "isearch: end of document reached"
let wrappedOfs = case direction of
Forward -> mkRegion 0 0
Backward -> mkRegion endPoint endPoint
putEditorDyn $ Isearch ((current,wrappedOfs,direction):rest)
(p:_) -> do
withCurrentBuffer $
moveTo (regionEnd p)
printMsg $ "I-search: " <> current
putEditorDyn $ Isearch ((current,p,direction):rest)
where startOfs = case direction of
Forward -> 1
Backward -> 1
isearchWordE :: EditorM ()
isearchWordE = do
text <- R.toText <$> withCurrentBuffer (pointB >>= nelemsB 32)
let (prefix, rest) = T.break isAlpha text
word = T.takeWhile isAlpha rest
isearchAddE $ prefix <> word
isearchFinishE :: EditorM ()
isearchFinishE = isearchEnd True
isearchCancelE :: EditorM ()
isearchCancelE = isearchEnd False
isearchFinishWithE :: EditorM a -> EditorM ()
isearchFinishWithE act = isearchEndWith act True
isearchCancelWithE :: EditorM a -> EditorM ()
isearchCancelWithE act = isearchEndWith act False
iSearch :: T.Text
iSearch = "isearch"
isearchEndWith :: EditorM a -> Bool -> EditorM ()
isearchEndWith act accept = getEditorDyn >>= \case
Isearch [] -> return ()
Isearch s@((lastSearched, _, dir):_) -> do
let (_,p0,_) = last s
historyFinishGen iSearch (return lastSearched)
assign searchDirectionA dir
if accept
then do void act
printMsg "Quit"
else do resetRegexE
withCurrentBuffer $ moveTo $ regionStart p0
isearchEnd :: Bool -> EditorM ()
isearchEnd = isearchEndWith (return ())
qrNext :: Window -> BufferRef -> SearchExp -> EditorM ()
qrNext win b what = do
mp <- withGivenBufferAndWindow win b $ regexB Forward what
case mp of
[] -> do
printMsg "String to search not found"
qrFinish
(r:_) -> withGivenBufferAndWindow win b $ setSelectRegionB r
qrReplaceAll :: Window -> BufferRef -> SearchExp -> R.YiString -> EditorM ()
qrReplaceAll win b what replacement = do
n <- withGivenBufferAndWindow win b $ do
exchangePointAndMarkB
searchAndRepRegion0 what replacement True =<< regionOfPartB Document Forward
printMsg $ "Replaced " <> showT n <> " occurrences"
qrFinish
qrFinish :: EditorM ()
qrFinish = do
assign currentRegexA Nothing
closeBufferAndWindowE
qrReplaceOne :: Window -> BufferRef -> SearchExp -> R.YiString -> EditorM ()
qrReplaceOne win b reg replacement = do
qrReplaceCurrent win b replacement
qrNext win b reg
qrReplaceCurrent :: Window -> BufferRef -> R.YiString -> EditorM ()
qrReplaceCurrent win b replacement =
withGivenBufferAndWindow win b $
flip replaceRegionB replacement =<< getRawestSelectRegionB