Skip to content
This repository was archived by the owner on Feb 1, 2019. It is now read-only.

Commit 04037ad

Browse files
committed
Merge pull request #30 from jetaggart/eval-in-haskell
Prototype for evaluation on the server.
2 parents 135b380 + 2fe4d13 commit 04037ad

File tree

7 files changed

+332
-265
lines changed

7 files changed

+332
-265
lines changed

haskell.behaviors

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
:lt.plugins.haskell/editor-reformat-result
1212
:lt.plugins.haskell/editor-syntax-result
1313
:lt.plugins.haskell/editor-lint-result
14+
1415
:lt.plugins.haskell/on-eval-one
16+
:lt.plugins.haskell/haskell-success
17+
:lt.plugins.haskell/haskell-result
18+
:lt.plugins.haskell/haskell-exception
1519
:lt.plugins.haskell/on-eval-type
1620

1721
[:lt.object/add-tag :docable]

haskell/LTHaskellClient.hs

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import System.Exit (exitSuccess)
99
import System.IO (Handle, hClose, hFlush, hGetLine,
1010
hPutStrLn, stderr, stdout)
1111

12-
import Control.Applicative ((<$>))
12+
import Control.Applicative ((<$>), (<*>))
1313

1414
import Data.Aeson (FromJSON (..), ToJSON (..),
1515
Value (..), eitherDecode, encode,
16-
object, (.:), (.=))
16+
object, (.:), (.:?), (.=), (.!=))
1717
import qualified Data.ByteString.Lazy.Char8 as BS
1818

