@@ -8,7 +8,8 @@ import com.intellij.openapi.application.EDT
88import com.intellij.openapi.components.Service
99import com.intellij.openapi.diagnostic.logger
1010import com.intellij.openapi.editor.EditorFactory
11- import com.intellij.openapi.editor.event.*
11+ import com.intellij.openapi.editor.event.EditorFactoryEvent
12+ import com.intellij.openapi.editor.event.EditorFactoryListener
1213import com.intellij.openapi.fileEditor.FileEditorManager
1314import com.intellij.openapi.fileEditor.FileEditorManagerListener
1415import com.intellij.openapi.fileEditor.TextEditor
@@ -18,23 +19,24 @@ import com.intellij.openapi.project.ProjectManager
1819import com.intellij.openapi.project.ProjectManagerListener
1920import com.intellij.openapi.vfs.VirtualFile
2021import com.intellij.openapi.wm.ToolWindowManager
22+ import com.intellij.psi.PsiDocumentManager
23+ import com.intellij.util.io.BaseOutputReader
2124import com.intellij.util.io.await
2225import com.intellij.util.io.awaitExit
2326import com.intellij.util.io.readLineAsync
24- import com.intellij.util.io.BaseOutputReader
2527import io.github.ethersync.protocol.*
2628import io.github.ethersync.settings.AppSettings
2729import io.github.ethersync.sync.Changetracker
2830import io.github.ethersync.sync.Cursortracker
2931import io.github.ethersync.ui.ToolWindow
30- import kotlinx.coroutines.CoroutineScope
31- import kotlinx.coroutines.Dispatchers
32- import kotlinx.coroutines.launch
33- import kotlinx.coroutines.withContext
32+ import kotlinx.coroutines.*
33+ import kotlinx.coroutines.channels.Channel
34+ import kotlinx.coroutines.channels.consumeEach
3435import org.eclipse.lsp4j.jsonrpc.Launcher
3536import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
3637import java.io.BufferedReader
3738import java.io.File
39+ import java.io.IOException
3840import java.io.InputStreamReader
3941import java.nio.file.Files
4042import java.nio.file.attribute.PosixFilePermissions
@@ -46,7 +48,7 @@ private val LOG = logger<EthersyncServiceImpl>()
4648class EthersyncServiceImpl (
4749 private val project : Project ,
4850 private val cs : CoroutineScope ,
49- ) : EthersyncService {
51+ ) : EthersyncService {
5052
5153 private var launcher: Launcher <RemoteEthersyncClientProtocol >? = null
5254 private var daemonProcess: ColoredProcessHandler ? = null
@@ -55,6 +57,9 @@ class EthersyncServiceImpl(
5557 private val changetracker: Changetracker = Changetracker (project, cs)
5658 private val cursortracker: Cursortracker = Cursortracker (project, cs)
5759
60+ /* * test-only! */
61+ var attachDaemonOutputToUi: Boolean = true
62+
5863 init {
5964 val bus = project.messageBus.connect()
6065 bus.subscribe(FileEditorManagerListener .FILE_EDITOR_MANAGER , object : FileEditorManagerListener {
@@ -83,10 +88,9 @@ class EthersyncServiceImpl(
8388
8489 EditorFactory .getInstance().addEditorFactoryListener(object : EditorFactoryListener {
8590 override fun editorCreated (event : EditorFactoryEvent ) {
86- val file = event.editor.virtualFile ? : return
87- if (! file.exists()) {
88- return
89- }
91+ val doc = event.editor.document
92+ val file = PsiDocumentManager .getInstance(project).getPsiFile(doc) ? : return
93+ LOG .debug(" Starting to watch changes of ${file} " )
9094
9195 event.editor.caretModel.addCaretListener(cursortracker)
9296 event.editor.document.addDocumentListener(changetracker)
@@ -103,7 +107,7 @@ class EthersyncServiceImpl(
103107 }
104108 }, project)
105109
106- ProjectManager .getInstance().addProjectManagerListener(project, object : ProjectManagerListener {
110+ ProjectManager .getInstance().addProjectManagerListener(project, object : ProjectManagerListener {
107111 override fun projectClosingBeforeSave (project : Project ) {
108112 shutdown()
109113 }
@@ -132,75 +136,97 @@ class EthersyncServiceImpl(
132136 cursortracker.clear()
133137 }
134138
135- override fun start (joinCode : String? ) {
139+ override fun start (joinCode : String? ): Job {
136140 val cmd = GeneralCommandLine (AppSettings .getInstance().state.ethersyncBinaryPath)
137141
138142 if (joinCode == null || joinCode.trim().isEmpty()) {
139143 cmd.addParameter(" share" )
140- }
141- else {
144+ } else {
142145 cmd.addParameter(" join" )
143146 cmd.addParameter(joinCode.trim())
144147 }
145148
146- launchDaemon(cmd)
149+ return cs.launch {
150+ val channel = Channel <Unit >()
151+ launchDaemon(cmd, channel)
152+ channel.consumeEach { msg ->
153+ LOG .debug(" Started: $msg " )
154+ }
155+ }
147156 }
148157
149158 override fun startWithCustomCommandLine (commandLine : String ) {
150159 // TODO: splitting by " " is probably insufficient if there is an argument with spaces in it…
151160 val cmd = GeneralCommandLine (commandLine.split(" " ))
152161
153- launchDaemon(cmd)
162+ cs.launch {
163+ val channel = Channel <Unit >()
164+ launchDaemon(cmd, channel)
165+ channel.consumeEach { msg ->
166+ LOG .debug(" Started: $msg " )
167+ }
168+ }
154169 }
155170
156- private fun launchDaemon (cmd : GeneralCommandLine ) {
171+ private suspend fun launchDaemon (cmd : GeneralCommandLine , clientStarted : Channel < Unit > ) {
157172 val projectDirectory = File (project.basePath!! )
158- val ethersyncDirectory = File (projectDirectory, " .ethersync " )
173+ val ethersyncDirectory = File (projectDirectory, " .teamtype " )
159174 cmd.workDirectory = projectDirectory
160175
161- cs.launch {
162- shutdownImpl()
176+ shutdownImpl()
177+
178+ if (! ethersyncDirectory.exists()) {
179+ LOG .debug(" Creating teamtype directory" )
180+ val permissions = PosixFilePermissions .asFileAttribute(PosixFilePermissions .fromString(" rwx------" ));
181+ withContext(Dispatchers .IO ) {
182+ Files .createDirectory(ethersyncDirectory.toPath(), permissions)
183+ };
184+ }
163185
164- if (! ethersyncDirectory.exists()) {
165- LOG .debug(" Creating ethersync directory" )
166- val permissions = PosixFilePermissions .asFileAttribute(PosixFilePermissions .fromString(" rwx------" ));
167- Files .createDirectory(ethersyncDirectory.toPath(), permissions);
186+ daemonProcess = object : ColoredProcessHandler (cmd) {
187+ override fun readerOptions (): BaseOutputReader .Options {
188+ return BaseOutputReader .Options .forMostlySilentProcess()
168189 }
190+ }
169191
170- withContext(Dispatchers .EDT ) {
171- daemonProcess = object : ColoredProcessHandler (cmd) {
172- override fun readerOptions (): BaseOutputReader .Options {
173- return BaseOutputReader .Options .forMostlySilentProcess()
192+ daemonProcess!! .addProcessListener(object : ProcessListener {
193+ override fun startNotified (event : ProcessEvent ) {
194+ cs.launch {
195+ val ethersyncSocket = File (ethersyncDirectory, " socket" ).toPath()
196+ while (! Files .exists(ethersyncSocket)) {
197+ Thread .sleep(100 )
174198 }
199+ launchEthersyncClient(projectDirectory, clientStarted)
175200 }
201+ }
176202
177- daemonProcess!! .addProcessListener(object : ProcessListener {
178- override fun startNotified (event : ProcessEvent ) {
179- cs.launch {
180- val ethersyncSocket = File (ethersyncDirectory, " socket" ).toPath()
181- while (! Files .exists(ethersyncSocket)) {
182- Thread .sleep(100 )
183- }
184- launchEthersyncClient(projectDirectory)
185- }
186- }
203+ override fun processTerminated (event : ProcessEvent ) {
204+ shutdown()
205+ }
206+ })
187207
188- override fun processTerminated (event : ProcessEvent ) {
189- shutdown()
190- }
191- })
192208
193- val tw = ToolWindowManager .getInstance(project).getToolWindow(" ethersync" )!!
194- val toolWindow = tw.contentManager.findContent(" Daemon" )!! .component
195- if (toolWindow is ToolWindow ) {
196- toolWindow.attachToProcess(daemonProcess!! )
197- }
209+ attachDaemonToToolWindow()
198210
199- tw.show()
211+ daemonProcess!! .startNotify()
212+ }
200213
201- daemonProcess!! .startNotify()
214+ private suspend fun attachDaemonToToolWindow () {
215+ if (! attachDaemonOutputToUi) {
216+ return
217+ }
218+ val process = daemonProcess ? : return
219+
220+ withContext(Dispatchers .EDT ) {
221+ val tw = ToolWindowManager .getInstance(project).getToolWindow(" ethersync" ) ? : return @withContext
222+
223+ val daemon = tw.contentManager.findContent(" Daemon" ) ? : return @withContext
224+ val toolWindow = daemon.component
225+ if (toolWindow is ToolWindow ) {
226+ toolWindow.attachToProcess(process)
202227 }
203228
229+ tw.show()
204230 }
205231 }
206232
@@ -218,51 +244,56 @@ class EthersyncServiceImpl(
218244 }
219245 }
220246
221- private fun launchEthersyncClient (projectDirectory : File ) {
247+ private suspend fun launchEthersyncClient (projectDirectory : File , clientStarted : Channel < Unit > ) {
222248 if (clientProcess != null ) {
223249 return
224250 }
225251
226- cs.launch {
227- LOG .info(" Starting ethersync client" )
228- // TODO: try catch not existing binary
229- val clientProcessBuilder = ProcessBuilder (AppSettings .getInstance().state.ethersyncBinaryPath, " client" )
230- .directory(projectDirectory)
231- clientProcess = clientProcessBuilder.start()
232- val clientProcess = clientProcess!!
233-
234- val ethersyncEditorProtocol = createProtocolHandler()
235- launcher = Launcher .createIoLauncher(
236- ethersyncEditorProtocol,
237- RemoteEthersyncClientProtocol ::class .java,
238- clientProcess.inputStream,
239- clientProcess.outputStream,
240- Executors .newCachedThreadPool(),
241- { c -> c },
242- { _ -> run {} }
243- )
244-
245- val listening = launcher!! .startListening()
246- cursortracker.remoteProxy = launcher!! .remoteProxy
247- changetracker.remoteProxy = launcher!! .remoteProxy
248-
249- val fileEditorManager = FileEditorManager .getInstance(project)
250- for (file in fileEditorManager.openFiles) {
251- val content = LoadTextUtil .loadText(file).toString()
252- launchDocumentOpenRequest(file.canonicalFile!! .url, content)
253- }
252+ LOG .info(" Starting teamtype client" )
253+ // TODO: try catch not existing binary
254+ val clientProcessBuilder = ProcessBuilder (AppSettings .getInstance().state.ethersyncBinaryPath, " client" )
255+ .directory(projectDirectory)
256+ clientProcess = clientProcessBuilder.start()
257+ val clientProcess = clientProcess!!
258+
259+ val ethersyncEditorProtocol = createProtocolHandler()
260+ launcher = Launcher .createIoLauncher(
261+ ethersyncEditorProtocol,
262+ RemoteEthersyncClientProtocol ::class .java,
263+ clientProcess.inputStream,
264+ clientProcess.outputStream,
265+ Executors .newCachedThreadPool(),
266+ { c -> c },
267+ { _ -> run {} }
268+ )
269+
270+ val listening = launcher!! .startListening()
271+ cursortracker.remoteProxy = launcher!! .remoteProxy
272+ changetracker.remoteProxy = launcher!! .remoteProxy
273+
274+ val fileEditorManager = FileEditorManager .getInstance(project)
275+ for (file in fileEditorManager.openFiles) {
276+ val content = LoadTextUtil .loadText(file).toString()
277+ launchDocumentOpenRequest(file.canonicalFile!! .url, content)
278+ }
254279
255- clientProcess.awaitExit()
280+ clientStarted.send(Unit )
281+ clientStarted.close()
282+ clientProcess.awaitExit()
256283
257- listening.cancel(true )
258- listening.await()
284+ listening.cancel(true )
285+ listening.await()
259286
260- if (clientProcess.exitValue() != 0 ) {
261- val stderr = BufferedReader (InputStreamReader (clientProcess.errorStream))
262- stderr.use {
263- while (true ) {
287+ if (clientProcess.exitValue() != 0 ) {
288+ val stderr = BufferedReader (InputStreamReader (clientProcess.errorStream))
289+ stderr.use {
290+ while (true ) {
291+ try {
264292 val line = stderr.readLineAsync() ? : break ;
265293 LOG .trace(line)
294+ } catch (e: IOException ) {
295+ LOG .trace(e)
296+ break
266297 }
267298 }
268299 }
0 commit comments