Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.intellij.platform.gradle.TestFrameworkType

plugins {
id("java")
// Do not upgrade until following has been fixed:
Expand All @@ -21,10 +23,17 @@ dependencies {
intellijPlatform {
intellijIdeaCommunity("2024.3.1.1")
bundledPlugin("org.jetbrains.plugins.terminal")

testFramework(TestFrameworkType.Platform)
}

implementation("org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1")
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.23.1")

testImplementation("junit:junit:4.13.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.14.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.1")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine")
}

kotlin {
Expand All @@ -38,13 +47,9 @@ tasks {
sinceBuild.set("243")
}

signPlugin {
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
privateKey.set(System.getenv("PRIVATE_KEY"))
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
}

publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
test {
useJUnitPlatform {
includeEngines("junit-vintage")
}
}
}
96 changes: 49 additions & 47 deletions src/main/kotlin/io/github/ethersync/EthersyncServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.ColoredProcessHandler
import com.intellij.execution.process.ProcessEvent
import com.intellij.execution.process.ProcessListener
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.event.*
import com.intellij.openapi.editor.event.EditorFactoryEvent
import com.intellij.openapi.editor.event.EditorFactoryListener
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.fileEditor.TextEditor
Expand All @@ -17,24 +17,21 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.util.io.BaseOutputReader
import com.intellij.util.io.await
import com.intellij.util.io.awaitExit
import com.intellij.util.io.readLineAsync
import com.intellij.util.io.BaseOutputReader
import io.github.ethersync.protocol.*
import io.github.ethersync.settings.AppSettings
import io.github.ethersync.sync.Changetracker
import io.github.ethersync.sync.Cursortracker
import io.github.ethersync.ui.ToolWindow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.eclipse.lsp4j.jsonrpc.Launcher
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.attribute.PosixFilePermissions
Expand All @@ -46,7 +43,7 @@ private val LOG = logger<EthersyncServiceImpl>()
class EthersyncServiceImpl(
private val project: Project,
private val cs: CoroutineScope,
) : EthersyncService {
) : EthersyncService {

private var launcher: Launcher<RemoteEthersyncClientProtocol>? = null
private var daemonProcess: ColoredProcessHandler? = null
Expand Down Expand Up @@ -83,10 +80,10 @@ class EthersyncServiceImpl(

EditorFactory.getInstance().addEditorFactoryListener(object : EditorFactoryListener {
override fun editorCreated(event: EditorFactoryEvent) {
val file = event.editor.virtualFile ?: return
if (!file.exists()) {
return
}
// val file = event.editor.virtualFile ?: return
// if (!file.exists()) {
// return
// }

event.editor.caretModel.addCaretListener(cursortracker)
event.editor.document.addDocumentListener(changetracker)
Expand All @@ -103,7 +100,7 @@ class EthersyncServiceImpl(
}
}, project)

ProjectManager.getInstance().addProjectManagerListener(project, object: ProjectManagerListener {
ProjectManager.getInstance().addProjectManagerListener(project, object : ProjectManagerListener {
override fun projectClosingBeforeSave(project: Project) {
shutdown()
}
Expand Down Expand Up @@ -137,8 +134,7 @@ class EthersyncServiceImpl(

if (joinCode == null || joinCode.trim().isEmpty()) {
cmd.addParameter("share")
}
else {
} else {
cmd.addParameter("join")
cmd.addParameter(joinCode.trim())
}
Expand All @@ -155,52 +151,53 @@ class EthersyncServiceImpl(

private fun launchDaemon(cmd: GeneralCommandLine) {
val projectDirectory = File(project.basePath!!)
val ethersyncDirectory = File(projectDirectory, ".ethersync")
val ethersyncDirectory = File(projectDirectory, ".teamtype")
cmd.workDirectory = projectDirectory

cs.launch {
shutdownImpl()

if (!ethersyncDirectory.exists()) {
LOG.debug("Creating ethersync directory")
LOG.debug("Creating teamtype directory")
val permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
Files.createDirectory(ethersyncDirectory.toPath(), permissions);
}

withContext(Dispatchers.EDT) {
daemonProcess = object : ColoredProcessHandler(cmd) {
override fun readerOptions(): BaseOutputReader.Options {
return BaseOutputReader.Options.forMostlySilentProcess()
}
daemonProcess = object : ColoredProcessHandler(cmd) {
override fun readerOptions(): BaseOutputReader.Options {
return BaseOutputReader.Options.forMostlySilentProcess()
}
}

daemonProcess!!.addProcessListener(object : ProcessListener {
override fun startNotified(event: ProcessEvent) {
cs.launch {
val ethersyncSocket = File(ethersyncDirectory, "socket").toPath()
while (!Files.exists(ethersyncSocket)) {
Thread.sleep(100)
}
launchEthersyncClient(projectDirectory)
daemonProcess!!.addProcessListener(object : ProcessListener {
override fun startNotified(event: ProcessEvent) {
cs.launch {
val ethersyncSocket = File(ethersyncDirectory, "socket").toPath()
while (!Files.exists(ethersyncSocket)) {
Thread.sleep(100)
}
launchEthersyncClient(projectDirectory)
}
}

override fun processTerminated(event: ProcessEvent) {
shutdown()
}
})
override fun processTerminated(event: ProcessEvent) {
shutdown()
}
})

/*
withContext(Dispatchers.EDT) {
val tw = ToolWindowManager.getInstance(project).getToolWindow("ethersync")!!
val toolWindow = tw.contentManager.findContent("Daemon")!!.component
if (toolWindow is ToolWindow) {
toolWindow.attachToProcess(daemonProcess!!)
}

tw.show()

daemonProcess!!.startNotify()
}
*/

daemonProcess!!.startNotify()
}
}

Expand All @@ -224,22 +221,22 @@ class EthersyncServiceImpl(
}

cs.launch {
LOG.info("Starting ethersync client")
LOG.info("Starting teamtype client")
// TODO: try catch not existing binary
val clientProcessBuilder = ProcessBuilder(AppSettings.getInstance().state.ethersyncBinaryPath, "client")
.directory(projectDirectory)
.directory(projectDirectory)
clientProcess = clientProcessBuilder.start()
val clientProcess = clientProcess!!

val ethersyncEditorProtocol = createProtocolHandler()
launcher = Launcher.createIoLauncher(
ethersyncEditorProtocol,
RemoteEthersyncClientProtocol::class.java,
clientProcess.inputStream,
clientProcess.outputStream,
Executors.newCachedThreadPool(),
{ c -> c },
{ _ -> run {} }
ethersyncEditorProtocol,
RemoteEthersyncClientProtocol::class.java,
clientProcess.inputStream,
clientProcess.outputStream,
Executors.newCachedThreadPool(),
{ c -> c },
{ _ -> run {} }
)

val listening = launcher!!.startListening()
Expand All @@ -261,8 +258,13 @@ class EthersyncServiceImpl(
val stderr = BufferedReader(InputStreamReader(clientProcess.errorStream))
stderr.use {
while (true) {
val line = stderr.readLineAsync() ?: break;
LOG.trace(line)
try {
val line = stderr.readLineAsync() ?: break;
LOG.trace(line)
} catch (e: IOException) {
LOG.trace(e)
break
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AppSettings : PersistentStateComponent<AppSettings.State> {

data class State(
@NonNls
var ethersyncBinaryPath: String = "ethersync"
var ethersyncBinaryPath: String = "teamtype"
)

private var state: State = State()
Expand Down
10 changes: 9 additions & 1 deletion src/main/kotlin/io/github/ethersync/sync/Cursortracker.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.ethersync.sync

import com.intellij.openapi.application.EDT
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.colors.EditorFontType
Expand All @@ -16,7 +17,9 @@ import io.github.ethersync.protocol.CursorEvent
import io.github.ethersync.protocol.CursorRequest
import io.github.ethersync.protocol.RemoteEthersyncClientProtocol
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException
Expand Down Expand Up @@ -132,7 +135,12 @@ class Cursortracker(

suspend fun clear() {
remoteProxy = null
withUiContext {
synchronized(highlighter) {
if (highlighter.isEmpty()) {
return
}
}
withContext(Dispatchers.EDT) {
synchronized(highlighter) {
for (entry in highlighter) {
val fileEditor = FileEditorManager.getInstance(project)
Expand Down
104 changes: 104 additions & 0 deletions src/test/kotlin/io/github/ethersync/EthersyncServiceImplTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package io.github.ethersync

import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.service
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.vfs.findOrCreateFile
import com.intellij.testFramework.HeavyPlatformTestCase
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.testFramework.utils.vfs.createFile
import com.intellij.util.application
import java.io.BufferedReader
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.PosixFilePermissions
import java.util.Optional
import kotlin.io.path.Path

class EthersyncServiceImplTest : HeavyPlatformTestCase() {

var daemon: Process? = null
var joinCode: String? = null
var daemonDir: Path? = null

override fun setUp() {
super.setUp()

daemonDir = Files.createTempDirectory("remote-project")
val permissions = PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"));
Files.createDirectory(Path(daemonDir!!.toString(), ".teamtype"), permissions);

daemon = ProcessBuilder()
.command("teamtype", "share")
.directory(daemonDir!!.toFile())
.start()

val reader = BufferedReader(InputStreamReader(daemon!!.inputStream))
reader.use {
var line: String? = null
do {
line = reader.readLine()

if(line != null) {
line = line.trim()
if (line.startsWith("teamtype join ")) {
joinCode = line.substring(14)
break
}
}
} while (line != null)
}
}

fun testIntention() {
val dir = orCreateProjectBaseDir

val service = project.service<EthersyncService>()
runInEdtAndWait {
service.start(joinCode)
}

Thread.sleep(5_000)


application.runWriteAction {
dir.createFile("file.txt")
}

runInEdtAndWait {
val file = dir.findOrCreateFile("file.txt")
FileEditorManager.getInstance(project).openFile(file)
val editor = FileEditorManager.getInstance(project)
.allEditors
.filterIsInstance<TextEditor>()
.firstOrNull { editor -> editor.file == file }!!

Thread.sleep(5_000)

val document = editor.editor.document
WriteCommandAction.runWriteCommandAction(project, {
document.insertString(0, "Hello")
})

Thread.sleep(5_000)
}

val firstLine = Files.lines(Path(daemonDir!!.toString(), "file.txt"))
.findFirst()
assertEquals(Optional.of("Hello"), firstLine)

runInEdtAndWait {
service.shutdown()
}
}

override fun tearDown() {
daemon!!.destroy()
daemon!!.waitFor()
daemon = null
daemonDir = null
joinCode = null
}
}