leksah-0.16.2.2: src/IDE/Find.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}
-----------------------------------------------------------------------------
--
-- Module : IDE.Find
-- Copyright : (c) Juergen Nicklisch-Franken, Hamish Mackenzie
-- License : GNU-GPL
--
-- Maintainer : <maintainer at leksah.org>
-- Stability : provisional
-- Portability : portable
--
-- | The toolbar for searching and replacing in a text buffer
--
-------------------------------------------------------------------------------
module IDE.Find (
toggleFindbar
, constructFindReplace
, hideFindbar
, showFindbar
, focusFindEntry
, editFindInc
, editGotoLine
, FindState(..)
, getFindState
, setFindState
, editFind
, showToolbar
, hideToolbar
, toggleToolbar
) where
import Control.Applicative (Applicative)
import IDE.Core.State
import IDE.Utils.GUIUtils
import IDE.TextEditor hiding(afterFocusIn)
import IDE.Pane.SourceBuffer
import Data.Char (digitToInt, isDigit, toLower, isAlphaNum)
import Text.Regex.TDFA hiding (caseSensitive, after)
import qualified Text.Regex.TDFA as Regex
import Text.Regex.TDFA.Text (compile)
import Data.List (find, isPrefixOf)
import Data.Array (bounds, (!), inRange)
import Data.Maybe (fromJust)
import IDE.Pane.Grep (grepWorkspace)
import IDE.Workspaces (workspaceTry, packageTry)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.Reader (ask)
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad (liftM, filterM, when, unless, void)
import Foreign.C.Types (CInt(..))
import Foreign.Ptr (Ptr(..))
import Foreign.ForeignPtr (withForeignPtr)
import Data.Text (Text)
import qualified Data.Text as T
(pack, unpack, singleton, isPrefixOf, length, null, toLower)
import Data.Monoid ((<>))
import GI.Gtk.Objects.SpinButton
(spinButtonSetRange, spinButtonNewWithRange, spinButtonSetValue,
SpinButton(..), spinButtonGetValueAsInt)
import Data.GI.Base (set, unsafeCastTo, nullToNothing)
import GI.Gtk.Objects.Entry
(afterEntryActivate, onEntryActivate, entrySetCompletion,
entrySetPlaceholderText, entryNew, entrySetText, entryGetText,
Entry(..))
import Data.GI.Gtk.ModelView.SeqStore
(seqStoreGetValue, SeqStore(..), seqStoreNew, seqStoreAppend,
seqStoreClear, seqStoreToList)
import GI.Gtk.Objects.Widget
(widgetGetName, widgetModifyText, widgetModifyBase,
afterWidgetFocusInEvent, onWidgetKeyPressEvent,
onWidgetFocusInEvent, setWidgetTooltipText, Widget(..), widgetSetName,
widgetGrabFocus, widgetShowAll, widgetHide)
import GI.Gtk.Objects.Toolbar
(toolbarInsert, toolbarSetIconSize, toolbarSetStyle, toolbarNew,
Toolbar(..))
import GI.Gtk.Enums (StateType(..), IconSize(..), ToolbarStyle(..))
import GI.Gtk.Objects.ToolButton
(toolButtonSetLabel, onToolButtonClicked, toolButtonNew,
toolButtonNewFromStock)
import GI.Gtk.Objects.ToolItem (toolItemSetExpand, toolItemNew)
import GI.Gtk.Objects.Container
(containerGetChildren, containerChildSetProperty, containerAdd)
import GI.Gtk.Objects.Label (labelNew)
import GI.Gtk.Objects.SeparatorToolItem (separatorToolItemNew)
import GI.Gtk.Objects.ToggleToolButton
(ToggleToolButton(..), toggleToolButtonSetActive,
toggleToolButtonGetActive, toggleToolButtonNew)
import Data.GI.Gtk.ModelView.TreeModel (makeColumnIdString, treeModelGetPath, treeModelGetValue)
import Data.GI.Gtk.ModelView.CustomStore (customStoreSetColumn)
import GI.Gtk.Objects.EntryCompletion
(EntryCompletion(..), onEntryCompletionMatchSelected,
entryCompletionSetMatchFunc, setEntryCompletionModel,
entryCompletionNew)
import GI.Gtk.Objects.CellRendererText
(setCellRendererTextText, cellRendererTextNew)
import Data.GI.Gtk.ModelView.CellLayout
(cellLayoutSetDataFunction, cellLayoutPackStart)
import GI.Gtk.Interfaces.Editable
(afterEditableDeleteText, afterEditableInsertText)
import GI.Gdk.Structs.EventKey
(getEventKeyState, getEventKeyKeyval)
import GI.Gdk.Functions (keyvalName)
import GI.Gdk.Flags (ModifierType(..))
import Data.GI.Base.GValue (IsGValue(..))
import GI.Gtk.Structs.TreeIter (TreeIter(..))
import GI.Gtk.Objects.Bin (Bin(..), binGetChild)
import IDE.Core.Types (toGdkColor)
import GI.Gtk.Structs.TreePath (treePathGetIndices)
import GI.Gtk (noWidget, imageNewFromIconName)
data FindState = FindState {
entryStr :: Text
, entryHist :: [Text]
, replaceStr :: Text
, replaceHist :: [Text]
, caseSensitive :: Bool
, entireWord :: Bool
, wrapAround :: Bool
, regex :: Bool
, lineNr :: Int}
deriving(Eq,Ord,Show,Read)
getFindState :: IDEM FindState
getFindState = do
(fb,ls) <- needFindbar
do
lineNr <- getLineEntry fb >>= spinButtonGetValueAsInt
replaceStr <- getReplaceEntry fb >>= entryGetText
entryStr <- getFindEntry fb >>= entryGetText
entryHist <- seqStoreToList ls
entireWord <- getEntireWord fb
wrapAround <- getWrapAround fb
caseSensitive <- getCaseSensitive fb
regex <- getRegex fb
return FindState{
entryStr = entryStr
, entryHist = entryHist
, replaceStr = replaceStr
, replaceHist = []
, caseSensitive = caseSensitive
, entireWord = entireWord
, wrapAround = wrapAround
, regex = regex
, lineNr = fromIntegral lineNr}
setFindState :: FindState -> IDEAction
setFindState fs = do
(fb,ls) <- needFindbar
getLineEntry fb >>= (\sb -> spinButtonSetValue sb (fromIntegral (lineNr fs)))
getReplaceEntry fb >>= (\e -> entrySetText e (replaceStr fs))
getFindEntry fb >>= (\e -> entrySetText e (entryStr fs))
seqStoreClear ls
mapM_ (seqStoreAppend ls) (entryHist fs)
setEntireWord fb (entireWord fs)
setWrapAround fb (wrapAround fs)
setCaseSensitive fb (caseSensitive fs)
setRegex fb (regex fs)
hideToolbar :: IDEAction
hideToolbar = do
(_,mbtb) <- readIDE toolbar
case mbtb of
Nothing -> return ()
Just tb -> do
modifyIDE_ (\ide -> ide{toolbar = (False,snd (toolbar ide))})
widgetHide tb
showToolbar :: IDEAction
showToolbar = do
(_,mbtb) <- readIDE toolbar
case mbtb of
Nothing -> return ()
Just tb -> do
modifyIDE_ (\ide -> ide{toolbar = (True,snd (toolbar ide))})
widgetShowAll tb
toggleToolbar :: IDEAction
toggleToolbar = do
toolbar' <- readIDE toolbar
if fst toolbar'
then hideToolbar
else showToolbar
hideFindbar :: IDEAction
hideFindbar = do
(_,mbfb) <- readIDE findbar
modifyIDE_ (\ide -> ide{findbar = (False,mbfb)})
case mbfb of
Nothing -> return ()
Just (fb,_) -> do
widgetHide fb
void $ inActiveBufContext False $ \_ sv ebuf _ _ -> do
removeTagByName ebuf "search-match"
return True
showFindbar :: IDEAction
showFindbar = do
(_,mbfb) <- readIDE findbar
modifyIDE_ (\ide -> ide{findbar = (True,mbfb)})
case mbfb of
Nothing -> return ()
Just (fb,_) -> widgetShowAll fb
focusFindEntry :: IDEAction
focusFindEntry = do
(fb,_) <- needFindbar
do
entry <- getFindEntry fb
widgetGrabFocus entry
toggleFindbar :: IDEAction
toggleFindbar = do
findbar <- readIDE findbar
if fst findbar
then hideFindbar
else showFindbar
constructFindReplace :: IDEM Toolbar
constructFindReplace = do
ideR <- ask
toolbar <- toolbarNew
toolbarSetStyle toolbar ToolbarStyleIcons
toolbarSetIconSize toolbar IconSizeSmallToolbar
let newButtonFromIconName name = imageNewFromIconName
(Just name)
(fromIntegral $ fromEnum IconSizeSmallToolbar)
>>= (`toolButtonNew` Nothing) . Just
closeButton <- newButtonFromIconName "window-close"
toolbarInsert toolbar closeButton 0
spinTool <- toolItemNew
spinL <- spinButtonNewWithRange 1.0 1000.0 10.0
widgetSetName spinL "gotoLineEntry"
containerAdd spinTool spinL
widgetSetName spinTool "gotoLineEntryTool"
toolbarInsert toolbar spinTool 0
labelTool3 <- toolItemNew
label3 <- labelNew (Just (__"Goto Line :"))
containerAdd labelTool3 label3
toolbarInsert toolbar labelTool3 0
sep1 <- separatorToolItemNew
toolbarInsert toolbar sep1 0
let performGrep = reflectIDE (workspaceTry $ doGrep toolbar) ideR
grepButton <- toolButtonNew noWidget (Just (__"Grep"))
toolbarInsert toolbar grepButton 0
onToolButtonClicked grepButton performGrep
setWidgetTooltipText grepButton $ __"Search in multiple files"
sep1 <- separatorToolItemNew
toolbarInsert toolbar sep1 0
replaceAllButton <- toolButtonNew noWidget (Just (__"Replace All"))
toolbarInsert toolbar replaceAllButton 0
replaceButton <- newButtonFromIconName "edit-find-replace"
toolbarInsert toolbar replaceButton 0
replaceTool <- toolItemNew
rentry <- entryNew
widgetSetName rentry "replaceEntry"
entrySetPlaceholderText rentry (Just "Replace with")
containerAdd replaceTool rentry
widgetSetName replaceTool "replaceTool"
toolbarInsert toolbar replaceTool 0
sep2 <- separatorToolItemNew
toolbarInsert toolbar sep2 0
nextButton <- newButtonFromIconName "go-next"
toolbarInsert toolbar nextButton 0
setWidgetTooltipText nextButton $ __"Search for the next match in the current file"
nextButton `onToolButtonClicked` doSearch toolbar Forward ideR
wrapAroundButton <- toggleToolButtonNew
toolButtonSetLabel wrapAroundButton (Just (__"Wrap"))
widgetSetName wrapAroundButton "wrapAroundButton"
toolbarInsert toolbar wrapAroundButton 0
setWidgetTooltipText wrapAroundButton $ __"When selected searching will continue from the top when no more matches are found"
previousButton <- newButtonFromIconName "go-previous"
toolbarInsert toolbar previousButton 0
setWidgetTooltipText previousButton $ __"Search for the previous match in the current file"
previousButton `onToolButtonClicked` doSearch toolbar Backward ideR
entryTool <- toolItemNew
entry <- entryNew
widgetSetName entry "searchEntry"
entrySetPlaceholderText entry (Just "Find")
containerAdd entryTool entry
widgetSetName entryTool "searchEntryTool"
toolItemSetExpand entryTool True
toolbarInsert toolbar entryTool 0
let column0 = makeColumnIdString 0
store <- seqStoreNew []
customStoreSetColumn store column0 id
completion <- entryCompletionNew
entrySetCompletion entry (Just completion)
setEntryCompletionModel completion store
cell <- cellRendererTextNew
cellLayoutPackStart completion cell True
cellLayoutSetDataFunction completion cell store
(setCellRendererTextText cell)
entryCompletionSetMatchFunc completion (matchFunc store)
onEntryCompletionMatchSelected completion $ \ model iter -> do
txt <- treeModelGetValue model iter column0
entrySetText entry txt
doSearch toolbar Forward ideR
return True
regexButton <- toggleToolButtonNew
toolButtonSetLabel regexButton (Just (__"Regex"))
widgetSetName regexButton "regexButton"
toolbarInsert toolbar regexButton 0
onToolButtonClicked regexButton $ doSearch toolbar Insert ideR
setWidgetTooltipText regexButton $ __"When selected the search string is used as a regular expression"
entireWordButton <- toggleToolButtonNew
toolButtonSetLabel entireWordButton (Just (__"Words"))
widgetSetName entireWordButton "entireWordButton"
toolbarInsert toolbar entireWordButton 0
entireWordButton `onToolButtonClicked` doSearch toolbar Insert ideR
setWidgetTooltipText entireWordButton $ __"When selected only entire words are matched"
caseSensitiveButton <- toggleToolButtonNew
toolButtonSetLabel caseSensitiveButton (Just (__"Case"))
widgetSetName caseSensitiveButton "caseSensitiveButton"
toolbarInsert toolbar caseSensitiveButton 0
caseSensitiveButton `onToolButtonClicked`
doSearch toolbar Insert ideR
setWidgetTooltipText caseSensitiveButton $ __"When selected the search is case sensitive"
afterEditableInsertText entry (\ t _ i -> do
doSearch toolbar Insert ideR
return i)
afterEditableDeleteText entry (\ _ _ -> doSearch toolbar Delete ideR)
onEntryActivate entry $ doSearch toolbar Forward ideR
onIDE onWidgetFocusInEvent entry $ do
liftIDE $ triggerEventIDE (Sensitivity [(SensitivityEditor, False)])
return False
onToolButtonClicked replaceButton $ replace toolbar Forward ideR
let performReplaceAll = replaceAll toolbar Initial ideR
onToolButtonClicked replaceAllButton performReplaceAll
let ctrl "c" = toggleToolButton caseSensitiveButton >> return True
ctrl "e" = toggleToolButton regexButton >> return True
ctrl "w" = toggleToolButton entireWordButton >> return True
ctrl "p" = toggleToolButton wrapAroundButton >> return True
ctrl "r" = performReplaceAll >> return True
ctrl "g" = performGrep >> return True
ctrl _ = return False
toggleToolButton btn = do
old <- toggleToolButtonGetActive btn
toggleToolButtonSetActive btn $ not old
onWidgetKeyPressEvent entry $ \e -> do
mbName <- getEventKeyKeyval e >>= keyvalName
mods <- getEventKeyState e
case mbName of
Just "Return" | ModifierTypeShiftMask `elem` mods ->
doSearch toolbar Backward ideR >> return True
Just "Down" -> doSearch toolbar Forward ideR >> return True
Just "Up" -> doSearch toolbar Backward ideR >> return True
Just "Escape" -> getOut ideR >> return True
Just "Tab" -> do
re <- getReplaceEntry toolbar
widgetGrabFocus re
--- widgetAc
return True
Just name | mapControlCommand ModifierTypeControlMask `elem` mods -> ctrl $ T.toLower name
_ -> return False
onWidgetKeyPressEvent rentry $ \e -> do
mbName <- getEventKeyKeyval e >>= keyvalName
mods <- getEventKeyState e
case mbName of
Just name
| name == "Tab" || name == "ISO_Left_Tab" -> do
fe <- getFindEntry toolbar
widgetGrabFocus fe
return True
| mapControlCommand ModifierTypeControlMask `elem` mods ->
ctrl $ T.toLower name
_ -> return False
onIDE afterWidgetFocusInEvent spinL . liftIDE $ inActiveBufContext True $ \ _ _ ebuf _ _ -> do
max <- getLineCount ebuf
spinButtonSetRange spinL 1.0 (fromIntegral max)
return True
onWidgetKeyPressEvent spinL $ \e -> do
mbName <- getEventKeyKeyval e >>= keyvalName
mods <- getEventKeyState e
case mbName of
Just "Escape" -> getOut ideR >> return True
Just "Tab" -> do
re <- getFindEntry toolbar
widgetGrabFocus re
return True
Just name | mapControlCommand ModifierTypeControlMask `elem` mods -> ctrl $ T.toLower name
_ -> return False
afterEntryActivate spinL . (`reflectIDE` ideR) $ inActiveBufContext () $ \ _ sv ebuf _ _ -> do
line <- spinButtonGetValueAsInt spinL
iter <- getIterAtLine ebuf (fromIntegral line - 1)
placeCursor ebuf iter
scrollToIter sv iter 0.2 Nothing
getOut ideR
return ()
onToolButtonClicked closeButton $ reflectIDE hideFindbar ideR
liftIO $ do
containerChildSetProperty toolbar spinTool "homogeneous" =<< toGValue False
containerChildSetProperty toolbar wrapAroundButton "homogeneous" =<< toGValue False
containerChildSetProperty toolbar entireWordButton "homogeneous" =<< toGValue False
containerChildSetProperty toolbar caseSensitiveButton "homogeneous" =<< toGValue False
containerChildSetProperty toolbar regexButton "homogeneous" =<< toGValue False
containerChildSetProperty toolbar replaceAllButton "homogeneous" =<< toGValue False
containerChildSetProperty toolbar labelTool3 "homogeneous" =<< toGValue False
modifyIDE_ (\ ide -> ide{findbar = (False, Just (toolbar, store))})
return toolbar
where
getOut ideR = liftIO $ reflectIDE (do
hideFindbar
maybeActiveBuf ?>>= makeActive) ideR
doSearch :: Toolbar -> SearchHint -> IDERef -> IO ()
doSearch fb hint ideR = do
entry <- getFindEntry fb
search <- entryGetText entry
entireWord <- getEntireWord fb
caseSensitive <- getCaseSensitive fb
wrapAround <- getWrapAround fb
regex <- getRegex fb
mbExpAndMatchIndex <- regexAndMatchIndex caseSensitive entireWord regex search
case mbExpAndMatchIndex of
Just (exp, matchIndex) -> do
res <- reflectIDE (editFind entireWord caseSensitive wrapAround regex search "" hint) ideR
if res || T.null search
then do
widgetModifyBase entry StateTypeNormal . Just =<< toGdkColor white
widgetModifyText entry StateTypeNormal . Just =<< toGdkColor black
else do
widgetModifyBase entry StateTypeNormal . Just =<< toGdkColor red
widgetModifyText entry StateTypeNormal . Just =<< toGdkColor white
Nothing ->
if T.null search
then do
widgetModifyBase entry StateTypeNormal . Just =<< toGdkColor white
widgetModifyText entry StateTypeNormal . Just =<< toGdkColor black
else do
widgetModifyBase entry StateTypeNormal . Just =<< toGdkColor orange
widgetModifyText entry StateTypeNormal . Just =<< toGdkColor black
reflectIDE (addToHist search) ideR
return ()
doGrep :: Toolbar -> WorkspaceAction
doGrep fb = do
package <- ask
ideR <- lift ask
entry <- getFindEntry fb
search <- entryGetText entry
entireWord <- getEntireWord fb
caseSensitive <- getCaseSensitive fb
wrapAround <- getWrapAround fb
regex <- getRegex fb
let (regexString, _) = regexStringAndMatchIndex entireWord regex search
liftIDE $ workspaceTry $ grepWorkspace regexString caseSensitive
matchFunc :: (Applicative m, MonadIO m) => SeqStore Text -> EntryCompletion -> Text -> TreeIter -> m Bool
matchFunc model completion str iter = do
tp <- treeModelGetPath model iter >>= treePathGetIndices
case tp of
(i:_) -> do row <- seqStoreGetValue model i
return (T.isPrefixOf (T.toLower str) (T.toLower row) && T.length str < T.length row)
_ -> return False
addToHist :: Text -> IDEAction
addToHist str =
unless (T.null str) $ do
(_, ls) <- needFindbar
entryHist <- seqStoreToList ls
unless (any (str `T.isPrefixOf`) entryHist) $ do
let newList = take 12 (str : filter (\ e -> not (e `T.isPrefixOf` str)) entryHist)
seqStoreClear ls
mapM_ (seqStoreAppend ls) newList
replace :: Toolbar -> SearchHint -> IDERef -> IO ()
replace fb hint ideR = do
entry <- getFindEntry fb
search <- entryGetText entry
rentry <- getReplaceEntry fb
replace <- entryGetText rentry
entireWord <- getEntireWord fb
caseSensitive <- getCaseSensitive fb
wrapAround <- getWrapAround fb
regex <- getRegex fb
found <- reflectIDE (editReplace entireWord caseSensitive wrapAround regex search replace hint)
ideR
return ()
replaceAll :: Toolbar -> SearchHint -> IDERef -> IO ()
replaceAll fb hint ideR = do
entry <- getFindEntry fb
search <- entryGetText entry
rentry <- getReplaceEntry fb
replace <- entryGetText rentry
entireWord <- getEntireWord fb
caseSensitive <- getCaseSensitive fb
wrapAround <- getWrapAround fb
regex <- getRegex fb
found <- reflectIDE (editReplaceAll entireWord caseSensitive regex search replace hint)
ideR
return ()
editFind :: Bool -> Bool -> Bool -> Bool -> Text -> Text -> SearchHint -> IDEM Bool
editFind entireWord caseSensitive wrapAround regex search dummy hint = do
mbExpAndMatchIndex <- regexAndMatchIndex caseSensitive entireWord regex search
case mbExpAndMatchIndex of
Nothing -> return False
Just (exp, matchIndex) -> editFind' exp matchIndex wrapAround dummy hint
editFind' :: Regex -> Int -> Bool -> Text -> SearchHint -> IDEM Bool
editFind' exp matchIndex wrapAround dummy hint =
inActiveBufContext False $ \_ sv ebuf _ _ -> do
i1 <- getStartIter ebuf
i2 <- getEndIter ebuf
text <- getText ebuf i1 i2 True
removeTagByName ebuf "search-match"
startMark <- getInsertMark ebuf
st1 <- getIterAtMark ebuf startMark
mbsr2 <-
if hint == Backward
then do
st2 <- backwardCharC st1
st3 <- backwardCharC st2
mbsr <- backSearch exp matchIndex ebuf text st3
case mbsr of
Nothing ->
if wrapAround
then backSearch exp matchIndex ebuf text i2
else return Nothing
m -> return m
else do
st2 <- if hint == Forward
then forwardCharC st1
else return st1
mbsr <- if hint == Initial
then initialSearch exp matchIndex ebuf text st2
else forwardSearch exp matchIndex ebuf text st2
case mbsr of
Nothing ->
if wrapAround
then forwardSearch exp matchIndex ebuf text i1
else return Nothing
m -> return m
case mbsr2 of
Just (start,end,_) -> do --found
--widgetGrabFocus sourceView
scrollToIter sv start 0.2 Nothing
applyTagByName ebuf "search-match" start end
placeCursor ebuf start
return True
Nothing -> return False
where
backSearch exp matchIndex ebuf text iter = do
offset <- getOffset iter
findMatch exp matchIndex ebuf text (<= offset) True
forwardSearch exp matchIndex ebuf text iter = do
offset <- getOffset iter
findMatch exp matchIndex ebuf text (>= offset) False
initialSearch exp matchIndex ebuf text iter = findMatch exp matchIndex ebuf text (>= 0) False
regexAndMatchIndex :: MonadIO m => Bool -> Bool -> Bool -> Text -> m (Maybe (Regex, Int))
regexAndMatchIndex caseSensitive entireWord regex string =
if T.null string
then return Nothing
else do
let (regexString, index) = regexStringAndMatchIndex entireWord regex string
case compileRegex caseSensitive regexString of
Left err -> do
sysMessage Normal $ T.pack err
return Nothing
Right regex -> return $ Just (regex, index)
regexStringAndMatchIndex :: Bool -> Bool -> Text -> (Text, Int)
regexStringAndMatchIndex entireWord regex string =
-- Escape non regex string
let regexString = if regex
then string
else foldl (\s c -> s <> if isAlphaNum c then T.singleton c else "\\"<>T.singleton c) "" $ T.unpack string in
-- Regular expression with word filter if needed
if entireWord
then ("(^|[^a-zA-Z0-9])(" <> regexString <> ")($|[^a-zA-Z0-9])", 2)
else (regexString, 0)
findMatch :: TextEditor editor => Regex -> Int -> EditorBuffer editor -> Text -> (Int -> Bool) -> Bool -> IDEM (Maybe (EditorIter editor, EditorIter editor, MatchArray))
findMatch exp matchIndex gtkbuf text offsetPred findLast = do
let matches = (if findLast then reverse else id) (matchAll exp text)
case find (offsetPred . fst . (!matchIndex)) matches of
Just matches -> do
iterStart <- getStartIter gtkbuf
iter1 <- forwardCharsC iterStart (fst (matches!matchIndex))
iter2 <- forwardCharsC iter1 (snd (matches!matchIndex))
return $ Just (iter1, iter2, matches)
Nothing -> return Nothing
editReplace :: Bool -> Bool -> Bool -> Bool -> Text -> Text -> SearchHint -> IDEM Bool
editReplace entireWord caseSensitive wrapAround regex search replace hint =
editReplace' entireWord caseSensitive wrapAround regex search replace hint False
editReplace' :: Bool -> Bool -> Bool -> Bool -> Text -> Text -> SearchHint -> Bool -> IDEM Bool
editReplace' entireWord caseSensitive wrapAround regex search replace hint fromStart =
inActiveBufContext False $ \_ _ ebuf _ _ -> do
insertMark <- getInsertMark ebuf
iter <- getIterAtMark ebuf insertMark
offset <- getOffset iter
let offset' = if fromStart then 0 else offset
mbExpAndMatchIndex <- regexAndMatchIndex caseSensitive entireWord regex search
case mbExpAndMatchIndex of
Just (exp, matchIndex) -> do
iStart <- getStartIter ebuf
iEnd <- getEndIter ebuf
text <- getText ebuf iStart iEnd True
match <- findMatch exp matchIndex ebuf text (== offset') False
case match of
Just (iterStart, iterEnd, matches) -> do
mbText <- replacementText regex text matchIndex matches $ T.unpack replace
case mbText of
Just text -> do
beginUserAction ebuf
delete ebuf iterStart iterEnd
insert ebuf iterStart (T.pack text)
endUserAction ebuf
Nothing -> do
sysMessage Normal
"Should never happen. findMatch worked but repleacementText failed"
return ()
editFind entireWord caseSensitive wrapAround regex search "" hint
Nothing -> do
r <- editFind entireWord caseSensitive wrapAround regex search "" hint
if r
then editReplace' entireWord caseSensitive wrapAround regex search
replace hint False
else return False
Nothing -> return False
where
replacementText False _ _ _ replace = return $ Just replace
replacementText True text matchIndex matches replace =
case compileRegex caseSensitive search of
Left err -> do
sysMessage Normal $ T.pack err
return Nothing
Right exp -> return $ Just $ regexReplacement text matchIndex matches replace
regexReplacement :: Text -> Int -> MatchArray -> String -> String
regexReplacement _ _ _ [] = []
regexReplacement text matchIndex matches ('\\' : '\\' : xs) = '\\' : regexReplacement text matchIndex matches xs
regexReplacement text matchIndex matches ('\\' : n : xs) | isDigit n =
let subIndex = matchIndex + digitToInt n
value = if inRange (bounds matches) subIndex
then
let subExp = matches!(matchIndex + digitToInt n) in
take (snd subExp) $ drop (fst subExp) $ T.unpack text
else ['\\', n] in
value ++ regexReplacement text matchIndex matches xs
regexReplacement text matchIndex matches (x : xs) = x : regexReplacement text matchIndex matches xs
editReplaceAll :: Bool -> Bool -> Bool -> Text -> Text -> SearchHint -> IDEM Bool
editReplaceAll = editReplaceAll' True
editReplaceAll' :: Bool -> Bool -> Bool -> Bool -> Text -> Text -> SearchHint -> IDEM Bool
editReplaceAll' fromStart entireWord caseSensitive regex search replace hint = do
res <- editReplace' entireWord caseSensitive False regex search replace hint fromStart
if res
then editReplaceAll' False entireWord caseSensitive regex search replace hint
else return False
compileRegex :: Bool -> Text -> Either String Regex
compileRegex caseSense searchString =
let compOption = defaultCompOpt {
Regex.caseSensitive = caseSense
, multiline = True } in
compile compOption defaultExecOpt searchString
red = Color 64000 10000 10000
orange = Color 64000 48000 0
white = Color 64000 64000 64000
black = Color 0 0 0
needFindbar :: IDEM (Toolbar,SeqStore Text)
needFindbar = do
(_,mbfb) <- readIDE findbar
case mbfb of
Nothing -> throwIDE "Find>>needFindbar: Findbar not initialized"
Just p -> return p
-- | Find action. If SearchHint==Initial then we'll use the selected text or current identifier to populate the find text box
editFindInc :: SearchHint -> IDEAction
editFindInc hint = do
ideR <- ask
(fb,_) <- needFindbar
case hint of
Initial -> do
mbtext <- selectedText -- if no text selected, search for the last query
case mbtext of
Just text -> do
findEntry <- getFindEntry fb
entrySetText findEntry text
Nothing -> return ()
_ -> return ()
showFindbar
reifyIDE $ \ideR -> do
entry <- getFindEntry fb
widgetGrabFocus entry
case hint of
Forward -> doSearch fb Forward ideR
Backward -> doSearch fb Backward ideR
_ -> return ()
editGotoLine :: IDEAction
editGotoLine = do
showFindbar
(fb,_) <- needFindbar
entry <- getLineEntry fb
widgetGrabFocus entry
getLineEntry :: (Applicative m, MonadIO m) => Toolbar -> m SpinButton
getLineEntry tb = getWidget "gotoLineEntryTool" tb >>= liftIO . unsafeCastTo SpinButton
getReplaceEntry, getFindEntry :: (Applicative m, MonadIO m) => Toolbar -> m Entry
getReplaceEntry tb = getWidget "replaceTool" tb >>= liftIO . unsafeCastTo Entry
getFindEntry tb = getWidget "searchEntryTool" tb >>= liftIO . unsafeCastTo Entry
getWidget :: (Applicative m, MonadIO m) => Text -> Toolbar -> m Widget
getWidget str tb = do
widgets <- containerGetChildren tb
entryL <- filterM (fmap (== str) . widgetGetName) widgets
case entryL of
[w] -> liftIO (unsafeCastTo Bin w) >>= (fmap fromJust . nullToNothing . binGetChild)
_ -> throwIDE "Find>>getWidget not found(2)"
getEntireWord, getWrapAround, getCaseSensitive, getRegex :: (Applicative m, MonadIO m) => Toolbar -> m Bool
getEntireWord = getSelection "entireWordButton"
getWrapAround = getSelection "wrapAroundButton"
getCaseSensitive = getSelection "caseSensitiveButton"
getRegex = getSelection "regexButton"
getSelection :: (Applicative m, MonadIO m) => Text -> Toolbar -> m Bool
getSelection str tb = do
widgets <- containerGetChildren tb
entryL <- filterM (fmap (== str) . widgetGetName) widgets
case entryL of
[w] -> liftIO (unsafeCastTo ToggleToolButton w) >>= toggleToolButtonGetActive
_ -> throwIDE "Find>>getIt widget not found"
setEntireWord, setWrapAround, setCaseSensitive, setRegex :: (Applicative m, MonadIO m) => Toolbar -> Bool -> m ()
setEntireWord = setSelection "entireWordButton"
setWrapAround = setSelection "wrapAroundButton"
setCaseSensitive = setSelection "caseSensitiveButton"
setRegex = setSelection "regexButton"
setSelection :: (Applicative m, MonadIO m) => Text -> Toolbar -> Bool -> m ()
setSelection str tb bool = do
widgets <- containerGetChildren tb
entryL <- filterM (fmap (== str) . widgetGetName ) widgets
case entryL of
[w] -> liftIO (unsafeCastTo ToggleToolButton w) >>= \ttb -> toggleToolButtonSetActive ttb bool
_ -> throwIDE "Find>>getIt widget not found"