1module PscIdeClient where
4import Data.Either (Either(..), either)
5import Data.Maybe (Maybe(..))
6import Data.Newtype (class Newtype, un, unwrap)
7import Data.Posix.Signal (Signal(..))
8import Data.String.Utils (endsWith)
9import Effect.Aff (Aff, Canceler, effectCanceler, makeAff)
10import Effect.Class (liftEffect)
11import Effect.Class.Console (log)
12import Effect.Ref (Ref)
13import Effect.Ref as Ref
14import Node.Buffer as Buf
15import Node.ChildProcess (ChildProcess, StdIOBehaviour(..))
16import Node.ChildProcess as CP
17import Node.Encoding (Encoding(..))
18import Node.FS.Aff (writeTextFile)
19import Node.Net.Socket (Socket)
20import Node.Net.Socket as Socket
21import Playground.Playground (Folder(..))
22import Simple.JSON (writeJSON)
38buildCommand ∷ BuildCommand
40 { "command": "rebuild"
41 , "params": { "file": "src/Main.purs" }
44closeSocketCanceller ∷ Socket -> Canceler
45closeSocketCanceller s =
48 (Socket.destroyed s) do
49 log $ "Destroying socket..."
50 Socket.destroy s Nothing
52startIdeServer ∷ Folder -> Int -> Aff ChildProcess
53startIdeServer folder port = do
54 log $ "Spawning ide server" <> infoString
55 cp <- spawnProcess folder "npx" [ "purs", "ide", "server", "-p", show port, "--editor-mode", "--no-watch" ]
56 log $ "Spawned ide server" <> infoString
58 { error, stderr, stdout } <- execCommand folder "npx spago build"
59 stderrStr <- liftEffect $ Buf.toString UTF8 stderr
60 log $ "Built: " <> stderrStr
61 log $ "Loading modules" <> infoString
62 loadPscIde folder port
71execCommand ∷ Folder -> String -> Aff CP.ExecResult
72execCommand folder command =
73 makeAff \callback -> do
74 let options = CP.defaultExecOptions { cwd = Just (un Folder folder) }
75 childProcess <- CP.exec command options (callback <<< Right)
76 pure $ effectCanceler ((log $ "Killing " <> show (CP.pid childProcess)) *> CP.kill SIGKILL childProcess)
78restartIdeServer ∷ Folder -> Int -> Ref ChildProcess -> Aff Unit
79restartIdeServer folder port processRef = do
80 cp <- Ref.read processRef # liftEffect
81 CP.kill SIGKILL cp # liftEffect
82 newCp <- startIdeServer folder port
83 Ref.write newCp processRef # liftEffect
85spawnProcess ∷ Folder -> String -> Array String -> Aff ChildProcess
86spawnProcess folder command args =
87 makeAff \callback -> do
88 let options = CP.defaultSpawnOptions { cwd = Just (un Folder folder), stdio = Just <$> [ Ignore, Ignore, Ignore ] }
89 childProcess <- CP.spawn command args options
90 callback (Right childProcess)
92 $ (log $ "Killing " <> show (CP.pid childProcess))
93 *> CP.kill SIGKILL childProcess
95newtype PscIdeConnection = PscIdeConnection
96 { serverProcessRef ∷ Ref ChildProcess
101derive instance ntPscIdeConnection ∷ Newtype PscIdeConnection _
103instance eqPscIdeConnection ∷ Eq PscIdeConnection where
104 eq c1 c2 = (unwrap c1).port == (unwrap c2).port
106saveMainFile ∷ Folder -> String -> Aff Unit
107saveMainFile folder code = writeTextFile UTF8 (un Folder folder <> "/src/Main.purs") code
109getFolder ∷ PscIdeConnection -> Folder
110getFolder (PscIdeConnection { folder }) = folder
112loadPscIde ∷ Folder -> Int -> Aff Unit
113loadPscIde folder port = do
115 socket <- Socket.createConnectionTCP port "localhost" mempty
117 Socket.onError socket (affCb <<< Left)
119 $ Socket.writeString socket (writeJSON loadCommand <> "\n") UTF8 (affCb (Right unit))
121 $ Socket.onClose socket case _ of
122 true -> mempty -- should be covered in onError
125 let command = writeJSON buildCommand <> "\n"
126 Socket.onReady socket (void $ Socket.writeString socket command UTF8 mempty)
127 pure (closeSocketCanceller socket)
129compileCode ∷ String -> PscIdeConnection -> Aff String
130compileCode code (PscIdeConnection { port, folder, serverProcessRef }) = do
131 serverProcess <- Ref.read serverProcessRef # liftEffect
132 saveMainFile folder code
134 log $ "Compiling on " <> show port
135 socket <- Socket.createConnectionTCP port "localhost" mempty
136 -- Socket.onError socket (affCb <<< Left)
137 resultRef <- Ref.new ""
138 Socket.onData socket \dataFromServer -> do
139 log $ "Data on port " <> show port
140 str <- dataFromServer # either (Buf.toString UTF8) pure
141 newStr <- resultRef # Ref.modify (_ <> str)
142 when (newStr # endsWith "\n") do
143 log $ "Enough data on " <> show port <> " ending socket\n"
144 void $ Socket.endString socket "" UTF8 mempty
146 let command = writeJSON buildCommand
147 Socket.onReady socket do
148 log $ "Socket " <> show port <> " ready"
149 void $ Socket.writeString socket (command <> "\n") UTF8 mempty
150 pure (closeSocketCanceller socket)
152mkConnection ∷ Folder -> Int -> Aff PscIdeConnection
153mkConnection folder port = do
154 serverProcess <- startIdeServer folder port
155 serverProcessRef <- Ref.new serverProcess # liftEffect
157 $ CP.onError serverProcess \e ->
158 log $ "Server process on port " <> show port <> " got error " <> show e
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