PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/IDE/Pane/Grep.hs

https://github.com/sannysanoff/leksah
Haskell | 326 lines | 274 code | 34 blank | 18 comment | 10 complexity | fcb650d533dfca3a9ed26b4870b40dd3 MD5 | raw file
  1. {-# LANGUAGE CPP, FlexibleInstances, DeriveDataTypeable, MultiParamTypeClasses,
  2. TypeSynonymInstances, RecordWildCards #-}
  3. -----------------------------------------------------------------------------
  4. --
  5. -- Module : IDE.Pane.Grep
  6. -- Copyright : (c) Juergen Nicklisch-Franken, Hamish Mackenzie
  7. -- License : GNU-GPL
  8. --
  9. -- Maintainer : <maintainer at leksah.org>
  10. -- Stability : provisional
  11. -- Portability : portable
  12. --
  13. -- | The pane of ide where grep results are displayed
  14. --
  15. -------------------------------------------------------------------------------
  16. module IDE.Pane.Grep (
  17. IDEGrep(..)
  18. , grepWorkspace
  19. , GrepState(..)
  20. , getGrep
  21. ) where
  22. import Graphics.UI.Gtk hiding (get)
  23. import qualified Graphics.UI.Gtk.Gdk.Events as Gdk
  24. import Text.ParserCombinators.Parsec.Language
  25. import Text.ParserCombinators.Parsec hiding(Parser)
  26. import qualified Text.ParserCombinators.Parsec.Token as P
  27. import Data.Maybe
  28. import Control.Monad.Reader
  29. import Data.Typeable
  30. import IDE.Core.State
  31. import IDE.BufferMode
  32. import IDE.Utils.Tool (runTool, ToolOutput(..))
  33. import Control.Concurrent
  34. (forkOS, newEmptyMVar, isEmptyMVar, takeMVar, putMVar, MVar,
  35. forkIO)
  36. import IDE.LogRef (logOutput, defaultLineLogger)
  37. import IDE.Pane.SourceBuffer
  38. (goToSourceDefinition, maybeActiveBuf, IDEBuffer(..))
  39. import IDE.TextEditor (grabFocus)
  40. import Control.Applicative ((<$>))
  41. import System.FilePath ((</>), dropFileName)
  42. import System.Exit (ExitCode(..))
  43. import IDE.Pane.Log (getLog)
  44. import Control.DeepSeq
  45. #ifdef MIN_VERSION_process_leksah
  46. import IDE.System.Process (getProcessExitCode, interruptProcessGroup)
  47. #else
  48. import System.Process (getProcessExitCode, interruptProcessGroupOf)
  49. #endif
  50. import qualified Data.Enumerator as E
  51. (Step(..), run_, Iteratee(..), run)
  52. import qualified Data.Enumerator.List as EL
  53. (foldM, head, dropWhile, isolate)
  54. import Data.Enumerator (($$), (>>==))
  55. import qualified Data.List as L ()
  56. data GrepRecord = GrepRecord {
  57. file :: FilePath
  58. , line :: Int
  59. , context :: String
  60. , parDir :: Maybe FilePath
  61. }
  62. isDir GrepRecord{parDir = Nothing} = True
  63. isDir otherwies = False
  64. -- | A grep pane description
  65. --
  66. data IDEGrep = IDEGrep {
  67. scrolledView :: ScrolledWindow
  68. , treeView :: TreeView
  69. , grepStore :: TreeStore GrepRecord
  70. , waitingGrep :: MVar Bool
  71. , activeGrep :: MVar Bool
  72. } deriving Typeable
  73. data GrepState = GrepState
  74. deriving(Eq,Ord,Read,Show,Typeable)
  75. instance Pane IDEGrep IDEM
  76. where
  77. primPaneName _ = "Grep"
  78. getAddedIndex _ = 0
  79. getTopWidget = castToWidget . scrolledView
  80. paneId b = "*Grep"
  81. instance RecoverablePane IDEGrep GrepState IDEM where
  82. saveState p = do
  83. return (Just GrepState)
  84. recoverState pp GrepState = do
  85. nb <- getNotebook pp
  86. buildPane pp nb builder
  87. builder pp nb windows = reifyIDE $ \ ideR -> do
  88. grepStore <- treeStoreNew []
  89. treeView <- treeViewNew
  90. treeViewSetModel treeView grepStore
  91. renderer1 <- cellRendererTextNew
  92. renderer10 <- cellRendererPixbufNew
  93. col1 <- treeViewColumnNew
  94. treeViewColumnSetTitle col1 "File"
  95. treeViewColumnSetSizing col1 TreeViewColumnAutosize
  96. treeViewColumnSetResizable col1 True
  97. treeViewColumnSetReorderable col1 True
  98. treeViewAppendColumn treeView col1
  99. cellLayoutPackStart col1 renderer10 False
  100. cellLayoutPackStart col1 renderer1 True
  101. cellLayoutSetAttributes col1 renderer1 grepStore
  102. $ \row -> [ cellText := file row]
  103. renderer2 <- cellRendererTextNew
  104. col2 <- treeViewColumnNew
  105. treeViewColumnSetTitle col2 "Line"
  106. treeViewColumnSetSizing col2 TreeViewColumnAutosize
  107. treeViewColumnSetResizable col2 True
  108. treeViewColumnSetReorderable col2 True
  109. treeViewAppendColumn treeView col2
  110. cellLayoutPackStart col2 renderer2 True
  111. cellLayoutSetAttributes col2 renderer2 grepStore
  112. $ \row -> [ cellText := show $ line row]
  113. renderer3 <- cellRendererTextNew
  114. renderer30 <- cellRendererPixbufNew
  115. col3 <- treeViewColumnNew
  116. treeViewColumnSetTitle col3 "Context"
  117. treeViewColumnSetSizing col3 TreeViewColumnAutosize
  118. treeViewColumnSetResizable col3 True
  119. treeViewColumnSetReorderable col3 True
  120. treeViewAppendColumn treeView col3
  121. cellLayoutPackStart col3 renderer30 False
  122. cellLayoutPackStart col3 renderer3 True
  123. cellLayoutSetAttributes col3 renderer3 grepStore
  124. $ \row -> [ cellText := context row]
  125. treeViewSetHeadersVisible treeView True
  126. sel <- treeViewGetSelection treeView
  127. treeSelectionSetMode sel SelectionSingle
  128. scrolledView <- scrolledWindowNew Nothing Nothing
  129. containerAdd scrolledView treeView
  130. scrolledWindowSetPolicy scrolledView PolicyAutomatic PolicyAutomatic
  131. waitingGrep <- newEmptyMVar
  132. activeGrep <- newEmptyMVar
  133. let grep = IDEGrep {..}
  134. let
  135. gotoSource :: Bool -> IO Bool
  136. gotoSource focus = do
  137. sel <- getSelectionGrepRecord treeView grepStore
  138. case sel of
  139. Just record -> reflectIDE (do
  140. case record of
  141. GrepRecord {file=f, line=l, parDir=Just pp} ->
  142. (goToSourceDefinition (pp </> f) $ Just $ Location l 0 l 0)
  143. ?>>= (\b -> when focus $ grabFocus (sourceView b))
  144. _ -> return ()) ideR
  145. Nothing -> return ()
  146. return True
  147. cid1 <- treeView `afterFocusIn`
  148. (\_ -> do reflectIDE (makeActive grep) ideR ; return True)
  149. cid2 <- treeView `onKeyPress`
  150. (\e ->
  151. case e of
  152. k@(Gdk.Key _ _ _ _ _ _ _ _ _ _)
  153. | Gdk.eventKeyName k == "Return" -> do
  154. gotoSource True
  155. | Gdk.eventKeyName k == "Escape" -> do
  156. reflectIDE (do
  157. lastActiveBufferPane ?>>= \paneName -> do
  158. (PaneC pane) <- paneFromName paneName
  159. makeActive pane
  160. return ()
  161. triggerEventIDE StartFindInitial) ideR
  162. return True
  163. -- gotoSource True
  164. | otherwise -> do
  165. return False
  166. _ -> return False
  167. )
  168. sel `onSelectionChanged` (void $ gotoSource False)
  169. return (Just grep,[ConnectC cid1])
  170. getGrep :: Maybe PanePath -> IDEM IDEGrep
  171. getGrep Nothing = forceGetPane (Right "*Grep")
  172. getGrep (Just pp) = forceGetPane (Left pp)
  173. grepLineParser :: CharParser () GrepRecord
  174. grepLineParser = try (do
  175. file <- many (noneOf ":")
  176. char ':'
  177. line <- int
  178. char ':'
  179. context <- many anyChar
  180. let parDir = Nothing
  181. return $ GrepRecord {..}
  182. <?> "grepLineParser")
  183. lexer = P.makeTokenParser emptyDef
  184. lexeme = P.lexeme lexer
  185. whiteSpace = P.whiteSpace lexer
  186. hexadecimal = P.hexadecimal lexer
  187. symbol = P.symbol lexer
  188. identifier = P.identifier lexer
  189. colon = P.colon lexer
  190. int = fmap fromInteger $ P.integer lexer
  191. getSelectionGrepRecord :: TreeView
  192. -> TreeStore GrepRecord
  193. -> IO (Maybe GrepRecord)
  194. getSelectionGrepRecord treeView grepStore = do
  195. treeSelection <- treeViewGetSelection treeView
  196. paths <- treeSelectionGetSelectedRows treeSelection
  197. case paths of
  198. p:_ -> Just <$> treeStoreGetValue grepStore p
  199. _ -> return Nothing
  200. grepWorkspace :: String -> Bool -> WorkspaceAction
  201. grepWorkspace "" caseSensitive = return ()
  202. grepWorkspace regexString caseSensitive = do
  203. ws <- ask
  204. maybeActive <- lift $ readIDE activePack
  205. let packages = case maybeActive of
  206. Just active -> active : (filter (/= active) $ wsPackages ws)
  207. Nothing -> wsPackages ws
  208. lift $ grepDirectories regexString caseSensitive $
  209. map (\p -> (dropFileName (ipdCabalFile p), ipdSrcDirs p)) $ packages
  210. grepDirectories :: String -> Bool -> [(FilePath, [FilePath])] -> IDEAction
  211. grepDirectories regexString caseSensitive dirs = do
  212. grep <- getGrep Nothing
  213. let store = grepStore grep
  214. ideRef <- ask
  215. liftIO $ do
  216. bringPaneToFront grep
  217. forkIO $ do
  218. putMVar (waitingGrep grep) True
  219. putMVar (activeGrep grep) True
  220. takeMVar (waitingGrep grep)
  221. postGUISync $ treeStoreClear store
  222. totalFound <- foldM (\a (dir, subDirs) -> do
  223. nooneWaiting <- isEmptyMVar (waitingGrep grep)
  224. found <- if nooneWaiting
  225. then do
  226. (output, pid) <- runTool "grep" ((if caseSensitive then [] else ["-i"])
  227. ++ ["-r", "-E", "-n", "-I", "--exclude=*~", "--exclude-dir=.svn", regexString] ++ subDirs) (Just dir)
  228. reflectIDE (do
  229. E.run_ $ output $$ do
  230. let max = 1000
  231. step <- EL.isolate (toInteger max) $$ setGrepResults dir
  232. case step of
  233. E.Continue _ -> do
  234. #ifdef MIN_VERSION_process_leksah
  235. liftIO $ interruptProcessGroup pid
  236. #else
  237. liftIO $ interruptProcessGroupOf pid
  238. #endif
  239. liftIO $ postGUISync $ do
  240. nDir <- treeModelIterNChildren store Nothing
  241. liftIO $ treeStoreChange store [nDir-1] (\r -> r{ context = "(Stoped Searching)" })
  242. return ()
  243. EL.dropWhile (const True)
  244. return max
  245. E.Yield n _ -> return n
  246. _ -> return 0) ideRef
  247. else return 0
  248. return $ a + found) 0 dirs
  249. nooneWaiting <- isEmptyMVar (waitingGrep grep)
  250. when nooneWaiting $ postGUISync $ do
  251. nDir <- treeModelIterNChildren store Nothing
  252. treeStoreInsert store [] nDir $ GrepRecord "Search Complete" totalFound "" Nothing
  253. when (totalFound > 0) $
  254. widgetGrabFocus (treeView grep)
  255. takeMVar (activeGrep grep) >> return ()
  256. return ()
  257. setGrepResults :: FilePath -> E.Iteratee ToolOutput IDEM Int
  258. setGrepResults dir = do
  259. ideRef <- lift ask
  260. grep <- lift $ getGrep Nothing
  261. log <- lift $ getLog
  262. let store = grepStore grep
  263. view = treeView grep
  264. nDir <- liftIO $ postGUISync $ treeModelIterNChildren store Nothing
  265. liftIO $ postGUISync $ treeStoreInsert store [] nDir $ GrepRecord dir 0 "" Nothing
  266. EL.foldM (\count line -> do
  267. if isError line
  268. then do
  269. liftIO $ postGUISync $ reflectIDE (defaultLineLogger log line >> return ()) ideRef
  270. return count
  271. else do
  272. case process dir line of
  273. Nothing -> return count
  274. Just record -> liftIO $ do
  275. nooneWaiting <- isEmptyMVar (waitingGrep grep)
  276. when nooneWaiting $ postGUISync $ do
  277. parent <- treeModelGetIter store [nDir]
  278. n <- treeModelIterNChildren store parent
  279. treeStoreInsert store [nDir] n record
  280. treeStoreChange store [nDir] (\r -> r{ line = n+1 })
  281. when (nDir == 0 && n == 0) $
  282. treeViewExpandAll view
  283. return (count+1)) 0
  284. where
  285. process pp (ToolOutput line) =
  286. case parse grepLineParser "" line of
  287. Right record -> Just record{parDir = Just pp}
  288. _ -> Nothing
  289. process _ _ = Nothing
  290. isError (ToolExit ExitSuccess) = False
  291. isError (ToolExit (ExitFailure 1)) = False
  292. isError o = isNothing (process "" o)