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