PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/server/Scion/Server/Commands.hs

https://github.com/FunctorSalad/scion
Haskell | 596 lines | 475 code | 74 blank | 47 comment | 5 complexity | c64b3c7eb0bfdce476e620178b08cf40 MD5 | raw file
  1. {-# LANGUAGE ScopedTypeVariables, CPP, PatternGuards,
  2. ExistentialQuantification #-} -- for 'Cmd'
  3. {-# OPTIONS_GHC -fno-warn-orphans #-}
  4. -- |
  5. -- Module : Scion.Server.Commands
  6. -- Copyright : (c) Thomas Schilling 2008
  7. -- License : BSD-style
  8. --
  9. -- Maintainer : nominolo@gmail.com
  10. -- Stability : experimental
  11. -- Portability : portable
  12. --
  13. -- Commands provided by the server.
  14. --
  15. -- TODO: Need some way to document the wire protocol. Autogenerate?
  16. --
  17. module Scion.Server.Commands (
  18. handleRequest, malformedRequest, -- allCommands, allCommands',
  19. -- these are reused in the vim interface
  20. supportedPragmas, allExposedModules,
  21. ) where
  22. import Prelude as P
  23. import Scion.Types
  24. import Scion.Types.Notes
  25. import Scion.Types.Outline
  26. import Scion.Utils
  27. import Scion.Session
  28. import Scion.Server.Protocol
  29. import Scion.Inspect
  30. import Scion.Inspect.DefinitionSite
  31. import Scion.Inspect.PackageDB
  32. import Scion.Cabal
  33. import Scion.Ghc hiding ( (<+>) )
  34. import DynFlags ( supportedLanguages, allFlags )
  35. import Exception
  36. import FastString
  37. import PprTyThing ( pprTypeForUser )
  38. import qualified Outputable as O ( (<+>) )
  39. import Control.Applicative
  40. import Control.Monad
  41. import Data.List ( nub )
  42. import Data.Time.Clock ( NominalDiffTime )
  43. import System.Exit ( ExitCode(..) )
  44. import Text.JSON
  45. import qualified Data.Map as M
  46. import qualified Data.MultiSet as MS
  47. import GHC.SYB.Utils
  48. #ifndef HAVE_PACKAGE_DB_MODULES
  49. import UniqFM ( eltsUFM )
  50. import Packages ( pkgIdMap )
  51. import Distribution.InstalledPackageInfo
  52. #endif
  53. type KeepGoing = Bool
  54. -- a scion request is JS object with 3 keys:
  55. -- method: the method to be called
  56. -- params: arguments to be passed
  57. -- id : this value will be passed back to the client
  58. -- to identify a reply to a specific request
  59. -- asynchronous requests will be implemented in the future
  60. handleRequest :: JSValue -> ScionM (JSValue, KeepGoing)
  61. handleRequest (JSObject req) =
  62. let request = do JSString method <- lookupKey req "method"
  63. params <- lookupKey req "params"
  64. seq_id <- lookupKey req "id"
  65. return (fromJSString method, params, seq_id)
  66. in
  67. case request of
  68. Error _ -> return (malformedRequest, True)
  69. Ok (method, params, seq_id)
  70. | method == "quit" -> return (makeObject
  71. [("version", str "0.1")
  72. ,("result", JSNull)
  73. ,("id", seq_id)], False)
  74. | otherwise ->
  75. case M.lookup method allCmds of
  76. Nothing -> return (unknownCommand seq_id, True)
  77. Just (Cmd _ arg_parser) ->
  78. decode_params params arg_parser seq_id
  79. where
  80. decode_params JSNull arg_parser seq_id =
  81. decode_params (makeObject []) arg_parser seq_id
  82. decode_params (JSObject args) arg_parser seq_id =
  83. case unPa arg_parser args of
  84. Left err -> return (paramParseError seq_id err, True)
  85. Right act -> do
  86. r <- handleScionException act
  87. case r of
  88. Error msg -> return (commandExecError seq_id msg, True)
  89. Ok a ->
  90. return (makeObject
  91. [("version", str "0.1")
  92. ,("id", seq_id)
  93. ,("result", showJSON a)], True)
  94. decode_params _ _ seq_id =
  95. return (paramParseError seq_id "Params not an object", True)
  96. handleRequest _ = do
  97. return (malformedRequest, True)
  98. malformedRequest :: JSValue
  99. malformedRequest = makeObject
  100. [("version", str "0.1")
  101. ,("error", makeObject
  102. [("name", str "MalformedRequest")
  103. ,("message", str "Request was not a proper request object.")])]
  104. unknownCommand :: JSValue -> JSValue
  105. unknownCommand seq_id = makeObject
  106. [("version", str "0.1")
  107. ,("id", seq_id)
  108. ,("error", makeObject
  109. [("name", str "UnknownCommand")
  110. ,("message", str "The requested method is not supported.")])]
  111. paramParseError :: JSValue -> String -> JSValue
  112. paramParseError seq_id msg = makeObject
  113. [("version", str "0.1")
  114. ,("id", seq_id)
  115. ,("error", makeObject
  116. [("name", str "ParamParseError")
  117. ,("message", str msg)])]
  118. commandExecError :: JSValue -> String -> JSValue
  119. commandExecError seq_id msg = makeObject
  120. [("version", str "0.1")
  121. ,("id", seq_id)
  122. ,("error", makeObject
  123. [("name", str "CommandFailed")
  124. ,("message", str msg)])]
  125. allCmds :: M.Map String Cmd
  126. allCmds = M.fromList [ (cmdName c, c) | c <- allCommands ]
  127. ------------------------------------------------------------------------
  128. -- | All Commands supported by this Server.
  129. allCommands :: [Cmd]
  130. allCommands =
  131. [ cmdConnectionInfo
  132. , cmdListSupportedLanguages
  133. , cmdListSupportedPragmas
  134. , cmdListSupportedFlags
  135. , cmdListCabalComponents
  136. , cmdListCabalConfigurations
  137. , cmdWriteSampleConfig
  138. , cmdListRdrNamesInScope
  139. , cmdListExposedModules
  140. , cmdCurrentComponent
  141. , cmdSetVerbosity
  142. , cmdGetVerbosity
  143. , cmdLoad
  144. , cmdDumpSources
  145. , cmdThingAtPoint
  146. , cmdSetGHCVerbosity
  147. , cmdBackgroundTypecheckFile
  148. , cmdBackgroundTypecheckArbitrary
  149. , cmdAddCmdLineFlag
  150. , cmdForceUnload
  151. , cmdDumpDefinedNames
  152. , cmdDefinedNames
  153. , cmdNameDefinitions
  154. , cmdIdentify
  155. , cmdDumpModuleGraph
  156. , cmdDumpNameDB
  157. , cmdToplevelNames
  158. , cmdOutline
  159. ]
  160. ------------------------------------------------------------------------------
  161. type OkErr a = Result a
  162. -- encode expected errors as proper return values
  163. handleScionException :: ScionM a -> ScionM (OkErr a)
  164. handleScionException m = ((((do
  165. r <- m
  166. return (Ok r)
  167. `gcatch` \(e :: SomeScionException) -> return (Error (show e)))
  168. `gcatch` \(e' :: GhcException) ->
  169. case e' of
  170. Panic _ -> throw e'
  171. InstallationError _ -> throw e'
  172. Interrupted -> throw e'
  173. _ -> return (Error (show e')))
  174. `gcatch` \(e :: ExitCode) ->
  175. -- client code may not exit the server!
  176. return (Error (show e)))
  177. `gcatch` \(e :: IOError) ->
  178. return (Error (show e)))
  179. -- `gcatch` \(e :: SomeException) ->
  180. -- liftIO (print e) >> liftIO (throwIO e)
  181. ------------------------------------------------------------------------------
  182. newtype Pa a = Pa { unPa :: JSObject JSValue -> Either String a }
  183. instance Monad Pa where
  184. return x = Pa $ \_ -> Right x
  185. m >>= k = Pa $ \req ->
  186. case unPa m req of
  187. Left err -> Left err
  188. Right a -> unPa (k a) req
  189. fail msg = Pa $ \_ -> Left msg
  190. withReq :: (JSObject JSValue -> Pa a) -> Pa a
  191. withReq f = Pa $ \req -> unPa (f req) req
  192. reqArg' :: JSON a => String -> (a -> b) -> (b -> r) -> Pa r
  193. reqArg' name trans f = withReq $ \req ->
  194. case lookupKey req name of
  195. Error _ -> fail $ "required arg missing: " ++ name
  196. Ok x ->
  197. case readJSON x of
  198. Error m -> fail $ "could not decode: " ++ name ++ " - " ++ m
  199. Ok a -> return (f (trans a))
  200. optArg' :: JSON a => String -> b -> (a -> b) -> (b -> r) -> Pa r
  201. optArg' name dflt trans f = withReq $ \req ->
  202. case lookupKey req name of
  203. Error _ -> return (f dflt)
  204. Ok x ->
  205. case readJSON x of
  206. Error n -> fail $ "could not decode: " ++ name ++ " - " ++ n
  207. Ok a -> return (f (trans a))
  208. reqArg :: JSON a => String -> (a -> r) -> Pa r
  209. reqArg name f = reqArg' name id f
  210. optArg :: JSON a => String -> a -> (a -> r) -> Pa r
  211. optArg name dflt f = optArg' name dflt id f
  212. noArgs :: r -> Pa r
  213. noArgs = return
  214. infixr 1 <&>
  215. -- | Combine two arguments.
  216. --
  217. -- TODO: explain type
  218. (<&>) :: (a -> Pa b)
  219. -> (b -> Pa c)
  220. -> a -> Pa c
  221. a1 <&> a2 = \f -> do f' <- a1 f; a2 f'
  222. data Cmd = forall a. JSON a => Cmd String (Pa (ScionM a))
  223. cmdName :: Cmd -> String
  224. cmdName (Cmd n _) = n
  225. ------------------------------------------------------------------------
  226. -- | Used by the client to initialise the connection.
  227. cmdConnectionInfo :: Cmd
  228. cmdConnectionInfo = Cmd "connection-info" $ noArgs worker
  229. where
  230. worker = let pid = 0 :: Int in -- TODO for linux: System.Posix.Internals (c_getpid)
  231. return $ makeObject
  232. [("version", showJSON scionVersion)
  233. ,("pid", showJSON pid)]
  234. decodeBool :: JSValue -> Bool
  235. decodeBool (JSBool b) = b
  236. decodeBool _ = error "no bool"
  237. decodeExtraArgs :: JSValue -> [String]
  238. decodeExtraArgs JSNull = []
  239. decodeExtraArgs (JSString s) =
  240. words (fromJSString s) -- TODO: check shell-escaping
  241. decodeExtraArgs (JSArray arr) =
  242. [ fromJSString s | JSString s <- arr ]
  243. instance JSON Component where
  244. readJSON obj = do
  245. case readJSON obj of
  246. Ok (c :: CabalComponent) -> return $ Component c
  247. _ -> case readJSON obj of
  248. Ok (c :: FileComp) -> return $ Component c
  249. _ -> fail $ "Unknown component" ++ show obj
  250. showJSON (Component c) = showJSON c
  251. instance JSON CompilationResult where
  252. showJSON (CompilationResult suc notes time) =
  253. makeObject [("succeeded", JSBool suc)
  254. ,("notes", showJSON notes)
  255. ,("duration", showJSON time)]
  256. readJSON (JSObject obj) = do
  257. JSBool suc <- lookupKey obj "succeeded"
  258. notes <- readJSON =<< lookupKey obj "notes"
  259. dur <- readJSON =<< lookupKey obj "duration"
  260. return (CompilationResult suc notes dur)
  261. readJSON _ = fail "compilation-result"
  262. instance (Ord a, JSON a) => JSON (MS.MultiSet a) where
  263. showJSON ms = showJSON (MS.toList ms)
  264. readJSON o = MS.fromList <$> readJSON o
  265. instance JSON Note where
  266. showJSON (Note note_kind loc msg) =
  267. makeObject [("kind", showJSON note_kind)
  268. ,("location", showJSON loc)
  269. ,("message", JSString (toJSString msg))]
  270. readJSON (JSObject obj) = do
  271. note_kind <- readJSON =<< lookupKey obj "kind"
  272. loc <- readJSON =<< lookupKey obj "location"
  273. JSString s <- lookupKey obj "message"
  274. return (Note note_kind loc (fromJSString s))
  275. readJSON _ = fail "note"
  276. str :: String -> JSValue
  277. str = JSString . toJSString
  278. instance JSON NoteKind where
  279. showJSON ErrorNote = JSString (toJSString "error")
  280. showJSON WarningNote = JSString (toJSString "warning")
  281. showJSON InfoNote = JSString (toJSString "info")
  282. showJSON OtherNote = JSString (toJSString "other")
  283. readJSON (JSString s) =
  284. case lookup (fromJSString s)
  285. [("error", ErrorNote), ("warning", WarningNote)
  286. ,("info", InfoNote), ("other", OtherNote)]
  287. of Just x -> return x
  288. Nothing -> fail "note-kind"
  289. readJSON _ = fail "note-kind"
  290. instance JSON Location where
  291. showJSON loc | not (isValidLoc loc) =
  292. makeObject [("no-location", str (noLocText loc))]
  293. showJSON loc | (src, l0, c0, l1, c1) <- viewLoc loc =
  294. makeObject [case src of
  295. FileSrc f -> ("file", str (toFilePath f))
  296. OtherSrc s -> ("other", str s)
  297. ,("region", JSArray (map showJSON [l0,c0,l1,c1]))]
  298. readJSON (JSObject obj) = do
  299. src <- (do JSString f <- lookupKey obj "file"
  300. return (FileSrc (mkAbsFilePath "/" (fromJSString f))))
  301. <|>
  302. (do JSString s <- lookupKey obj "other"
  303. return (OtherSrc (fromJSString s)))
  304. JSArray ls <- lookupKey obj "region"
  305. case mapM readJSON ls of
  306. Ok [l0,c0,l1,c1] -> return (mkLocation src l0 c0 l1 c1)
  307. _ -> fail "region"
  308. readJSON _ = fail "location"
  309. instance JSON NominalDiffTime where
  310. showJSON t = JSRational True (fromRational (toRational t))
  311. readJSON (JSRational _ n) = return $ fromRational (toRational n)
  312. readJSON _ = fail "diff-time"
  313. instance JSON OutlineDef where
  314. showJSON t =
  315. makeObject $
  316. [("name", str $ case od_name t of
  317. Left n -> showSDocUnqual $ ppr n
  318. Right s -> s)
  319. ,("location", showJSON $ od_loc t)
  320. ,("block", showJSON $ od_block t)
  321. ,("type", str $ od_type t)]
  322. ++
  323. (case od_parentName t of
  324. Just (n,typ) ->
  325. [("parent", makeObject [("name", str $ showSDocUnqual $ ppr $ n)
  326. ,("type", str typ)])]
  327. Nothing -> [])
  328. readJSON _ = fail "OutlineDef"
  329. cmdListSupportedLanguages :: Cmd
  330. cmdListSupportedLanguages = Cmd "list-supported-languages" $ noArgs cmd
  331. where cmd = return (map toJSString supportedLanguages)
  332. cmdListSupportedPragmas :: Cmd
  333. cmdListSupportedPragmas =
  334. Cmd "list-supported-pragmas" $ noArgs $ return supportedPragmas
  335. supportedPragmas :: [String]
  336. supportedPragmas =
  337. [ "OPTIONS_GHC", "LANGUAGE", "INCLUDE", "WARNING", "DEPRECATED"
  338. , "INLINE", "NOINLINE", "RULES", "SPECIALIZE", "UNPACK", "SOURCE"
  339. , "SCC"
  340. , "LINE" -- XXX: only used by code generators, still include?
  341. ]
  342. cmdListSupportedFlags :: Cmd
  343. cmdListSupportedFlags =
  344. Cmd "list-supported-flags" $ noArgs $ return (nub allFlags)
  345. cmdListRdrNamesInScope :: Cmd
  346. cmdListRdrNamesInScope =
  347. Cmd "list-rdr-names-in-scope" $ noArgs $ cmd
  348. where cmd = do
  349. rdr_names <- getNamesInScope
  350. return (map (showSDoc . ppr) rdr_names)
  351. cmdListCabalComponents :: Cmd
  352. cmdListCabalComponents =
  353. Cmd "list-cabal-components" $ reqArg' "cabal-file" fromJSString $ cmd
  354. where cmd cabal_file = cabalProjectComponents cabal_file
  355. -- return all cabal configurations.
  356. -- currently this just globs for * /setup-config
  357. -- in the future you may write a config file describing the most common configuration settings
  358. cmdListCabalConfigurations :: Cmd
  359. cmdListCabalConfigurations =
  360. Cmd "list-cabal-configurations" $
  361. reqArg' "cabal-file" fromJSString <&>
  362. optArg' "type" "uniq" id <&>
  363. optArg' "scion-default" False decodeBool $ cmd
  364. where cmd cabal_file type' scionDefault = liftM showJSON $ cabalConfigurations cabal_file type' scionDefault
  365. cmdWriteSampleConfig :: Cmd
  366. cmdWriteSampleConfig =
  367. Cmd "write-sample-config" $
  368. reqArg "file" $ cmd
  369. where cmd fp = liftIO $ writeSampleConfig fp
  370. allExposedModules :: ScionM [ModuleName]
  371. #ifdef HAVE_PACKAGE_DB_MODULES
  372. allExposedModules = map moduleName `fmap` packageDbModules True
  373. #else
  374. -- This implementation requires our Cabal to be the same as GHC's.
  375. allExposedModules = do
  376. dflags <- getSessionDynFlags
  377. let pkg_db = pkgIdMap (pkgState dflags)
  378. return $ P.concat (map exposedModules (filter exposed (eltsUFM pkg_db)))
  379. #endif
  380. cmdListExposedModules :: Cmd
  381. cmdListExposedModules = Cmd "list-exposed-modules" $ noArgs $ cmd
  382. where cmd = do
  383. mod_names <- allExposedModules
  384. return $ map (showSDoc . ppr) mod_names
  385. cmdSetGHCVerbosity :: Cmd
  386. cmdSetGHCVerbosity =
  387. Cmd "set-ghc-verbosity" $ reqArg "level" $ setGHCVerbosity
  388. cmdBackgroundTypecheckFile :: Cmd
  389. cmdBackgroundTypecheckFile =
  390. Cmd "background-typecheck-file" $ reqArg' "file" fromJSString $ cmd
  391. where cmd fname = backgroundTypecheckFile fname
  392. cmdBackgroundTypecheckArbitrary :: Cmd
  393. cmdBackgroundTypecheckArbitrary =
  394. Cmd "background-typecheck-arbitrary" $
  395. reqArg' "file" fromJSString <&>
  396. reqArg' "contents" fromJSString $ cmd
  397. where cmd fname contents = backgroundTypecheckArbitrary fname contents
  398. cmdForceUnload :: Cmd
  399. cmdForceUnload = Cmd "force-unload" $ noArgs $ unload
  400. cmdAddCmdLineFlag :: Cmd
  401. cmdAddCmdLineFlag =
  402. Cmd "add-command-line-flag" $
  403. optArg' "flag" "" fromJSString <&>
  404. optArg' "flags" [] (map fromJSString) $ cmd
  405. where cmd flag flags' = do
  406. addCmdLineFlags $ (if flag == "" then [] else [flag]) ++ flags'
  407. return JSNull
  408. cmdThingAtPoint :: Cmd
  409. cmdThingAtPoint =
  410. Cmd "thing-at-point" $
  411. reqArg "file" <&> reqArg "line" <&> reqArg "column" $ cmd
  412. where
  413. cmd fname line col = do
  414. let loc = srcLocSpan $ mkSrcLoc (fsLit fname) line col
  415. tc_res <- gets bgTcCache
  416. -- TODO: don't return something of type @Maybe X@. The default
  417. -- serialisation sucks.
  418. case tc_res of
  419. Just (Typechecked tcm) -> do
  420. --let Just (src, _, _, _, _) = renamedSource tcm
  421. let src = typecheckedSource tcm
  422. --let in_range = const True
  423. let in_range = overlaps loc
  424. let r = findHsThing in_range src
  425. --return (Just (showSDoc (ppr $ S.toList r)))
  426. unqual <- unqualifiedForModule tcm
  427. case pathToDeepest r of
  428. Nothing -> return (Just "no info")
  429. Just (x,xs) ->
  430. --return $ Just (showSDoc (ppr x O.$$ ppr xs))
  431. case typeOf (x,xs) of
  432. Just t ->
  433. return $ Just $ showSDocForUser unqual
  434. (prettyResult x O.<+> dcolon O.<+>
  435. pprTypeForUser True t)
  436. _ -> return (Just "No info") --(Just (showSDocDebug (ppr x O.$$ ppr xs )))
  437. _ -> return Nothing
  438. cmdToplevelNames :: Cmd
  439. cmdToplevelNames=
  440. Cmd "top-level-names" $ noArgs $ cmd
  441. where
  442. cmd =do
  443. tc_res <- gets bgTcCache
  444. case tc_res of
  445. Just (Typechecked tcm) -> do
  446. return $ map (showSDocDump . ppr) $ toplevelNames tcm
  447. _ -> return []
  448. cmdOutline :: Cmd
  449. cmdOutline =
  450. Cmd "outline" $ optArg' "trimFile" True decodeBool $ cmd
  451. where
  452. cmd trim = do
  453. root_dir <- projectRootDir
  454. tc_res <- gets bgTcCache
  455. case tc_res of
  456. Just (Typechecked tcm) -> do
  457. let f = if trim then trimLocationFile else id
  458. return $ f $ outline root_dir tcm
  459. _ -> return []
  460. cmdDumpSources :: Cmd
  461. cmdDumpSources = Cmd "dump-sources" $ noArgs $ cmd
  462. where
  463. cmd = do
  464. tc_res <- gets bgTcCache
  465. case tc_res of
  466. Just (Typechecked tcm)
  467. | Just rn <- renamedSourceGroup `fmap` renamedSource tcm ->
  468. do let tc = typecheckedSource tcm
  469. liftIO $ putStrLn $ showSDocDump $ ppr rn
  470. liftIO $ putStrLn $ showData TypeChecker 2 tc
  471. return ()
  472. _ -> return ()
  473. cmdLoad :: Cmd
  474. cmdLoad = Cmd "load" $ reqArg "component" <&>
  475. optArg' "output" False decodeBool $ cmd
  476. where
  477. cmd comp output= do
  478. loadComponent' comp output
  479. cmdSetVerbosity :: Cmd
  480. cmdSetVerbosity =
  481. Cmd "set-verbosity" $ reqArg "level" $ cmd
  482. where cmd v = setVerbosity (intToVerbosity v)
  483. cmdGetVerbosity :: Cmd
  484. cmdGetVerbosity = Cmd "get-verbosity" $ noArgs $ verbosityToInt <$> getVerbosity
  485. -- rename to GetCurrentComponent?
  486. cmdCurrentComponent :: Cmd
  487. cmdCurrentComponent = Cmd "current-component" $ noArgs $ getActiveComponent
  488. cmdDumpDefinedNames :: Cmd
  489. cmdDumpDefinedNames = Cmd "dump-defined-names" $ noArgs $ cmd
  490. where
  491. cmd = do db <- gets defSiteDB
  492. liftIO $ putStrLn $ dumpDefSiteDB db
  493. cmdDefinedNames :: Cmd
  494. cmdDefinedNames = Cmd "defined-names" $ noArgs $ cmd
  495. where cmd = definedNames <$> gets defSiteDB
  496. cmdNameDefinitions :: Cmd
  497. cmdNameDefinitions =
  498. Cmd "name-definitions" $ reqArg' "name" fromJSString $ cmd
  499. where cmd nm = do
  500. db <- gets defSiteDB
  501. let locs = map fst $ lookupDefSite db nm
  502. return locs
  503. cmdIdentify :: Cmd
  504. cmdIdentify =
  505. Cmd "client-identify" $ reqArg' "name" fromJSString $ cmd
  506. where cmd c = modifySessionState $ \s -> s { client = c }
  507. cmdDumpModuleGraph :: Cmd
  508. cmdDumpModuleGraph =
  509. Cmd "dump-module-graph" $ noArgs $ cmd
  510. where
  511. cmd = do
  512. mg <- getModuleGraph
  513. liftIO $ printDump (ppr mg)
  514. return ()
  515. cmdDumpNameDB :: Cmd
  516. cmdDumpNameDB =
  517. Cmd "dump-name-db" $ noArgs $ cmd
  518. where
  519. cmd = do
  520. db <- buildNameDB
  521. dumpNameDB db
  522. return ()