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 (pflags, _) <- parseDynamicFlags flags ghcArgs
55 let flags = pflags { hscTarget = HscNothing }
56 GHC.defaultCleanupHandler flags $ do
57 flags <- initPackages flags
58 setSessionDynFlags session flags
59 filedata <- mapM (findthings session) filenames
60 if mode == BothTags || mode == CTags
62 ctagsfile <- openFile "tags" openFileMode
63 writectagsfile ctagsfile filedata
66 if mode == BothTags || mode == ETags
68 etagsfile <- openFile "TAGS" openFileMode
69 writeetagsfile etagsfile filedata
73 -- | getMode takes a list of modes and extract the mode with the
74 -- highest precedence. These are as follows: Both, CTags, ETags
75 -- The default case is Both.
76 getMode :: [Mode] -> Mode
79 getMode (x:xs) = max x (getMode xs)
82 splitArgs :: [String] -> ([String], [String], Bool)
83 -- pull out arguments between -- for GHC
84 splitArgs args = split [] [] False args
85 where split ghc' tags' unbal ("--" : args) = split tags' ghc' (not unbal) args
86 split ghc' tags' unbal (arg : args) = split ghc' (arg:tags') unbal args
87 split ghc' tags' unbal [] = (reverse ghc', reverse tags', unbal)
89 data Mode = ETags | CTags | BothTags | Append | Help deriving (Ord, Eq, Show)
91 options :: [OptDescr Mode]
92 options = [ Option "c" ["ctags"]
93 (NoArg CTags) "generate CTAGS file (ctags)"
94 , Option "e" ["etags"]
95 (NoArg ETags) "generate ETAGS file (etags)"
97 (NoArg BothTags) ("generate both CTAGS and ETAGS")
98 , Option "a" ["append"]
99 (NoArg Append) ("append to existing CTAGS and/or ETAGS file(s)")
100 , Option "h" ["help"] (NoArg Help) "This help"
103 type FileName = String
105 type ThingName = String
107 -- The position of a token or definition
109 FileName -- file name
112 String -- string that makes up that line
115 srcLocToPos :: SrcLoc -> Pos
117 Pos (unpackFS $ srcLocFile loc) (srcLocLine loc) (srcLocCol loc) "bogus"
119 -- A definition we have found
120 data FoundThing = FoundThing ThingName Pos
123 -- Data we have obtained from a file
124 data FileData = FileData FileName [FoundThing]
126 data Token = Token String Pos
130 -- stuff for dealing with ctags output format
132 writectagsfile :: Handle -> [FileData] -> IO ()
133 writectagsfile ctagsfile filedata = do
134 let things = concat $ map getfoundthings filedata
135 mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing x) things
137 getfoundthings :: FileData -> [FoundThing]
138 getfoundthings (FileData filename things) = things
140 dumpthing :: FoundThing -> String
141 dumpthing (FoundThing name (Pos filename line _ _)) =
142 name ++ "\t" ++ filename ++ "\t" ++ (show $ line + 1)
145 -- stuff for dealing with etags output format
147 writeetagsfile :: Handle -> [FileData] -> IO ()
148 writeetagsfile etagsfile filedata = do
149 mapM_ (\x -> hPutStr etagsfile $ e_dumpfiledata x) filedata
151 e_dumpfiledata :: FileData -> String
152 e_dumpfiledata (FileData filename things) =
153 "\x0c\n" ++ filename ++ "," ++ (show thingslength) ++ "\n" ++ thingsdump
155 thingsdump = concat $ map e_dumpthing things
156 thingslength = length thingsdump
158 e_dumpthing :: FoundThing -> String
159 e_dumpthing (FoundThing name (Pos filename line token fullline)) =
160 ---- (concat $ take (token + 1) $ spacedwords fullline)
162 ++ "\x7f" ++ (show line) ++ "," ++ (show $ line+1) ++ "\n"
165 -- like "words", but keeping the whitespace, and so letting us build
168 spacedwords :: String -> [String]
170 spacedwords xs = (blanks ++ wordchars):(spacedwords rest2)
172 (blanks,rest) = span Char.isSpace xs
173 (wordchars,rest2) = span (\x -> not $ Char.isSpace x) rest
176 -- Find the definitions in a file
178 modsummary :: ModuleGraph -> FileName -> Maybe ModSummary
180 List.find matches graph
181 where matches ms = n == msHsFilePath ms
183 modname :: ModSummary -> ModuleName
184 modname summary = moduleName $ ms_mod $ summary
186 findthings :: Session -> FileName -> IO FileData
187 findthings session filename = do
188 setTargets session [Target (TargetFile filename Nothing) Nothing]
190 success <- load session LoadAllTargets --- bring module graph up to date
192 Failed -> do { print "load failed"; return emptyFileData }
194 do print "loaded all targets"
195 graph <- getModuleGraph session
196 print "got modules graph"
197 case modsummary graph filename of
198 Nothing -> panic "loaded a module from a file but then could not find its summary"
200 mod <- checkModule session (modname ms)
201 print "got the module"
203 Nothing -> return emptyFileData
204 Just m -> case renamedSource m of
205 Nothing -> return emptyFileData
206 Just s -> return $ fileData filename s
207 where emptyFileData = FileData filename []
210 fileData :: FileName -> RenamedSource -> FileData
211 fileData filename (group, imports, lie) =
212 -- lie is related to type checking and so is irrelevant
213 -- imports contains import declarations and no definitions
214 FileData filename (boundValues group)
216 boundValues :: HsGroup Name -> [FoundThing]
218 let vals = case hs_valds group of
219 ValBindsOut nest sigs ->
220 [ x | (_rec, binds) <- nest, bind <- bagToList binds, x <- boundThings bind ]
221 tys = concat $ map tyBound (hs_tyclds group)
222 where tyBound ltcd = case unLoc ltcd of
223 ForeignType { tcdLName = n } -> [foundOfLName n]
224 TyData { tcdLName = n } -> [foundOfLName n]
225 TySynonym { tcdLName = n } -> [foundOfLName n]
226 ClassDecl { tcdLName = n } -> [foundOfLName n]
227 fors = concat $ map forBound (hs_fords group)
228 where forBound lford = case unLoc lford of
229 ForeignImport n _ _ -> [foundOfLName n]
230 ForeignExport { } -> []
231 in vals ++ tys ++ fors
233 posOfLocated :: Located a -> Pos
234 posOfLocated lHs = srcLocToPos $ srcSpanStart $ getLoc lHs
236 foundOfLName :: Located Name -> FoundThing
237 foundOfLName id = FoundThing (getOccString $ unLoc id) (posOfLocated id)
239 boundThings :: LHsBind Name -> [FoundThing]
240 boundThings lbinding =
241 let thing = foundOfLName
242 in case unLoc lbinding of
243 FunBind { fun_id = id } -> [thing id]
244 PatBind { pat_lhs = lhs } -> panic "Pattern at top level"
245 VarBind { var_id = id } -> [FoundThing (getOccString id) (posOfLocated lbinding)]
246 AbsBinds { } -> [] -- nothing interesting in a type abstraction