git.iko.soy/rowtype-yoga/psfp
on 4a3f8f8 help sign in
rowtype-yoga/psfp/server/src/PscIdeClient.purs
138 lines 4.3 kB history raw
1module PscIdeClient where
3import Prelude
5import Data.Array as Array
6import Data.Either (Either(..), either)
7import Data.Foldable (intercalate)
8import Data.Maybe (Maybe(..))
9import Data.Newtype (class Newtype, un, unwrap)
10import Data.Posix.Signal (Signal(..))
11import Data.String.Utils (endsWith)
12import Debug.Trace (spy)
13import Effect (Effect)
14import Effect.Aff (Aff, Canceler, effectCanceler, makeAff)
15import Effect.Class (liftEffect)
16import Effect.Class.Console (log)
17import Effect.Exception (message)
18import Effect.Ref (Ref)
19import Effect.Ref as Ref
20import Foreign (renderForeignError)
21import Node.Buffer as Buf
22import Node.ChildProcess (ChildProcess)
23import Node.ChildProcess as CP
24import Node.Encoding (Encoding(..))
25import Node.FS.Aff (writeTextFile)
26import Node.Net.Socket (Socket)
27import Node.Net.Socket as Socket
28import Playground.Playground (Folder(..))
29import Shared.Models.Body (CompileResult)
30import Simple.JSON (readJSON, writeJSON)
32type BuildCommand
33 = { command ∷ String
34 , params ∷
35 { file ∷ String
36 }
37 }
39buildCommand ∷ BuildCommand
40buildCommand =
41 { "command": "rebuild"
42 , "params": { "file": "src/Main.purs" }
43 }
45closeSocketCanceller ∷ Socket -> Canceler
46closeSocketCanceller s =
47 effectCanceler
48 $ unlessM
49 (Socket.destroyed s) do
50 -- Socket.end s (pure unit)
51 log $ "Destroying socket..."
52 Socket.destroy s Nothing
54startIdeServer ∷ Folder -> Int -> Aff ChildProcess
55startIdeServer folder port = do
56 log
57 $ "Spawning ide server in folder "
58 <> un Folder folder
59 <> " on port "
60 <> show port
61 cp <- spawnProcess folder ("npx purs ide server -p " <> show port)
62 log
63 $ "Spawned ide server in folder "
64 <> un Folder folder
65 <> " on port "
66 <> show port
67 pure cp
69restartIdeServer ∷ Folder -> Int -> Ref ChildProcess -> Aff Unit
70restartIdeServer folder port processRef = do
71 cp <- Ref.read processRef # liftEffect
72 CP.kill SIGKILL cp # liftEffect
73 newCp <- startIdeServer folder port
74 Ref.write newCp processRef # liftEffect
76spawnProcess ∷ Folder -> String -> Aff ChildProcess
77spawnProcess folder command =
78 makeAff \callback -> do
79 let
80 options = CP.defaultExecOptions { cwd = Just (un Folder folder) }
81 childProcess <- CP.exec command options mempty
82 callback (Right childProcess)
83 pure $ effectCanceler ((log $ "Killing " <> show (CP.pid childProcess)) *> CP.kill SIGKILL childProcess)
85newtype PscIdeConnection
86 = PscIdeConnection
87 { serverProcess ∷ ChildProcess
88 , port ∷ Int
89 , folder ∷ Folder
90 }
92derive instance ntPscIdeConnection :: Newtype PscIdeConnection _
93instance eqPscIdeConnection :: Eq PscIdeConnection where
94 eq c1 c2 = (unwrap c1).port == (unwrap c2).port
96saveMainFile ∷ Folder -> String -> Aff Unit
97saveMainFile folder code = writeTextFile UTF8 (un Folder folder <> "/src/Main.purs") code
99getFolder ∷ PscIdeConnection -> Folder
100getFolder (PscIdeConnection { folder }) = folder
102compileCode ∷ String -> PscIdeConnection -> Aff String
103compileCode code (PscIdeConnection { port, folder }) = do
104 saveMainFile folder code
105 makeAff \affCb -> do
106 log $
107 "Compiling code in " <> (un Folder folder) <> " on port " <> show port
108 socket <- Socket.createConnectionTCP port "localhost" mempty
109 -- maybe timeout?
110 Socket.onError socket (affCb <<< Left)
111 resultRef <- Ref.new ""
112 Socket.onData socket \dataFromServer -> do
113 str <- dataFromServer # either (Buf.toString UTF8) pure
114 newStr <- resultRef # Ref.modify (_ <> str)
115 when (newStr # endsWith "\n") do
116 Socket.endString socket "" UTF8 mempty
117 liftEffect
118 $ Socket.onClose socket case _ of
119 true -> mempty -- should be covered in onError
120 false -> do
121 res <- Ref.read resultRef
122 affCb (Right res)
123 let
124 command = writeJSON buildCommand <> "\n"
125 Socket.onReady socket (void $ Socket.writeString socket command UTF8 mempty)
126 pure (closeSocketCanceller socket)
128mkConnection ∷ Folder -> Int -> Aff PscIdeConnection
129mkConnection folder port = do
130 serverProcess <- startIdeServer folder port
131 liftEffect $ CP.onError serverProcess \e ->
132 log $ "Server process on port " <> show port <> " got error " <> show e
133 pure
134 $ PscIdeConnection
135 { serverProcess
136 , port
137 , folder
138 }