diff --git a/build.gradle.kts b/build.gradle.kts index 9edfc90a7..8b10b74ce 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ version = properties("pluginVersion") dependencies { implementation(project(":magicmetamodel")) + implementation(project(":protocol")) implementation("ch.epfl.scala:bsp4j:2.0.0-M15") implementation("com.google.code.gson:gson:2.9.0") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 2a62bd8ac..7262b95e7 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -8,7 +8,7 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.0") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.20.0") implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0") } diff --git a/gradle.properties b/gradle.properties index d5869727e..a58ece26d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,11 +20,11 @@ platformDownloadSources = true # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = +platformPlugins = com.intellij.java # Opt-out flag for bundling Kotlin standard library. # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. kotlin.stdlib.default.dependency = false javaVersion = 11 -kotlinVersion = 1.6 +kotlinVersion = 1.7 diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts new file mode 100644 index 000000000..7db7c72d3 --- /dev/null +++ b/protocol/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id("intellijbsp.kotlin-conventions") +} + +dependencies { + implementation("ch.epfl.scala:bsp4j:2.0.0-M15") + implementation("com.google.code.gson:gson:2.9.0") + + testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") + testImplementation("io.kotest:kotest-assertions-core:5.3.0") + testImplementation(project(":test-utils")) +} diff --git a/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGenerator.kt b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGenerator.kt new file mode 100644 index 000000000..2f54f0b60 --- /dev/null +++ b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGenerator.kt @@ -0,0 +1,33 @@ +package org.jetbrains.protocol.connection + +import com.intellij.openapi.vfs.VirtualFile + +public interface BspConnectionDetailsGenerator { + + public fun name(): String + + public fun canGenerateBspConnectionDetailsFile(projectPath: VirtualFile): Boolean + + public fun generateBspConnectionDetailsFile(projectPath: VirtualFile): VirtualFile +} + +public class BspConnectionDetailsGeneratorProvider( + private val projectPath: VirtualFile, + bspConnectionDetailsGenerators: List, +) { + + private val availableBspConnectionDetailsGenerators by lazy { + bspConnectionDetailsGenerators.filter { it.canGenerateBspConnectionDetailsFile(projectPath) } + } + + public fun canGenerateAnyBspConnectionDetailsFile(): Boolean = + availableBspConnectionDetailsGenerators.isNotEmpty() + + public fun availableGeneratorsNames(): List = + availableBspConnectionDetailsGenerators.map { it.name() } + + public fun generateBspConnectionDetailFileForGeneratorWithName(generatorName: String): VirtualFile? = + availableBspConnectionDetailsGenerators + .find { it.name() == generatorName } + ?.generateBspConnectionDetailsFile(projectPath) +} diff --git a/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProvider.kt b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProvider.kt new file mode 100644 index 000000000..9872938e0 --- /dev/null +++ b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProvider.kt @@ -0,0 +1,30 @@ +package org.jetbrains.protocol.connection + +import com.intellij.openapi.vfs.VirtualFile + +public class BspConnectionDetailsProvider( + private val bspConnectionDetailsGenerators: List +) { + + private lateinit var bspConnectionFilesProvider: BspConnectionFilesProvider + + private lateinit var bspConnectionDetailsGeneratorProvider: BspConnectionDetailsGeneratorProvider + + public fun canOpenBspProject(projectPath: VirtualFile): Boolean { + initProvidersIfNeeded(projectPath) + + return bspConnectionFilesProvider.isAnyBspConnectionFileDefined() + } + + + private fun initProvidersIfNeeded(projectPath: VirtualFile) { + if (!::bspConnectionFilesProvider.isInitialized) { + bspConnectionFilesProvider = BspConnectionFilesProvider(projectPath) + } + + if (!::bspConnectionDetailsGeneratorProvider.isInitialized) { + bspConnectionDetailsGeneratorProvider = + BspConnectionDetailsGeneratorProvider(projectPath, bspConnectionDetailsGenerators) + } + } +} diff --git a/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProvider.kt b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProvider.kt new file mode 100644 index 000000000..5f360fb53 --- /dev/null +++ b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProvider.kt @@ -0,0 +1,20 @@ +package org.jetbrains.protocol.connection + +import com.intellij.openapi.vfs.VirtualFile + +public class BspConnectionFilesProvider(projectPath: VirtualFile) { + + public val connectionFiles: List by lazy { calculateConnectionFile(projectPath) } + + private fun calculateConnectionFile(projectPath: VirtualFile): List = + projectPath.findChild(dotBspDir) + ?.children + ?.mapNotNull { LocatedBspConnectionDetailsParser.parseFromFile(it) } + .orEmpty() + + public fun isAnyBspConnectionFileDefined(): Boolean = connectionFiles.isNotEmpty() + + private companion object { + private const val dotBspDir = ".bsp" + } +} diff --git a/protocol/src/main/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetails.kt b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetails.kt new file mode 100644 index 000000000..f835f27e8 --- /dev/null +++ b/protocol/src/main/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetails.kt @@ -0,0 +1,34 @@ +package org.jetbrains.protocol.connection + +import ch.epfl.scala.bsp4j.BspConnectionDetails +import com.google.gson.Gson +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile + +public data class LocatedBspConnectionDetails( + val bspConnectionDetails: BspConnectionDetails, + val connectionFileLocation: VirtualFile, +) + +// TODO visib?? +public object LocatedBspConnectionDetailsParser { + + private val log = logger() + + public fun parseFromFile(file: VirtualFile): LocatedBspConnectionDetails? = + parseBspConnectionDetails(file)?.let { + LocatedBspConnectionDetails( + bspConnectionDetails = it, + connectionFileLocation = file, + ) + } + + private fun parseBspConnectionDetails(file: VirtualFile): BspConnectionDetails? = + try { + Gson().fromJson(VfsUtil.loadText(file), BspConnectionDetails::class.java) + } catch (e: Exception) { + log.info("Parsing file '$file' to BspConnectionDetails failed!", e) + null + } +} diff --git a/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGeneratorProviderTest.kt b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGeneratorProviderTest.kt new file mode 100644 index 000000000..4bb0eb1ee --- /dev/null +++ b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsGeneratorProviderTest.kt @@ -0,0 +1,177 @@ +package org.jetbrains.protocol.connection + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.io.createDirectories +import com.intellij.util.io.createFile +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import org.jetbrains.workspace.model.test.framework.MockProjectBaseTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.createTempDirectory + +private object GeneratorWhichCantGenerate : BspConnectionDetailsGenerator { + + override fun name(): String = "cant generate" + + override fun canGenerateBspConnectionDetailsFile(projectPath: VirtualFile): Boolean = false + + override fun generateBspConnectionDetailsFile(projectPath: VirtualFile): VirtualFile = projectPath +} + +private class GeneratorWhichCanGenerate(private val name: String, private val generatedFilePath: VirtualFile) : + BspConnectionDetailsGenerator { + + var hasGenerated = false + + override fun name(): String = name + + override fun canGenerateBspConnectionDetailsFile(projectPath: VirtualFile): Boolean = true + + override fun generateBspConnectionDetailsFile(projectPath: VirtualFile): VirtualFile { + hasGenerated = true + + return generatedFilePath + } +} + +class BspConnectionDetailsGeneratorProviderTest : MockProjectBaseTest() { + + private lateinit var projectPath: Path + private lateinit var generatedVirtualFile: VirtualFile + private lateinit var otherGeneratedVirtualFile: VirtualFile + + @BeforeEach + override fun beforeEach() { + // given + super.beforeEach() + + this.projectPath = createTempDirectory("project") + this.generatedVirtualFile = projectPath.resolve(".bsp").resolve("connection-file.json").createFile().toVirtualFile() + this.otherGeneratedVirtualFile = projectPath.resolve(".bsp").resolve("other-connection-file.json").createFile().toVirtualFile() + } + + @AfterEach + fun afterEach() { + projectPath.toFile().deleteRecursively() + } + + @Test + fun `should return false for canGenerateAnyBspConnectionDetailsFile(), empty list for availableGeneratorsNames() if there is no generator provided`() { + // given + val bspConnectionDetailsGenerators = emptyList() + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe false + provider.availableGeneratorsNames() shouldBe emptyList() + } + + @Test + fun `should return false for canGenerateAnyBspConnectionDetailsFile(), empty list for availableGeneratorsNames() if there is no generator which can generate`() { + // given + val bspConnectionDetailsGenerators = listOf(GeneratorWhichCantGenerate) + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe false + provider.availableGeneratorsNames() shouldBe emptyList() + } + + @Test + fun `should not generate if the generator name is wrong`() { + // given + val generator = GeneratorWhichCanGenerate("generator 1", generatedVirtualFile) + val bspConnectionDetailsGenerators = listOf(generator) + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe true + provider.availableGeneratorsNames() shouldContainExactlyInAnyOrder listOf("generator 1") + + provider.generateBspConnectionDetailFileForGeneratorWithName("wrong name") shouldBe null + generator.hasGenerated shouldBe false + } + + @Test + fun `should return true for canGenerateAnyBspConnectionDetailsFile(), list with one element for availableGeneratorsNames() and generate if there is one generator which can generate`() { + // given + val generator = GeneratorWhichCanGenerate("generator 1", generatedVirtualFile) + val bspConnectionDetailsGenerators = listOf(generator) + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe true + provider.availableGeneratorsNames() shouldContainExactlyInAnyOrder listOf("generator 1") + + provider.generateBspConnectionDetailFileForGeneratorWithName("generator 1") shouldBe generatedVirtualFile + generator.hasGenerated shouldBe true + } + + @Test + fun `should return true for canGenerateAnyBspConnectionDetailsFile(), list with generators which can generate for availableGeneratorsNames() and generate if there are multiple generators which can generate and few which cant`() { + // given + val generator1 = GeneratorWhichCanGenerate("generator 1", otherGeneratedVirtualFile) + val generator2 = GeneratorWhichCanGenerate("generator 2", generatedVirtualFile) + val generator3 = GeneratorWhichCanGenerate("generator 3", otherGeneratedVirtualFile) + val bspConnectionDetailsGenerators = + listOf(generator1, GeneratorWhichCantGenerate, generator2, generator3, GeneratorWhichCantGenerate) + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe true + provider.availableGeneratorsNames() shouldContainExactlyInAnyOrder listOf( + "generator 1", + "generator 2", + "generator 3" + ) + + provider.generateBspConnectionDetailFileForGeneratorWithName("generator 2") shouldBe generatedVirtualFile + generator1.hasGenerated shouldBe false + generator2.hasGenerated shouldBe true + generator3.hasGenerated shouldBe false + } + + @Test + fun `should call first generator with the given name`() { + // given + val generator1 = GeneratorWhichCanGenerate("generator 1", otherGeneratedVirtualFile) + val generator21 = GeneratorWhichCanGenerate("generator 2", generatedVirtualFile) + val generator22 = GeneratorWhichCanGenerate("generator 2", otherGeneratedVirtualFile) + val generator3 = GeneratorWhichCanGenerate("generator 3", otherGeneratedVirtualFile) + val bspConnectionDetailsGenerators = + listOf(generator1, GeneratorWhichCantGenerate, generator21, generator3, generator22, GeneratorWhichCantGenerate) + + // when + val provider = BspConnectionDetailsGeneratorProvider(projectPath.toVirtualFile(), bspConnectionDetailsGenerators) + + // then + provider.canGenerateAnyBspConnectionDetailsFile() shouldBe true + provider.availableGeneratorsNames() shouldContainExactlyInAnyOrder listOf( + "generator 1", + "generator 2", + "generator 2", + "generator 3" + ) + + provider.generateBspConnectionDetailFileForGeneratorWithName("generator 2") shouldBe generatedVirtualFile + generator1.hasGenerated shouldBe false + generator21.hasGenerated shouldBe true + generator22.hasGenerated shouldBe false + generator3.hasGenerated shouldBe false + } +} diff --git a/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProviderTest.kt b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProviderTest.kt new file mode 100644 index 000000000..c1c067279 --- /dev/null +++ b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionDetailsProviderTest.kt @@ -0,0 +1,9 @@ +package org.jetbrains.protocol.connection + +import org.junit.jupiter.api.Test + +//class BspConnectionDetailsProviderTest { +// +// @Test +// fun `should return false for canOpenBspProject() +//} \ No newline at end of file diff --git a/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProviderTest.kt b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProviderTest.kt new file mode 100644 index 000000000..b8456d533 --- /dev/null +++ b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/BspConnectionFilesProviderTest.kt @@ -0,0 +1,177 @@ +package org.jetbrains.protocol.connection + +import ch.epfl.scala.bsp4j.BspConnectionDetails +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import io.kotest.matchers.shouldBe +import org.jetbrains.workspace.model.test.framework.MockProjectBaseTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.nio.file.Files.createDirectory +import java.nio.file.Path +import kotlin.io.path.createFile +import kotlin.io.path.createTempDirectory +import kotlin.io.path.writeText + +class BspConnectionFilesProviderTest : MockProjectBaseTest() { + + private lateinit var projectPath: Path + private lateinit var provider: BspConnectionFilesProvider + + @BeforeEach + override fun beforeEach() { + // given + super.beforeEach() + + this.projectPath = createTempDirectory("project") + this.provider = BspConnectionFilesProvider(projectPath.toVirtualFile()) + } + + @AfterEach + fun afterEach() { + projectPath.toFile().deleteRecursively() + } + + @Test + fun `should return false for isAnyBspConnectionFileDefined and en empty list for connectionFiles if there is no configuration files`() { + // given + + // when & then + provider.isAnyBspConnectionFileDefined() shouldBe false + provider.connectionFiles shouldBe emptyList() + } + + @Test + fun `should return true for isAnyBspConnectionFileDefined and list with 1 element for connectionFiles if there is 1 configuration file`() { + // given + val dotBspDir = createDirectory(projectPath.resolve(".bsp")) + + val connectionFile1 = dotBspDir.resolve("connection-file-1.json").createFile() + connectionFile1.writeText( + """ + |{ + | "name": "build tool 1", + | "argv": ["build-tool-1", "bsp"], + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + // when & then + provider.isAnyBspConnectionFileDefined() shouldBe true + + val expectedConnectionFiles = listOf( + LocatedBspConnectionDetails( + bspConnectionDetails = BspConnectionDetails( + "build tool 1", + listOf("build-tool-1", "bsp"), + "1.2.37", + "2.0.0", + listOf("scala", "kotlin"), + ), + connectionFileLocation = connectionFile1.toVirtualFile(), + ) + ) + provider.connectionFiles shouldContainExactlyInAnyOrder expectedConnectionFiles + } + + @Test + fun `should return true for isAnyBspConnectionFileDefined and list with 3 element for connectionFiles if there are three configuration files and 1 non configuration file and 1 invalid connection file`() { + // given + val dotBspDir = createDirectory(projectPath.resolve(".bsp")) + + val connectionFile1 = dotBspDir.resolve("connection-file-1.json").createFile() + connectionFile1.writeText( + """ + |{ + | "name": "build tool 1", + | "argv": ["build-tool-1", "bsp"], + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + val connectionFile2 = dotBspDir.resolve("connection-file-2.json").createFile() + connectionFile2.writeText( + """ + |{ + | "name": "build tool 2", + | "argv": ["build-tool-2", "bsp"], + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + val nonConnectionFile = dotBspDir.resolve("non-connection-file.xd").createFile() + nonConnectionFile.writeText("random content") + + val connectionFile3 = dotBspDir.resolve("connection-file-3.json").createFile() + connectionFile3.writeText( + """ + |{ + | "name": "build tool 3", + | "argv": ["build-tool-3", "bsp"], + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + val invalidConnectionFile = dotBspDir.resolve("invalid-connection-file.json").createFile() + invalidConnectionFile.writeText( + """ + |{ + | "name": "invalid build tool", + | "argv": ["invalid + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + // when & then + provider.isAnyBspConnectionFileDefined() shouldBe true + + val expectedConnectionFiles = listOf( + LocatedBspConnectionDetails( + bspConnectionDetails = BspConnectionDetails( + "build tool 1", + listOf("build-tool-1", "bsp"), + "1.2.37", + "2.0.0", + listOf("scala", "kotlin"), + ), + connectionFileLocation = connectionFile1.toVirtualFile(), + ), + LocatedBspConnectionDetails( + bspConnectionDetails = BspConnectionDetails( + "build tool 2", + listOf("build-tool-2", "bsp"), + "1.2.37", + "2.0.0", + listOf("scala", "kotlin"), + ), + connectionFileLocation = connectionFile2.toVirtualFile(), + ), + LocatedBspConnectionDetails( + bspConnectionDetails = BspConnectionDetails( + "build tool 3", + listOf("build-tool-3", "bsp"), + "1.2.37", + "2.0.0", + listOf("scala", "kotlin"), + ), + connectionFileLocation = connectionFile3.toVirtualFile(), + ) + ) + provider.connectionFiles shouldContainExactlyInAnyOrder expectedConnectionFiles + } +} diff --git a/protocol/src/test/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetailsParserTest.kt b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetailsParserTest.kt new file mode 100644 index 000000000..cc5275258 --- /dev/null +++ b/protocol/src/test/kotlin/org/jetbrains/protocol/connection/LocatedBspConnectionDetailsParserTest.kt @@ -0,0 +1,84 @@ +package org.jetbrains.protocol.connection + +import ch.epfl.scala.bsp4j.BspConnectionDetails +import io.kotest.matchers.shouldBe +import org.jetbrains.workspace.model.test.framework.MockProjectBaseTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.nio.file.Path +import kotlin.io.path.createTempFile +import kotlin.io.path.deleteIfExists +import kotlin.io.path.writeText + +class LocatedBspConnectionDetailsParserTest : MockProjectBaseTest() { + + private lateinit var filePath: Path + + @BeforeEach + override fun beforeEach() { + // given + super.beforeEach() + + this.filePath = createTempFile("connection-file", ".json") + } + + @AfterEach + fun afterEach() { + filePath.deleteIfExists() + } + + @Test + fun `should return null if file contains invalid json`() { + // given + filePath.writeText( + """ + |{ + | "name": "build tool", + | "argv": ["build-tool", "bsp"], + | "version": "1.2.3 + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + // when + val locatedBspConnectionDetails = LocatedBspConnectionDetailsParser.parseFromFile(filePath.toVirtualFile()) + + // then + locatedBspConnectionDetails shouldBe null + } + + @Test + fun `should parse if file contains valid json`() { + // given + filePath.writeText( + """ + |{ + | "name": "build tool", + | "argv": ["build-tool", "bsp"], + | "version": "1.2.37", + | "bspVersion": "2.0.0", + | "languages": ["scala", "kotlin"] + |} + """.trimMargin() + ) + + // when + val locatedBspConnectionDetails = LocatedBspConnectionDetailsParser.parseFromFile(filePath.toVirtualFile()) + + // then + val expectedLocatedBspConnectionDetails = + LocatedBspConnectionDetails( + bspConnectionDetails = BspConnectionDetails( + "build tool", + listOf("build-tool", "bsp"), + "1.2.37", + "2.0.0", + listOf("scala", "kotlin"), + ), + connectionFileLocation = filePath.toVirtualFile(), + ) + locatedBspConnectionDetails shouldBe expectedLocatedBspConnectionDetails + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 99bcb86ac..9e5e98097 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,2 @@ rootProject.name = "intellij-bsp" -include("test-utils", "magicmetamodel") +include("test-utils", "magicmetamodel", "protocol") diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/extension/points/BspConnectionDetailsGeneratorExtension.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/extension/points/BspConnectionDetailsGeneratorExtension.kt new file mode 100644 index 000000000..40b62db30 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/extension/points/BspConnectionDetailsGeneratorExtension.kt @@ -0,0 +1,35 @@ +package org.jetbrains.plugins.bsp.extension.points + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.protocol.connection.BspConnectionDetailsGenerator + +public interface BspConnectionDetailsGeneratorExtension : BspConnectionDetailsGenerator { + + public companion object { + private val ep = + ExtensionPointName.create("com.intellij.bspConnectionDetailsGeneratorExtension") + + public fun extensions(): List = + ep.extensionList + } +} + +// TODO: NOT TESTED & SHOULD BE MOVED TO THE BAZEL PLUGIN +public class TemporaryBazelBspConnectionDetailsGenerator : BspConnectionDetailsGeneratorExtension { + + override fun name(): String = "bazel" + + override fun canGenerateBspConnectionDetailsFile(projectPath: VirtualFile): Boolean = + projectPath.children.any { it.name == "WORKSPACE" } + + override fun generateBspConnectionDetailsFile(projectPath: VirtualFile): VirtualFile { + Runtime.getRuntime().exec( + "cs launch org.jetbrains.bsp:bazel-bsp:2.1.0 -M org.jetbrains.bsp.bazel.install.Install", + emptyArray(), + projectPath.toNioPath().toFile() + ).waitFor() + + return projectPath.findChild(".bsp")?.findChild("bazelbsp.json")!! + } +} diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspInitializer.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspInitializer.kt new file mode 100644 index 000000000..a12ba1700 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspInitializer.kt @@ -0,0 +1,34 @@ +package org.jetbrains.plugins.bsp.import + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupActivity +import org.jetbrains.plugins.bsp.services.MagicMetaModelService + +/** + * Runs actions after the project has started up and the index is up to date. + * + * @see ProjectOpenActivity for actions that run earlier. + * @see BspProjectOpenProcessor for additional actions that + * may run when a project is being imported for the first time. + */ +public class BspInitializer : StartupActivity { + override fun runActivity(project: Project) { + println("BspInitializer.runActivity") + Logger.getInstance(ProjectOpenActivity::class.java).info("BspInitializer.runActivity") + + ApplicationManager.getApplication().invokeLater { + val magicMetaModelService = MagicMetaModelService.getInstance(project) + magicMetaModelService.initializeMagicModel() + + println("ProjectOpenActivity.invokeLater") + runWriteAction { + val magicMetaModel = magicMetaModelService.magicMetaModel + println("ProjectOpenActivity.runWriteAction") + magicMetaModel.loadDefaultTargets() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspProjectOpenProcessor.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspProjectOpenProcessor.kt index a8c6ec8f6..f4ebc5c41 100644 --- a/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspProjectOpenProcessor.kt +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/import/BspProjectOpenProcessor.kt @@ -2,15 +2,30 @@ package org.jetbrains.plugins.bsp.import import com.intellij.ide.impl.OpenProjectTask import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.project.ex.ProjectManagerEx +import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.vfs.VirtualFile +import com.intellij.platform.PlatformProjectOpenProcessor import com.intellij.projectImport.ProjectOpenProcessor +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.dsl.builder.Cell +import com.intellij.ui.dsl.builder.bind +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.selected +import com.intellij.ui.layout.not import org.jetbrains.plugins.bsp.config.BspPluginBundle import org.jetbrains.plugins.bsp.config.BspPluginIcons +import org.jetbrains.plugins.bsp.extension.points.BspConnectionDetailsGeneratorExtension +import org.jetbrains.plugins.bsp.services.BspConnectionService import org.jetbrains.plugins.bsp.services.MagicMetaModelService +import org.jetbrains.protocol.connection.BspConnectionDetailsGeneratorProvider +import org.jetbrains.protocol.connection.BspConnectionFilesProvider +import org.jetbrains.protocol.connection.LocatedBspConnectionDetailsParser import java.nio.file.Paths import javax.swing.Icon +import javax.swing.JComponent public class BspProjectOpenProcessor : ProjectOpenProcessor() { @@ -18,23 +33,108 @@ public class BspProjectOpenProcessor : ProjectOpenProcessor() { override fun getIcon(): Icon = BspPluginIcons.bsp - // TODO - override fun canOpenProject(file: VirtualFile): Boolean = true + override fun canOpenProject(file: VirtualFile): Boolean { + val bspConnectionFilesProvider = BspConnectionFilesProvider(file) + val bspConnectionDetailsGeneratorProvider = + BspConnectionDetailsGeneratorProvider(file, BspConnectionDetailsGeneratorExtension.extensions()) + + return bspConnectionFilesProvider.isAnyBspConnectionFileDefined() or + bspConnectionDetailsGeneratorProvider.canGenerateAnyBspConnectionDetailsFile() + } override fun doOpenProject( virtualFile: VirtualFile, projectToClose: Project?, forceOpenInNewFrame: Boolean ): Project? { - // TODO better options - val options = OpenProjectTask(isNewProject = true) - val project = ProjectManagerEx.getInstanceEx().openProject(Paths.get(virtualFile.path), options)!! + val bspConnectionFilesProvider = BspConnectionFilesProvider(virtualFile) + val bspConnectionDetailsGeneratorProvider = + BspConnectionDetailsGeneratorProvider(virtualFile, BspConnectionDetailsGeneratorExtension.extensions()) + + val dialog = TemporaryBspImportDialog(bspConnectionFilesProvider, bspConnectionDetailsGeneratorProvider) + + return if (dialog.showAndGet()) { + // TODO better options + val options = OpenProjectTask(isNewProject = true) +// val project = ProjectManagerEx.getInstanceEx().openProject(Paths.get(virtualFile.path), options)!! + + val project = PlatformProjectOpenProcessor.getInstance().doOpenProject(virtualFile, projectToClose, forceOpenInNewFrame) + + if (project != null) { + + val connectionService = BspConnectionService.getInstance(project) + + if (dialog.buildToolUsed.selected()) { + val xd = dialog.buildTool + val xd1 = bspConnectionDetailsGeneratorProvider.generateBspConnectionDetailFileForGeneratorWithName(xd) + val xd2 = LocatedBspConnectionDetailsParser.parseFromFile(xd1!!) + connectionService.connect(xd2!!) + } else { + val xd = bspConnectionFilesProvider.connectionFiles[dialog.connectionFileId] + connectionService.connect(xd) + } + +// val magicMetaModelService = MagicMetaModelService.getInstance(project) +// magicMetaModelService.initializeMagicModel() +// val magicMetaModel = magicMetaModelService.magicMetaModel + +// runWriteAction { magicMetaModel.loadDefaultTargets() } + + project + } else null + } else null + +// { +// val bspDir = getBspDir(virtualFile) ?: return null +// val baseDir = bspDir.parent ?: return null +// +// } + } + + private inner class TemporaryBspImportDialog( + private val bspConnectionFilesProvider: BspConnectionFilesProvider, + private val bspConnectionDetailsGeneratorProvider: BspConnectionDetailsGeneratorProvider + ) : DialogWrapper(true) { + + lateinit var buildToolUsed: Cell + + var connectionFileId = 0 + + var buildTool = bspConnectionDetailsGeneratorProvider + .availableGeneratorsNames().firstOrNull() ?: "" + + init { + title = "Select An Import Method (Temporary)" + init() + } + + override fun createCenterPanel(): JComponent = + panel { - val magicMetaModelService = MagicMetaModelService.getInstance(project) - val magicMetaModel = magicMetaModelService.magicMetaModel + row { + buildToolUsed = checkBox("Use build tool") + } - runWriteAction { magicMetaModel.loadDefaultTargets() } + buttonsGroup(title = "Detected Connection Files:") { + bspConnectionFilesProvider + .connectionFiles + .mapIndexed { id, a -> + row { + radioButton("name: ${a.bspConnectionDetails.name} location: ${a.connectionFileLocation.url}", id) + .enabledIf(buildToolUsed.selected.not()) + } + } + }.bind(::connectionFileId) - return project + buttonsGroup(title = "Detected Build Tools:") { + bspConnectionDetailsGeneratorProvider + .availableGeneratorsNames() + .map { + row { + radioButton(it, it).enabledIf(buildToolUsed.selected) + } + } + }.bind(::buildTool) + } } } diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/import/ProjectOpenActivity.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/import/ProjectOpenActivity.kt new file mode 100644 index 000000000..483c8b956 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/import/ProjectOpenActivity.kt @@ -0,0 +1,26 @@ +package org.jetbrains.plugins.bsp.import + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.StartupActivity +import com.intellij.openapi.startup.StartupManager +import org.jetbrains.plugins.bsp.services.BuildWindowSomething +import org.jetbrains.plugins.bsp.services.MagicMetaModelService + +/** + * Runs startup actions just after a project is opened, before it's indexed. + * + * @see BspInitializer for actions that run later. + */ +public class ProjectOpenActivity : StartupActivity, StartupActivity.DumbAware { + override fun runActivity(project: Project) { + println("ProjectOpenActivity.runActivity") + Logger.getInstance(ProjectOpenActivity::class.java).info("ProjectOpenActivity.runActivity") + + BuildWindowSomething(project) + + + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspConnection.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspConnection.kt deleted file mode 100644 index ee9bc193b..000000000 --- a/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspConnection.kt +++ /dev/null @@ -1,95 +0,0 @@ -package org.jetbrains.plugins.bsp.protocol - -import ch.epfl.scala.bsp4j.BspConnectionDetails -import ch.epfl.scala.bsp4j.BuildClient -import ch.epfl.scala.bsp4j.BuildServer -import ch.epfl.scala.bsp4j.DidChangeBuildTarget -import ch.epfl.scala.bsp4j.LogMessageParams -import ch.epfl.scala.bsp4j.PublishDiagnosticsParams -import ch.epfl.scala.bsp4j.ShowMessageParams -import ch.epfl.scala.bsp4j.TaskFinishParams -import ch.epfl.scala.bsp4j.TaskProgressParams -import ch.epfl.scala.bsp4j.TaskStartParams -import com.google.gson.Gson -import com.intellij.util.concurrency.AppExecutorUtil -import org.eclipse.lsp4j.jsonrpc.Launcher -import java.io.File -import java.nio.file.Path - -public interface BspServer : BuildServer - -internal class VeryTemporaryBspConnection(projectBaseDir: Path) { - - val bspServer = connect(projectBaseDir) - - private fun connect(projectBaseDir: Path): BspServer { - val config = getConfig(projectBaseDir) - - val client = BspClient() - val process = ProcessBuilder(config.argv) - .directory(projectBaseDir.toFile()) - .start() - - val launcher = Launcher.Builder() - .setRemoteInterface(BspServer::class.java) - .setExecutorService(AppExecutorUtil.getAppExecutorService()) - .setInput(process.inputStream) - .setOutput(process.outputStream) - .setLocalService(client) - .create() - - launcher.startListening() - val server = launcher.remoteProxy - client.onConnectWithServer(server) - - return server - } - - private fun getConfig(projectBaseDir: Path): BspConnectionDetails { - val configFile = getConfigFile(projectBaseDir) - - return Gson().fromJson(configFile.readText(), BspConnectionDetails::class.java) - } - - private fun getConfigFile(projectBaseDir: Path): File = - File(projectBaseDir.toFile(), ".bsp") - .listFiles { file -> file.name.endsWith(".json") } - .first() -} - -private class BspClient : BuildClient { - override fun onBuildShowMessage(params: ShowMessageParams?) { - println("onBuildShowMessage") - println(params) - } - - override fun onBuildLogMessage(params: LogMessageParams?) { - println("onBuildLogMessage") - println(params) - } - - override fun onBuildTaskStart(params: TaskStartParams?) { - println("onBuildTaskStart") - println(params) - } - - override fun onBuildTaskProgress(params: TaskProgressParams?) { - println("onBuildTaskProgress") - println(params) - } - - override fun onBuildTaskFinish(params: TaskFinishParams?) { - println("onBuildTaskFinish") - println(params) - } - - override fun onBuildPublishDiagnostics(params: PublishDiagnosticsParams?) { - println("onBuildPublishDiagnostics") - println(params) - } - - override fun onBuildTargetDidChange(params: DidChangeBuildTarget?) { - println("onBuildTargetDidChange") - println(params) - } -} diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspResolver.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspResolver.kt deleted file mode 100644 index 2a1167f88..000000000 --- a/src/main/kotlin/org/jetbrains/plugins/bsp/protocol/VeryTemporaryBspResolver.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.jetbrains.plugins.bsp.protocol - -import ch.epfl.scala.bsp4j.BuildClientCapabilities -import ch.epfl.scala.bsp4j.BuildTarget -import ch.epfl.scala.bsp4j.DependencySourcesParams -import ch.epfl.scala.bsp4j.InitializeBuildParams -import ch.epfl.scala.bsp4j.ResourcesParams -import ch.epfl.scala.bsp4j.SourcesParams -import com.google.gson.JsonArray -import com.google.gson.JsonObject -import org.jetbrains.magicmetamodel.ProjectDetails -import java.nio.file.Path - -public class VeryTemporaryBspResolver(private val projectBaseDir: Path) { - - private val server = VeryTemporaryBspConnection(projectBaseDir).bspServer - - public fun collectModel(): ProjectDetails { - println("buildInitialize") - server.buildInitialize(createInitializeBuildParams()).get() - - println("onBuildInitialized") - server.onBuildInitialized() - - println("workspaceBuildTargets") - val workspaceBuildTargetsResult = server.workspaceBuildTargets().get() - val allTargetsIds = workspaceBuildTargetsResult.targets.map(BuildTarget::getId) - - println("buildTargetSources") - val sourcesResult = server.buildTargetSources(SourcesParams(allTargetsIds)).get() - - println("buildTargetResources") - val resourcesResult = server.buildTargetResources(ResourcesParams(allTargetsIds)).get() - - println("buildTargetDependencySources") - val dependencySourcesResult = server.buildTargetDependencySources(DependencySourcesParams(allTargetsIds)).get() - - println("done!") - - return ProjectDetails( - targetsId = allTargetsIds, - targets = workspaceBuildTargetsResult.targets.toSet(), - sources = sourcesResult.items, - resources = resourcesResult.items, - dependenciesSources = dependencySourcesResult.items, - ) - } - - private fun createInitializeBuildParams(): InitializeBuildParams { - val params = InitializeBuildParams( - "IntelliJ-BSP", - "1.0.0", - "2.0.0", - projectBaseDir.toString(), - BuildClientCapabilities(listOf("java")) - ) - val dataJson = JsonObject() - dataJson.addProperty("clientClassesRootDir", "$projectBaseDir/out") - dataJson.add("supportedScalaVersions", JsonArray()) - params.data = dataJson - - return params - } -} diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/services/BspConnectionService.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/services/BspConnectionService.kt new file mode 100644 index 000000000..f10f129a1 --- /dev/null +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/services/BspConnectionService.kt @@ -0,0 +1,226 @@ +package org.jetbrains.plugins.bsp.services + +import ch.epfl.scala.bsp4j.BspConnectionDetails +import ch.epfl.scala.bsp4j.BuildClient +import ch.epfl.scala.bsp4j.BuildClientCapabilities +import ch.epfl.scala.bsp4j.BuildServer +import ch.epfl.scala.bsp4j.BuildTarget +import ch.epfl.scala.bsp4j.BuildTargetIdentifier +import ch.epfl.scala.bsp4j.DependencySourcesParams +import ch.epfl.scala.bsp4j.DependencySourcesResult +import ch.epfl.scala.bsp4j.DidChangeBuildTarget +import ch.epfl.scala.bsp4j.InitializeBuildParams +import ch.epfl.scala.bsp4j.LogMessageParams +import ch.epfl.scala.bsp4j.PublishDiagnosticsParams +import ch.epfl.scala.bsp4j.ResourcesParams +import ch.epfl.scala.bsp4j.ResourcesResult +import ch.epfl.scala.bsp4j.ShowMessageParams +import ch.epfl.scala.bsp4j.SourcesParams +import ch.epfl.scala.bsp4j.SourcesResult +import ch.epfl.scala.bsp4j.TaskFinishParams +import ch.epfl.scala.bsp4j.TaskProgressParams +import ch.epfl.scala.bsp4j.TaskStartParams +import ch.epfl.scala.bsp4j.WorkspaceBuildTargetsResult +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.intellij.build.AbstractViewManager +import com.intellij.build.DefaultBuildDescriptor +import com.intellij.build.SyncViewManager +import com.intellij.build.events.MessageEvent +import com.intellij.build.events.impl.FinishBuildEventImpl +import com.intellij.build.events.impl.MessageEventImpl +import com.intellij.build.events.impl.ProgressBuildEventImpl +import com.intellij.build.events.impl.StartBuildEventImpl +import com.intellij.build.events.impl.SuccessResultImpl +import com.intellij.openapi.project.Project +import com.intellij.project.stateStore +import com.intellij.util.concurrency.AppExecutorUtil +import org.eclipse.lsp4j.jsonrpc.Launcher +import org.jetbrains.magicmetamodel.ProjectDetails +import org.jetbrains.protocol.connection.LocatedBspConnectionDetails +import java.nio.file.Path + +public interface BspServer : BuildServer + +public class BspConnectionService(private val project: Project) { + + public var server: BspServer? = null + + public var bspResolver: VeryTemporaryBspResolver? = null + + public fun connect(connectionFile: LocatedBspConnectionDetails) { + val process = createAndStartProcess(connectionFile.bspConnectionDetails) + // TODO + val buildView = project.getService(SyncViewManager::class.java) + + val client = BspClient(buildView, "xd2", "xd") + + val launcher = createLauncher(process, client) + + launcher.startListening() + server = launcher.remoteProxy + client.onConnectWithServer(server) + + bspResolver = VeryTemporaryBspResolver(project.stateStore.projectBasePath, server!!, project, buildView) + } + + private fun createAndStartProcess(bspConnectionDetails: BspConnectionDetails): Process = + ProcessBuilder(bspConnectionDetails.argv) + .directory(project.stateStore.projectBasePath.toFile()) + .start() + + private fun createLauncher(process: Process, client: BuildClient): Launcher = + Launcher.Builder() + .setRemoteInterface(BspServer::class.java) + .setExecutorService(AppExecutorUtil.getAppExecutorService()) + .setInput(process.inputStream) + .setOutput(process.outputStream) + .setLocalService(client) + .create() + + public fun disconnect() { + } + + public companion object { + public fun getInstance(project: Project): BspConnectionService = + project.getService(BspConnectionService::class.java) + } +} + + +public class VeryTemporaryBspResolver(private val projectBaseDir: Path, private val server: BspServer, private val project: Project, private val buildViewManager: AbstractViewManager) { + + public fun collectModel(): ProjectDetails { + val buildId = "xd" + val title = "Title 2" + val basePath = project.basePath!! + val buildDescriptor = DefaultBuildDescriptor(buildId, title, basePath, System.currentTimeMillis()) + + + var workspaceBuildTargetsResult: WorkspaceBuildTargetsResult? = null + var allTargetsIds: List? = null + var sourcesResult: SourcesResult? = null + var resourcesResult: ResourcesResult? = null + var dependencySourcesResult: DependencySourcesResult? = null + +// val task = object : Task.Backgroundable(project, "Loading changes 1 ", true) { +// +// override fun run(indicator: ProgressIndicator) { + + val startEvent = StartBuildEventImpl(buildDescriptor, "message") + buildViewManager.onEvent(buildId, startEvent) + +// indicator.text = "Loading changes 2" +// indicator.isIndeterminate = true +// indicator.fraction = 0.0 + val progressEvent = ProgressBuildEventImpl( + "xd2", buildId, System.currentTimeMillis(), "Tmport", -1, + -1, "kłykcie" + ) + buildViewManager.onEvent(buildId, progressEvent) + + println("buildInitialize") + server.buildInitialize(createInitializeBuildParams()).get() + + println("onBuildInitialized") + server.onBuildInitialized() + + println("workspaceBuildTargets") + workspaceBuildTargetsResult = server.workspaceBuildTargets().get() + allTargetsIds = workspaceBuildTargetsResult!!.targets.map(BuildTarget::getId) + + println("buildTargetSources") + sourcesResult = server.buildTargetSources(SourcesParams(allTargetsIds)).get() + + println("buildTargetResources") + resourcesResult = server.buildTargetResources(ResourcesParams(allTargetsIds)).get() + + println("buildTargetDependencySources") + dependencySourcesResult = server.buildTargetDependencySources(DependencySourcesParams(allTargetsIds)).get() + + println("done!") + val xd = FinishBuildEventImpl( + "xd2", null, System.currentTimeMillis(), "Tmport 2", SuccessResultImpl() + ) + buildViewManager.onEvent(buildId, xd) + println("done!") + val xd2 = FinishBuildEventImpl( + buildId, null, System.currentTimeMillis(), "Tmport 233", SuccessResultImpl() + ) + buildViewManager.onEvent(buildId, xd2) +// buildViewManager.dispose() +// } +// } +// task.queue() +// ProgressManager.getInstance().run(task) + + + println("done done!") + return ProjectDetails( + targetsId = allTargetsIds!!, + targets = workspaceBuildTargetsResult!!.targets.toSet(), + sources = sourcesResult!!.items, + resources = resourcesResult!!.items, + dependenciesSources = dependencySourcesResult!!.items, + ) + } + + private fun createInitializeBuildParams(): InitializeBuildParams { + val params = InitializeBuildParams( + "IntelliJ-BSP", + "1.0.0", + "2.0.0", + projectBaseDir.toString(), + BuildClientCapabilities(listOf("java")) + ) + val dataJson = JsonObject() + dataJson.addProperty("clientClassesRootDir", "$projectBaseDir/out") + dataJson.add("supportedScalaVersions", JsonArray()) + params.data = dataJson + + return params + } +} + +private class BspClient(private val buildViewManager: AbstractViewManager, private val randomId: String, private val buildId: String) : BuildClient { + override fun onBuildShowMessage(params: ShowMessageParams?) { + println("onBuildShowMessage") + println(params) + + val event = MessageEventImpl(randomId, MessageEvent.Kind.SIMPLE, null, "", params?.message) + buildViewManager.onEvent(buildId, event) + } + + override fun onBuildLogMessage(params: LogMessageParams?) { + println("onBuildLogMessage") + println(params) + + val event = MessageEventImpl(randomId, MessageEvent.Kind.SIMPLE, null, "", params?.message) + buildViewManager.onEvent(buildId, event) + } + + override fun onBuildTaskStart(params: TaskStartParams?) { + println("onBuildTaskStart") + println(params) + } + + override fun onBuildTaskProgress(params: TaskProgressParams?) { + println("onBuildTaskProgress") + println(params) + } + + override fun onBuildTaskFinish(params: TaskFinishParams?) { + println("onBuildTaskFinish") + println(params) + } + + override fun onBuildPublishDiagnostics(params: PublishDiagnosticsParams?) { + println("onBuildPublishDiagnostics") + println(params) + } + + override fun onBuildTargetDidChange(params: DidChangeBuildTarget?) { + println("onBuildTargetDidChange") + println(params) + } +} diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/services/BuildWindowSomething.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/services/BuildWindowSomething.kt new file mode 100644 index 000000000..ff640f0ba --- /dev/null +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/services/BuildWindowSomething.kt @@ -0,0 +1,58 @@ +package org.jetbrains.plugins.bsp.services + +import com.intellij.build.BuildViewManager +import com.intellij.build.DefaultBuildDescriptor +import com.intellij.build.events.MessageEvent +import com.intellij.build.events.impl.MessageEventImpl +import com.intellij.build.events.impl.ProgressBuildEventImpl +import com.intellij.build.events.impl.StartBuildEventImpl +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runInEdt +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressIndicatorProvider +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import kotlinx.coroutines.runBlocking +import org.jetbrains.concurrency.runAsync + + +public class BuildWindowSomething(project: Project) { + init { + val buildView = project.getService(BuildViewManager::class.java) + val buildId = "buildId" + val title = "Title 2" + val basePath = project.basePath!! + val buildDescriptor = DefaultBuildDescriptor(buildId, title, basePath, System.currentTimeMillis()) + val startEvent = StartBuildEventImpl(buildDescriptor, "message") + buildView.onEvent(buildId, startEvent) + + val task = object : Task.Backgroundable(project, "Loading changes 1 ", false) { + override fun run(indicator: ProgressIndicator) { + + indicator.text = "Loading changes 2" + indicator.isIndeterminate = true +// indicator.fraction = 0.0; + for (i in 1..10) { + Thread.sleep(1000) +// indicator.fraction = i / 10.0 + val progressEvent = ProgressBuildEventImpl( + "nowe id $i", buildId, System.currentTimeMillis(), "message$i", -1, + -1, "kłykcie" + ) + + val a = MessageEventImpl("nowe id $i", MessageEvent.Kind.SIMPLE, "daas", "loloo 1", "loll2") + +// progressEvent.description = "XDDDDD" +// a.description = "XDDDD" + buildView.onEvent(buildId, progressEvent) + buildView.onEvent(buildId, a) + } +// indicator.fraction = 1.0; + } + } + + ProgressManager.getInstance().run(task) + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/jetbrains/plugins/bsp/services/MagicMetaModelService.kt b/src/main/kotlin/org/jetbrains/plugins/bsp/services/MagicMetaModelService.kt index 5c56700ba..448885cf8 100644 --- a/src/main/kotlin/org/jetbrains/plugins/bsp/services/MagicMetaModelService.kt +++ b/src/main/kotlin/org/jetbrains/plugins/bsp/services/MagicMetaModelService.kt @@ -7,18 +7,18 @@ import com.intellij.workspaceModel.ide.getInstance import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager import org.jetbrains.magicmetamodel.MagicMetaModel import org.jetbrains.magicmetamodel.MagicMetaModelProjectConfig -import org.jetbrains.plugins.bsp.protocol.VeryTemporaryBspResolver -public class MagicMetaModelService(project: Project) { +public class MagicMetaModelService(private val project: Project) { - public val magicMetaModel: MagicMetaModel = initializeMagicModel(project) + public lateinit var magicMetaModel: MagicMetaModel - private fun initializeMagicModel(project: Project): MagicMetaModel { + private val bspConnectionService = BspConnectionService.getInstance(project) + + public fun initializeMagicModel() { val magicMetaModelProjectConfig = calculateProjectConfig(project) - val bspResolver = VeryTemporaryBspResolver(magicMetaModelProjectConfig.projectBaseDir) - val projectDetails = bspResolver.collectModel() + val projectDetails = bspConnectionService.bspResolver!!.collectModel() - return MagicMetaModel.create(magicMetaModelProjectConfig, projectDetails) + magicMetaModel = MagicMetaModel.create(magicMetaModelProjectConfig, projectDetails) } private fun calculateProjectConfig(project: Project): MagicMetaModelProjectConfig { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5f95aef5f..17282f922 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -3,13 +3,27 @@ Build Server Protocol JetBrains - - com.intellij.modules.platform + + + + + + + + + + + diff --git a/test-utils/src/main/kotlin/org/jetbrains/workspace/model/test/framework/MockProjectBaseTest.kt b/test-utils/src/main/kotlin/org/jetbrains/workspace/model/test/framework/MockProjectBaseTest.kt new file mode 100644 index 000000000..1bfa9ff01 --- /dev/null +++ b/test-utils/src/main/kotlin/org/jetbrains/workspace/model/test/framework/MockProjectBaseTest.kt @@ -0,0 +1,33 @@ +package org.jetbrains.workspace.model.test.framework + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.workspaceModel.ide.getInstance +import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager +import org.junit.jupiter.api.BeforeEach +import java.nio.file.Path + +public open class MockProjectBaseTest { + + protected lateinit var project: Project + protected lateinit var virtualFileManager: VirtualFileManager + + @BeforeEach + protected open fun beforeEach() { + project = emptyProjectTestMock() + virtualFileManager = VirtualFileManager.getInstance() + } + + private fun emptyProjectTestMock(): Project { + val factory = IdeaTestFixtureFactory.getFixtureFactory() + val fixtureBuilder = factory.createFixtureBuilder("test", true) + val fixture = fixtureBuilder.fixture + fixture.setUp() + + return fixture.project + } + + protected fun Path.toVirtualFile(): VirtualFile = virtualFileManager.findFileByNioPath(this)!! +}