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 let usageString = "Usage: " ++ progName ++ " [OPTION...] [files...]"
36 let (modes, filenames, errs) = getOpt Permute options args
37 if errs /= [] || elem Help modes || filenames == []
40 putStr $ usageInfo usageString options
41 exitWith (ExitFailure 1)
43 let mode = getMode (Append `delete` modes)
44 let openFileMode = if elem Append modes
47 GHC.init (Just "/usr/local/lib/ghc-6.5")
48 GHC.defaultErrorHandler defaultDynFlags $ do
49 session <- newSession JustTypecheck
50 print "created a session"
51 flags <- getSessionDynFlags session
52 (flags, _) <- parseDynamicFlags flags ["-package", "ghc"]
53 GHC.defaultCleanupHandler flags $ do
54 flags <- initPackages flags
55 setSessionDynFlags session flags
56 filedata <- mapM (findthings session) filenames
57 if mode == BothTags || mode == CTags
59 ctagsfile <- openFile "tags" openFileMode
60 writectagsfile ctagsfile filedata
63 if mode == BothTags || mode == ETags
65 etagsfile <- openFile "TAGS" openFileMode
66 writeetagsfile etagsfile filedata
70 -- | getMode takes a list of modes and extract the mode with the
71 -- highest precedence. These are as follows: Both, CTags, ETags
72 -- The default case is Both.
73 getMode :: [Mode] -> Mode
76 getMode (x:xs) = max x (getMode xs)
79 data Mode = ETags | CTags | BothTags | Append | Help deriving (Ord, Eq, Show)
81 options :: [OptDescr Mode]
82 options = [ Option "c" ["ctags"]
83 (NoArg CTags) "generate CTAGS file (ctags)"
84 , Option "e" ["etags"]
85 (NoArg ETags) "generate ETAGS file (etags)"
87 (NoArg BothTags) ("generate both CTAGS and ETAGS")
88 , Option "a" ["append"]
89 (NoArg Append) ("append to existing CTAGS and/or ETAGS file(s)")
90 , Option "h" ["help"] (NoArg Help) "This help"
93 type FileName = String
95 type ThingName = String
97 -- The position of a token or definition
102 String -- string that makes up that line
105 srcLocToPos :: SrcLoc -> Pos
107 Pos (unpackFS $ srcLocFile loc) (srcLocLine loc) (srcLocCol loc) "bogus"
109 -- A definition we have found
110 data FoundThing = FoundThing ThingName Pos
113 -- Data we have obtained from a file
114 data FileData = FileData FileName [FoundThing]
116 data Token = Token String Pos
120 -- stuff for dealing with ctags output format
122 writectagsfile :: Handle -> [FileData] -> IO ()
123 writectagsfile ctagsfile filedata = do
124 let things = concat $ map getfoundthings filedata
125 mapM_ (\x -> hPutStrLn ctagsfile $ dumpthing x) things
127 getfoundthings :: FileData -> [FoundThing]
128 getfoundthings (FileData filename things) = things
130 dumpthing :: FoundThing -> String
131 dumpthing (FoundThing name (Pos filename line _ _)) =
132 name ++ "\t" ++ filename ++ "\t" ++ (show $ line + 1)
135 -- stuff for dealing with etags output format
137 writeetagsfile :: Handle -> [FileData] -> IO ()
138 writeetagsfile etagsfile filedata = do
139 mapM_ (\x -> hPutStr etagsfile $ e_dumpfiledata x) filedata
141 e_dumpfiledata :: FileData -> String
142 e_dumpfiledata (FileData filename things) =
143 "\x0c\n" ++ filename ++ "," ++ (show thingslength) ++ "\n" ++ thingsdump
145 thingsdump = concat $ map e_dumpthing things
146 thingslength = length thingsdump
148 e_dumpthing :: FoundThing -> String
149 e_dumpthing (FoundThing name (Pos filename line token fullline)) =
150 ---- (concat $ take (token + 1) $ spacedwords fullline)
152 ++ "\x7f" ++ (show line) ++ "," ++ (show $ line+1) ++ "\n"
155 -- like "words", but keeping the whitespace, and so letting us build
158 spacedwords :: String -> [String]
160 spacedwords xs = (blanks ++ wordchars):(spacedwords rest2)
162 (blanks,rest) = span Char.isSpace xs
163 (wordchars,rest2) = span (\x -> not $ Char.isSpace x) rest
166 -- Find the definitions in a file
168 modsummary :: ModuleGraph -> FileName -> Maybe ModSummary
170 List.find matches graph
171 where matches ms = n == msHsFilePath ms
173 modname :: ModSummary -> ModuleName
174 modname summary = moduleName $ ms_mod $ summary
176 findthings :: Session -> FileName -> IO FileData
177 findthings session filename = do
178 setTargets session [Target (TargetFile filename Nothing) Nothing]
180 success <- load session LoadAllTargets --- bring module graph up to date
182 Failed -> do { print "load failed"; return emptyFileData }
184 do print "loaded all targets"
185 graph <- getModuleGraph session
186 print "got modules graph"
187 case modsummary graph filename of
188 Nothing -> panic "loaded a module from a file but then could not find its summary"
190 mod <- checkModule session (modname ms)
191 print "got the module"
193 Nothing -> return emptyFileData
194 Just m -> case renamedSource m of
195 Nothing -> return emptyFileData
196 Just s -> return $ fileData filename s
197 where emptyFileData = FileData filename []
200 fileData :: FileName -> RenamedSource -> FileData
201 fileData filename (group, imports, lie) =
202 -- lie is related to type checking and so is irrelevant
203 -- imports contains import declarations and no definitions
204 FileData filename (boundValues group)
206 boundValues :: HsGroup Name -> [FoundThing]
208 case hs_valds group of
209 ValBindsOut nest sigs ->
210 [ x | (_rec, binds) <- nest, bind <- bagToList binds, x <- boundThings bind ]
212 posOfLocated :: Located a -> Pos
213 posOfLocated lHs = srcLocToPos $ srcSpanStart $ getLoc lHs
215 boundThings :: LHsBind Name -> [FoundThing]
216 boundThings lbinding =
217 let thing id = FoundThing (getOccString $ unLoc id) (posOfLocated id)
218 in case unLoc lbinding of
219 FunBind { fun_id = id } -> [thing id]
220 PatBind { pat_lhs = lhs } -> patBoundIds lhs
221 -- VarBind { var_id = id } -> [thing id]
225 patBoundIds :: a -> b
226 patBoundIds _ = panic "not on your life"
228 -- actually pick up definitions
230 findstuff :: [Token] -> [FoundThing]
231 findstuff ((Token "data" _):(Token name pos):xs) =
232 FoundThing name pos : (getcons xs) ++ (findstuff xs)
233 findstuff ((Token "newtype" _):(Token name pos):xs) =
234 FoundThing name pos : findstuff xs
235 findstuff ((Token "type" _):(Token name pos):xs) =
236 FoundThing name pos : findstuff xs
237 findstuff ((Token name pos):(Token "::" _):xs) =
238 FoundThing name pos : findstuff xs
239 findstuff (x:xs) = findstuff xs
243 -- get the constructor definitions, knowing that a datatype has just started
245 getcons :: [Token] -> [FoundThing]
246 getcons ((Token "=" _):(Token name pos):xs) =
247 FoundThing name pos : getcons2 xs
248 getcons (x:xs) = getcons xs
252 getcons2 ((Token "=" _):xs) = []
253 getcons2 ((Token "|" _):(Token name pos):xs) =
254 FoundThing name pos : getcons2 xs
255 getcons2 (x:xs) = getcons2 xs