fix(projects): update entries from module source class indices on file events

This commit is contained in:
Akash Yadav
2023-03-13 18:04:21 +05:30
parent 674e367933
commit 4149e3909e
6 changed files with 121 additions and 19 deletions
Binary file not shown.
@@ -220,6 +220,10 @@ open class ClassTrie(val root: Node = Node()) {
return children.computeIfAbsent(name) { Node(it, qualifiedName, this) }
}
open fun removeChild(child: Node) {
this.children.remove(child.name)
}
open fun allClassNames(): Set<String> {
return this.allClassNodes().map { it.qualifiedName }.toSet()
}
@@ -32,9 +32,11 @@ import com.itsaky.androidide.projects.util.ProjectTransformer
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
@@ -148,10 +150,6 @@ object ProjectManager : EventReceiver {
return app
}
fun getApplicationResDirectories(): Set<File> {
return getApplicationModule()?.getResourceDirectories() ?: emptySet()
}
fun notifyProjectUpdate() {
executeAsync {
@@ -170,16 +168,18 @@ object ProjectManager : EventReceiver {
}
}
fun findModuleForFile(file: File): ModuleProject? {
@JvmOverloads
fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject? {
if (!checkInit()) {
return null
}
return this.rootProject!!.findModuleForFile(file)
return this.rootProject!!.findModuleForFile(file, checkExistance)
}
fun findModuleForFile(file: Path): ModuleProject? {
return findModuleForFile(file.toFile())
@JvmOverloads
fun findModuleForFile(file: Path, checkExistance: Boolean = false): ModuleProject? {
return findModuleForFile(file.toFile(), checkExistance)
}
fun containsSourceFile(file: Path): Boolean {
@@ -187,6 +187,10 @@ object ProjectManager : EventReceiver {
return false
}
if (!Files.exists(file)) {
return false
}
for (module in this.rootProject!!.subModules) {
if (module !is ModuleProject) {
continue
@@ -245,18 +249,55 @@ object ProjectManager : EventReceiver {
@Subscribe(threadMode = BACKGROUND)
fun onFileCreated(event: FileCreationEvent) {
generateSourcesIfNecessary(event)
if (DocumentUtils.isJavaFile(event.file.toPath())) {
findModuleForFile(event.file)?.let {
val sourceRoot = it.findSourceRoot(event.file) ?: return@let
// add the source node entry
it.compileJavaSourceClasses.append(event.file.toPath(), sourceRoot)
}
}
}
@Suppress("unused")
@Subscribe(threadMode = BACKGROUND)
fun onFileDeleted(event: FileDeletionEvent) {
generateSourcesIfNecessary(event)
// Remove the source node entry
// Do not check for Java file DocumentUtils.isJavaFile(...) as it checks for file existence as
// well. As the file is already deleted, it will always return false
if (event.file.extension == "java") {
findModuleForFile(event.file)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
}
@Suppress("unused")
@Subscribe(threadMode = BACKGROUND)
fun onFileRenamed(event: FileRenameEvent) {
generateSourcesIfNecessary(event)
// Do not check for Java file DocumentUtils.isJavaFile(...) as it checks for file existence as
// well. As the file is already renamed to another filename, it will always return false
if (event.file.extension == "java") {
// remove the source node entry
findModuleForFile(event.file)
?.compileJavaSourceClasses
?.findSource(event.file.toPath())
?.let { it.parent?.removeChild(it) }
}
if (DocumentUtils.isJavaFile(event.newFile.toPath())) {
findModuleForFile(event.newFile)?.let {
val sourceRoot = it.findSourceRoot(event.newFile) ?: return@let
// add the new source node entry
it.compileJavaSourceClasses.append(event.newFile.toPath(), sourceRoot)
}
}
}
private fun generateSourcesIfNecessary(event: FileEvent) {
@@ -269,6 +310,10 @@ object ProjectManager : EventReceiver {
generateSources(builder)
}
private fun ModuleProject.findSourceRoot(file: File): Path? {
return getCompileSourceDirectories().find { file.path.startsWith(it.path) }?.toPath()
}
private fun isResource(file: File): Boolean {
val module = findModuleForFile(file) ?: return false
if (module is AndroidModule) {
@@ -33,7 +33,7 @@ import java.util.concurrent.*
* @param name The display name of the project.
* @param description The project description.
* @param path The project path (same as Gradle project paths). For example, `:app`,
* `:module:submodule`, etc. Root project is always represented by path `:`.
* `:module:submodule`, etc. Root project is always represented by path `:`.
* @param projectDir The project directory.
* @param buildDir The build directory of the project.
* @param buildScript The Gradle buildscript file of the project.
@@ -124,7 +124,13 @@ open class Project(
return findModuleForFile(file.toFile())
}
fun findModuleForFile(file: File): ModuleProject? {
@JvmOverloads
fun findModuleForFile(file: File, checkExistance: Boolean = false): ModuleProject? {
if (!file.exists() && checkExistance) {
return null
}
val path = file.path
var longestPath = ""
var moduleWithLongestPath: ModuleProject? = null
@@ -18,18 +18,23 @@
package com.itsaky.androidide.projects.api
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.eventbus.events.file.FileCreationEvent
import com.itsaky.androidide.eventbus.events.file.FileDeletionEvent
import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.utils.SourceClassTrie.SourceNode
import java.io.File
import java.nio.file.Files
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.writeText
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@@ -40,9 +45,8 @@ class ModuleProjectTest {
fun test() {
val (server, project) = ToolingApiTestLauncher().launchServer()
server.initialize(InitializeProjectMessage(File("../../tests/test-project").absolutePath)).get()
Lookup.DEFAULT.register(BuildService.KEY_PROJECT_PROXY, project)
ProjectManager.setupProject()
verifyProjectManagerAPIs()
@@ -177,18 +181,35 @@ class ModuleProjectTest {
}
private fun verifyProjectManagerAPIs() {
val testCls =
Paths.get("../../tests/test-project/app/src/main/java/com/itsaky/test/app/Test.java")
.toAbsolutePath()
.normalize()
val testCls_renamed = testCls.parent!!.resolve("TestRenamed.java")
if (Files.exists(testCls)) {
Files.delete(testCls)
}
if (Files.exists(testCls_renamed)) {
Files.delete(testCls_renamed)
}
ProjectManager.setupProject()
val root = ProjectManager.rootProject
assertThat(root).isNotNull()
assertThat(root!!.path).isEqualTo(":")
val source =
root.projectDir.toPath().resolve("app/src/main/java/com/itsaky/test/app/MainActivity.java")
val moduleForFile = root.findModuleForFile(source)
assertThat(moduleForFile).isNotNull()
assertThat(moduleForFile!!.path).isEqualTo(":app")
assertThat(moduleForFile.projectDir.path).isEqualTo(File(root.projectDir, "app").path)
val module = root.findModuleForFile(source)
assertThat(module).isNotNull()
assertThat(module!!.path).isEqualTo(":app")
assertThat(module.projectDir.path).isEqualTo(File(root.projectDir, "app").path)
val sourceNode = moduleForFile.compileJavaSourceClasses.findSource(source)
val sourceNode = module.compileJavaSourceClasses.findSource(source)
assertThat(sourceNode).isNotNull()
assertThat(sourceNode!!.isClass).isTrue()
assertThat(sourceNode.name).isEqualTo("MainActivity")
@@ -196,5 +217,30 @@ class ModuleProjectTest {
assertThat(sourceNode.qualifiedName).isEqualTo("com.itsaky.test.app.MainActivity")
assertThat(sourceNode.children).isEmpty()
assertThat(Files.isSameFile(source, sourceNode.file)).isTrue()
// make sure the files are not indexed
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
testCls.writeText("public class Test { }")
ProjectManager.onFileCreated(FileCreationEvent(testCls.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isTrue()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isEqualTo(module)
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isNull()
Files.move(testCls, testCls_renamed)
ProjectManager.onFileRenamed(FileRenameEvent(testCls.toFile(), testCls_renamed.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isTrue()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isNull()
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isEqualTo(module)
Files.delete(testCls_renamed)
ProjectManager.onFileDeleted(FileDeletionEvent(testCls_renamed.toFile()))
assertThat(ProjectManager.containsSourceFile(testCls)).isFalse()
assertThat(ProjectManager.containsSourceFile(testCls_renamed)).isFalse()
assertThat(ProjectManager.findModuleForFile(testCls, true)).isNull()
assertThat(ProjectManager.findModuleForFile(testCls_renamed, true)).isNull()
}
}
@@ -0,0 +1 @@
public class Test { }