4 import DynFlags(GhcMode, defaultDynFlags)
7 import HscTypes (msHsFilePath)
13 import System.Environment
14 import System.Console.GetOpt
18 -- search for definitions of things
19 -- we do this by parsing the source and grabbing top-level definitions
21 -- We generate both CTAGS and ETAGS format tags files
22 -- The former is for use in most sensible editors, while EMACS uses ETAGS
26 placateGhc = defaultErrorHandler defaultDynFlags $ do
27 GHC.init (Just "/usr/local/lib/ghc-6.5") -- or your build tree!
33 progName <- getProgName
35 "Usage: " ++ progName ++ " [OPTION...] [-- GHC OPTION... --] [files...]"
37 let (ghcArgs, ourArgs, unbalanced) = splitArgs args
38 let (modes, filenames, errs) = getOpt Permute options ourArgs
39 if unbalanced || errs /= [] || elem Help modes || filenames == []
42 putStr $ usageInfo usageString options
43 exitWith (ExitFailure 1)
45 let mode = getMode (Append `delete` modes)
46 let openFileMode = if elem Append modes
49 GHC.init (Just "/usr/local/lib/ghc-6.5")
50 GHC.defaultErrorHandler defaultDynFlags $ do
51 session <- newSession JustTypecheck
52 print "created a session"
53 flags <- getSessionDynFlags session
54 (flags, _) <- parseDynamicFlags flags ghcArgs
55 GHC.defaultCleanupHandler flags $ do
56 flags <- initPackages flags
57 setSessionDynFlags session flags
58 filedata <- mapM (findthings session) filenames
59 if mode == BothTags || mode == CTags
61 ctagsfile <- openFile "tags" openFileMode
62 writectagsfile ctagsfile filedata
65 if mode == BothTags || mode == ETags
67 etagsfile <- openFile "TAGS" openFileMode
68 writeetagsfile etagsfile filedata
72 -- | getMode takes a list of modes and extract the mode with the
73 -- highest precedence. These are as follows: Both, CTags, ETags
74 -- The default case is Both.
75 getMode :: [Mode] -> Mode
78 getMode (x:xs) = max x (getMode xs)
81 splitArgs :: [String] -> ([String], [String], Bool)
82 -- pull out arguments between -- for GHC
83 splitArgs args = split [] [] False args
84 where split ghc' tags' unbal ("--" : args) = split tags' ghc' (not unbal) args
85 split ghc' tags' unbal (arg : args) = split ghc' (arg:tags') unbal args
86 split ghc' tags' unbal [] = (reverse ghc', reverse tags', unbal)
88 data Mode = ETags | CTags | BothTags | Append | Help deriving (Ord, Eq, Show)
90 options :: [OptDescr Mode]
91 options = [ Option "c" ["ctags"]
92 (NoArg CTags) "generate CTAGS file (ctags)"
93 , Option "e" ["etags"]
94 (NoArg ETags) "generate ETAGS file (etags)"
96 (NoArg BothTags) ("generate both CTAGS and ETAGS")
97 , Option "a" ["append"]
98 (NoArg Append) ("append to existing CTAGS and/or ETAGS file(s)")
99 , Option "h" ["help"] (NoArg Help) "This help"
102 type FileName = String
104 type ThingName = String
106 -- The position of a token or definition
108 FileName -- file name
111 String -- string that makes up that line
114 srcLocToPos :: SrcLoc -> Pos
116 Pos (unpackFS $ srcLocFile loc) (srcLocLine loc) (srcLocCol loc) "bogus"
118 -- A definition we have found
119 data FoundThing = FoundThing ThingName Pos
122 -- Data we have obtained from a file
123 data FileData = FileData FileName [FoundThing]
125 data Token = Token String Pos
129 -- stuff for dealing with ctags output format
131 writectagsfile :: Handle -> [FileData] -> IO ()
132 writectagsfile ctagsfile filedata = do
133 let things = concat $ map getfoundthings filedata
134 mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing x) things
136 getfoundthings :: FileData -> [FoundThing]
137 getfoundthings (FileData filename things) = things
139 dumpthing :: FoundThing -> String
140 dumpthing (FoundThing name (Pos filename line _ _)) =
141 name ++ "\t" ++ filename ++ "\t" ++ (show $ line + 1)
144 -- stuff for dealing with etags output format
146 writeetagsfile :: Handle -> [FileData] -> IO ()
147 writeetagsfile etagsfile filedata = do
148 mapM_ (\x -> hPutStr etagsfile $ e_dumpfiledata x) filedata
150 e_dumpfiledata :: FileData -> String
151 e_dumpfiledata (FileData filename things) =
152 "\x0c\n" ++ filename ++ "," ++ (show thingslength) ++ "\n" ++ thingsdump
154 thingsdump = concat $ map e_dumpthing things
155 thingslength = length thingsdump
157 e_dumpthing :: FoundThing -> String
158 e_dumpthing (FoundThing name (Pos filename line token fullline)) =
159 ---- (concat $ take (token + 1) $ spacedwords fullline)
161 ++ "\x7f" ++ (show line) ++ "," ++ (show $ line+1) ++ "\n"
164 -- like "words", but keeping the whitespace, and so letting us build
167 spacedwords :: String -> [String]
169 spacedwords xs = (blanks ++ wordchars):(spacedwords rest2)
171 (blanks,rest) = span Char.isSpace xs
172 (wordchars,rest2) = span (\x -> not $ Char.isSpace x) rest
175 -- Find the definitions in a file
177 modsummary :: ModuleGraph -> FileName -> Maybe ModSummary
179 List.find matches graph
180 where matches ms = n == msHsFilePath ms
182 modname :: ModSummary -> ModuleName
183 modname summary = moduleName $ ms_mod $ summary
185 findthings :: Session -> FileName -> IO FileData
186 findthings session filename = do
187 setTargets session [Target (TargetFile filename Nothing) Nothing]
189 success <- load session LoadAllTargets --- bring module graph up to date
191 Failed -> do { print "load failed"; return emptyFileData }
193 do print "loaded all targets"
194 graph <- getModuleGraph session
195 print "got modules graph"
196 case modsummary graph filename of
197 Nothing -> panic "loaded a module from a file but then could not find its summary"
199 mod <- checkModule session (modname ms)
200 print "got the module"
202 Nothing -> return emptyFileData
203 Just m -> case renamedSource m of
204 Nothing -> return emptyFileData
205 Just s -> return $ fileData filename s
206 where emptyFileData = FileData filename []
209 fileData :: FileName -> RenamedSource -> FileData
210 fileData filename (group, imports, lie) =
211 -- lie is related to type checking and so is irrelevant
212 -- imports contains import declarations and no definitions
213 FileData filename (boundValues group)
215 boundValues :: HsGroup Name -> [FoundThing]
217 let vals = case hs_valds group of
218 ValBindsOut nest sigs ->
219 [ x | (_rec, binds) <- nest, bind <- bagToList binds, x <- boundThings bind ]
220 tys = concat $ map tyBound (hs_tyclds group)
221 where tyBound ltcd = case unLoc ltcd of
222 ForeignType { tcdLName = n } -> [foundOfLName n]
223 TyData { tcdLName = n } -> [foundOfLName n]
224 TySynonym { tcdLName = n } -> [foundOfLName n]
225 ClassDecl { tcdLName = n } -> [foundOfLName n]
226 fors = concat $ map forBound (hs_fords group)
227 where forBound lford = case unLoc lford of
228 ForeignImport n _ _ -> [foundOfLName n]
229 ForeignExport { } -> []
230 in vals ++ tys ++ fors
232 posOfLocated :: Located a -> Pos
233 posOfLocated lHs = srcLocToPos $ srcSpanStart $ getLoc lHs
235 foundOfLName :: Located Name -> FoundThing
236 foundOfLName id = FoundThing (getOccString $ unLoc id) (posOfLocated id)
238 boundThings :: LHsBind Name -> [FoundThing]
239 boundThings lbinding =
240 let thing = foundOfLName
241 in case unLoc lbinding of
242 FunBind { fun_id = id } -> [thing id]
243 PatBind { pat_lhs = lhs } -> panic "Pattern at top level"
244 VarBind { var_id = id } -> [FoundThing (getOccString id) (posOfLocated lbinding)]
245 AbsBinds { } -> [] -- nothing interesting in a type abstraction