1919
import GHC.Generics (Generic)
@@ -22,29 +22,32 @@ import Language.Haskell.GhcMod (check, defaultOptions, findCradle,
2222

2323
import Language.Haskell.Stylish
2424

25+
import Data.Char (isSpace)
26+
import ReplSession
27+
2528
main :: IO ()
2629
main = withSocketsDo $ do
27-
[portStr, clientIdStr] <- getArgs
30+
[portStr, clientIdStr, projectDir] <- getArgs
2831
let port = fromIntegral (read portStr :: Int)
2932
clientId = read clientIdStr
3033
handle <- connectTo "localhost" (PortNumber port)
31-
cwd <- getCurrentDirectory
34+
client <- startSession projectDir
3235

33-
putStrLn $ "Connected: " ++ cwd
36+
putStrLn $ "Connected: " ++ projectDir
3437
hFlush stdout
3538

36-
sendResponse handle $ LTConnection "Haskell" "haskell" clientId cwd ["haskell.api.reformat", "haskell.api.syntax"]
37-
processCommands handle
39+
sendResponse handle $ LTConnection "Haskell" "haskell" clientId projectDir ["haskell.api.reformat", "haskell.api.syntax"]
3840

41+
processCommands $ LTClientState handle client
3942

40-
processCommands :: Handle -> IO ()
41-
processCommands handle = do
43+
processCommands :: LTClientState -> IO ()
44+
processCommands state@(LTClientState handle _) = do
4245
line <- hGetLine handle
4346
case parseCommand line of
4447
Left e -> hPutStrLn stderr ("Error processing command: " ++ e)
45-
Right ltCommand -> execCommand handle ltCommand
48+
Right ltCommand -> execCommand state ltCommand
4649

47-
processCommands handle
50+
processCommands state
4851

4952
where
5053
parseCommand :: String -> Either String (LTCommand (Maybe LTPayload))
@@ -55,19 +58,22 @@ sendResponse handle = hPutStrLn handle . BS.unpack . encode
5558

5659
-- API
5760

58-
execCommand :: Handle -> LTCommand (Maybe LTPayload) -> IO ()
61+
data LTClientState = LTClientState { ltHandle :: Handle, ltReplSession :: ReplSession }
62+
63+
execCommand :: LTClientState -> LTCommand (Maybe LTPayload) -> IO ()
5964

60-
execCommand handle (LTCommand (_, "client.close", Nothing)) = do
61-
hClose handle
65+
execCommand state (LTCommand (_, "client.close", Nothing)) = do
66+
hClose $ ltHandle state
67+
endSession $ ltReplSession state
6268
exitSuccess
6369

64-
execCommand handle (LTCommand (cId, command, Just ltPayload)) =
70+
execCommand state (LTCommand (cId, command, Just ltPayload)) =
6571
go command $ ltData ltPayload
6672

6773
where
6874
go "haskell.api.reformat" payloadData = do
6975
reformattedCode <- format payloadData
70-
respond "editor.haskell.reformat.result" $ LTPayload reformattedCode
76+
respond "editor.haskell.reformat.result" $ LTPayload reformattedCode 0
7177

7278
go "haskell.api.syntax" payloadData = do
7379
syntaxIssues <- getSyntaxIssues payloadData
@@ -77,8 +83,26 @@ execCommand handle (LTCommand (cId, command, Just ltPayload)) =
7783
lintIssues <- getLintIssues payloadData
7884
respond "editor.haskell.lint.result" $ LTArrayPayload lintIssues
7985

86+
go "haskell.api.eval" payloadData = do
87+
result <- evalInSession payloadData $ ltReplSession state
88+
let line = ltLine ltPayload
89+
case result of
90+
Left msg -> respond "editor.eval.haskell.exception" $ LTPayload msg line
91+
Right msg | isBlank msg -> respond "editor.eval.haskell.success" $ LTPayload "" line
92+
Right msg -> respond "editor.eval.haskell.result" $ LTPayload msg line
93+
94+
go "haskell.api.type" payloadData = do
95+
result <- evalInSession (":type " ++ payloadData) $ ltReplSession state
96+
let line = ltLine ltPayload
97+
case result of
98+
Left msg -> respond "editor.eval.haskell.exception" $ LTPayload msg line
99+
Right msg -> respond "editor.eval.haskell.result" $ LTPayload msg line
100+
80101
respond :: (ToJSON a) => Command -> a -> IO ()
81-
respond respCommand respPayload = sendResponse handle $ LTCommand (cId, respCommand, respPayload)
102+
respond respCommand respPayload = sendResponse (ltHandle state) $ LTCommand (cId, respCommand, respPayload)
103+
104+
isBlank :: String -> Bool
105+
isBlank = all isSpace
82106

83107
-- API types
84108

@@ -89,12 +113,12 @@ data LTCommand a = LTCommand (Client, Command, a) deriving (Show, Generic)
89113
instance (FromJSON a) => FromJSON (LTCommand a)
90114
instance (ToJSON a) => ToJSON (LTCommand a)
91115

92-
data LTPayload = LTPayload { ltData :: String } deriving (Show)
116+
data LTPayload = LTPayload { ltData :: String, ltLine :: Int } deriving (Show)
93117
instance FromJSON LTPayload where
94-
parseJSON (Object v) = LTPayload <$> v .: "data"
118+
parseJSON (Object v) = LTPayload <$> v .: "data" <*> (v .:? "line" .!= 0)
95119

96120
instance ToJSON LTPayload where
97-
toJSON payload = object [ "data" .= ltData payload ]
121+
toJSON payload = object [ "data" .= ltData payload, "line" .= ltLine payload ]
98122

99123
data LTArrayPayload = LTArrayPayload { ltDataArray :: [String] } deriving (Show)
100124
instance FromJSON LTArrayPayload where

haskell/ReplSession.hs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module ReplSession (
2+
ReplSession,
3+
evalInSession,
4+
startSession,
5+
endSession
6+
) where
7+
8+
import System.IO
9+
import System.Process
10+
import System.Directory (getDirectoryContents)
11+
import Data.List (isSuffixOf)
12+
import Control.Monad (liftM)
13+
14+
data ReplSession = ReplSession {
15+
replIn :: Handle,
16+
replOut :: Handle,
17+
replError :: Handle,
18+
replProcess :: ProcessHandle
19+
}
20+
21+
evalInSession :: String -> ReplSession -> IO (Either String String)
22+
evalInSession cmd session@(ReplSession input out err _) = do
23+
clearHandle out 0
24+
clearHandle err 0
25+
sendCommand (cmd ++ "\n") session
26+
readEvalOutput session
27+
28+
readEvalOutput :: ReplSession -> IO (Either String String)
29+
readEvalOutput (ReplSession _ out err _) = do
30+
output <- readUntil out ("--EvalFinished\n" `isSuffixOf`)
31+
let onlyOutput = take (length output - length "--EvalFinished\n") output
32+
hasErrorOutput <- hReady err
33+
if hasErrorOutput
34+
then readAll err >>= \errorOutput -> return . Left $ errorOutput
35+
else return . Right $ onlyOutput
36+
37+
readUntil :: Handle -> (String -> Bool) -> IO String
38+
readUntil handle predicate = readUntil' handle "" predicate
39+
40+
readUntil' :: Handle -> String -> (String -> Bool) -> IO String
41+
readUntil' handle output predicate = do
42+
char <- hGetChar handle
43+
let newOutput = output ++ [char]
44+
if predicate $ newOutput
45+
then return newOutput
46+
else readUntil' handle newOutput predicate
47+
48+
readAll :: Handle -> IO String
49+
readAll handle = untilM' (liftM not $ hReady handle) (hGetChar handle)
50+
51+
startSession :: FilePath -> IO ReplSession
52+
startSession path = do
53+
cabalProject <- isCabalProject path
54+
let (cmd, args) = if cabalProject then ("cabal", ["repl"]) else ("ghci", [])
55+
(input, out, err, process) <- runInteractiveProcess cmd args (Just path) Nothing
56+
let session = ReplSession input out err process
57+
prepareSession session
58+
return session
59+
60+
isCabalProject :: FilePath -> IO Bool
61+
isCabalProject dir = do
62+
files <- getDirectoryContents dir
63+
return $ any (".cabal" `isSuffixOf`) files
64+
65+
prepareSession :: ReplSession -> IO ()
66+
prepareSession session@(ReplSession _ out _ _) = do
67+
sendCommand ":set prompt \"--EvalFinished\\n\"\n" session
68+
clearHandle out 1000
69+
70+
sendCommand :: String -> ReplSession -> IO ()
71+
sendCommand cmd (ReplSession input _ _ _) = do
72+
hPutStrLn input cmd
73+
hFlush input
74+
75+
clearHandle :: Handle -> Int -> IO ()
76+
clearHandle handle wait =
77+
untilM (liftM not $ hWaitForInput handle wait) $ do
78+
hGetChar handle
79+
80+
untilM :: (Monad m) => m Bool -> m a -> m ()
81+
untilM predicate action = untilM' predicate action >> return ()
82+
83+
untilM' :: (Monad m) => m Bool -> m a -> m [a]
84+
untilM' predicate action = do
85+
isFinished <- predicate
86+
if isFinished
87+
then return []
88+
else do
89+
res <- action
90+
others <- untilM' predicate action
91+
return $ res : others
92+
93+
endSession :: ReplSession -> IO ()
94+
endSession session = do
95+
sendCommand ":quit\n" session
96+
waitForProcess $ replProcess session
97+
return ()

0 commit comments

Comments
 (0)