1-----------------------------------------------------------------------------
4-- Description : The server accepting commands for psc-ide
5-- Copyright : Christoph Hegemann 2016
6-- License : MIT (http://opensource.org/licenses/MIT)
8-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9-- Stability : experimental
12-- The server accepting commands for psc-ide
13-----------------------------------------------------------------------------
15{-# LANGUAGE ConstraintKinds #-}
16{-# LANGUAGE FlexibleContexts #-}
17{-# LANGUAGE MultiParamTypeClasses #-}
18{-# LANGUAGE OverloadedStrings #-}
19{-# LANGUAGE PackageImports #-}
20{-# LANGUAGE TemplateHaskell #-}
27import Control.Concurrent (forkFinally)
28import Control.Concurrent.STM
29import Control.Exception (bracketOnError, catchJust)
31import Control.Monad.Error.Class
32import "monad-logger" Control.Monad.Logger
33import Control.Monad.Reader
34import Control.Monad.Trans.Except
35import qualified Data.Text as T
36import qualified Data.Text.IO as T
37import Data.Version (showVersion)
38import Language.PureScript.Ide
39import Language.PureScript.Ide.Util
40import Language.PureScript.Ide.Error
41import Language.PureScript.Ide.Types
42import Language.PureScript.Ide.Watcher
43import Network hiding (socketPort)
44import Network.BSD (getProtocolNumber)
45import Network.Socket hiding (PortNumber, Type,
47import Options.Applicative
48import System.Directory
51import System.IO.Error (isEOFError)
53import qualified Paths_purescript as Paths
55-- "Borrowed" from the Idris Compiler
56-- Copied from upstream impl of listenOn
57-- bound to localhost interface instead of iNADDR_ANY
58listenOnLocalhost :: PortID -> IO Socket
59listenOnLocalhost (PortNumber port) = do
60 proto <- getProtocolNumber "tcp"
61 localhost <- inet_addr "127.0.0.1"
63 (socket AF_INET Stream proto)
66 setSocketOption sock ReuseAddr 1
67 bindSocket sock (SockAddrInet port localhost)
68 listen sock maxListenQueue
70listenOnLocalhost _ = error "Wrong Porttype"
73 { optionsDirectory :: Maybe FilePath
74 , optionsOutputPath :: FilePath
75 , optionsPort :: PortID
76 , optionsDebug :: Bool
81 Options dir outputPath port debug <- execParser opts
82 maybe (pure ()) setCurrentDirectory dir
83 serverState <- newTVarIO emptyPscIdeState
84 cwd <- getCurrentDirectory
85 let fullOutputPath = cwd </> outputPath
87 doesDirectoryExist fullOutputPath
89 (do putStrLn ("Your output directory didn't exist. I'll create it at: " <> fullOutputPath)
90 createDirectory fullOutputPath
91 putStrLn "This usually means you didn't compile your project yet."
92 putStrLn "psc-ide needs you to compile your project (for example by running pulp build)"
95 _ <- forkFinally (watcher serverState fullOutputPath) print
100 , confOutputPath = outputPath
105 envStateVar = serverState
106 , envConfiguration = conf
112 optional (strOption (long "directory" <> short 'd')) <*>
113 strOption (long "output-directory" <> value "output/") <*>
114 (PortNumber . fromIntegral <$>
115 option auto (long "port" <> short 'p' <> value (4242 :: Integer))) <*>
116 switch (long "debug")
117 opts = info (version <*> helper <*> parser) mempty
118 version = abortOption
119 (InfoMsg (showVersion Paths.version))
120 (long "version" <> help "Show the version number")
122startServer :: PortID -> PscIdeEnvironment -> IO ()
123startServer port env = withSocketsDo $ do
124 sock <- listenOnLocalhost port
125 runLogger (runReaderT (forever (loop sock)) env)
127 runLogger = runStdoutLoggingT . filterLogger (\_ _ -> confDebug (envConfiguration env))
129 loop :: (PscIde m, MonadLogger m) => Socket -> m ()
131 accepted <- runExceptT $ acceptCommand sock
133 Left err -> $(logDebug) err
137 result <- runExceptT (handleCommand cmd')
138 -- $(logDebug) ("Answer was: " <> T.pack (show result))
139 liftIO (hFlush stdout)
141 -- What function can I use to clean this up?
142 Right r -> liftIO $ T.hPutStrLn h (encodeT r)
143 Left err -> liftIO $ T.hPutStrLn h (encodeT err)
145 $(logDebug) ("Parsing the command failed. Command: " <> cmd)
147 T.hPutStrLn h (encodeT (GeneralError "Error parsing Command."))
152acceptCommand :: (MonadIO m, MonadLogger m, MonadError T.Text m)
153 => Socket -> m (T.Text, Handle)
154acceptCommand sock = do
155 h <- acceptConnection
156 $(logDebug) "Accepted a connection"
157 cmd' <- liftIO (catchJust
158 -- this means that the connection was
159 -- terminated without receiving any input
160 (\e -> if isEOFError e then Just () else Nothing)
161 (Just <$> T.hGetLine h)
162 (const (pure Nothing)))
164 Nothing -> throwError "Connection was closed before any input arrived"
169 acceptConnection = liftIO $ do
170 (h,_,_) <- accept sock
172 hSetBuffering h LineBuffering
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list