Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package org.jetbrains.bazel.languages.projectview

import com.google.common.hash.HashCode
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.readAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiManager
import org.jetbrains.bazel.config.rootDir
import org.jetbrains.bazel.flow.open.ProjectViewFileUtils
import org.jetbrains.bazel.languages.projectview.base.ProjectViewFileType
import org.jetbrains.bazel.languages.projectview.base.ProjectViewLanguage
import org.jetbrains.bazel.languages.projectview.psi.ProjectViewPsiFile
import org.jetbrains.bazel.settings.bazel.bazelProjectSettings
Expand All @@ -24,6 +31,7 @@ import kotlin.io.path.notExists
*/
@Service(Service.Level.PROJECT)
class ProjectViewService(private val project: Project) {
private val log = logger<ProjectViewService>()
fun getDefaultProjectViewFileContent(): ProjectView {
val content = ProjectViewFileUtils.projectViewTemplate(project).format(".")
val psiFile = PsiFileFactory.getInstance(project)
Expand Down Expand Up @@ -69,20 +77,80 @@ class ProjectViewService(private val project: Project) {

private fun parseProjectView(projectViewPath: Path): ProjectView {
return runBlockingCancellable {
val projectView = readAction {
val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(projectViewPath)
?: return@readAction null
val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(projectViewPath)
?: return@runBlockingCancellable readAction { getDefaultProjectViewFileContent() }

val psiFile = readAction { PsiManager.getInstance(project).findFile(virtualFile) }

val psi = PsiManager.getInstance(project).findFile(virtualFile) as? ProjectViewPsiFile
?: return@readAction null
ensureCorrectFileType(virtualFile, psiFile, projectViewPath)

return@readAction ProjectView.fromProjectViewPsiFile(psi)
val projectView = readAction {
val psi = psiFile as? ProjectViewPsiFile ?: return@readAction null
ProjectView.fromProjectViewPsiFile(psi)
}

return@runBlockingCancellable projectView
?: readAction { getDefaultProjectViewFileContent() }
}
}

/**
* Ensures that .bazelproject file has correct ProjectView file type.
* Attempts auto-fix if needed, throws exception if fails.
*/
private fun ensureCorrectFileType(virtualFile: VirtualFile, psiFile: PsiFile?, projectViewPath: Path) {
if (psiFile is ProjectViewPsiFile) {
return
}

if (psiFile != null) {
log.warn("ProjectView file type not recognized: path=$projectViewPath, fileType=${virtualFile.fileType.name}, language=${psiFile.language}")

if (!tryAutoFixFileTypeRegistration(virtualFile)) {
throw IllegalStateException("""
.bazelproject file not recognized as ProjectView file type.

Please fix manually:
1. Go to Settings → Editor → File Types
2. Find "ProjectView file for Bazel project" in the list
3. Add "*.bazelproject" to File name patterns
4. Restart IDE and try sync again
""".trimIndent())
}
}
}

/**
* Attempts to auto-fix ProjectView file type registration by associating *.bazelproject pattern
* with ProjectViewFileType.
*
* @param virtualFile the .bazelproject file that needs to be fixed
* @return true if auto-fix succeeded, false if it failed
*/
private fun tryAutoFixFileTypeRegistration(virtualFile: VirtualFile): Boolean {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me this method breaks SRP. It doesn't check that file is of necessary type, and makes a lot of assumes.
I would suggests to change this method to do fileTypeManager.associatePattern(ProjectViewFileType, "*.bazelproject") only, and forcinf virtualFile refresh outside upon success.

log.info("Attempting to auto-fix ProjectView file type registration...")

return try {
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
val fileTypeManager = FileTypeManager.getInstance()
fileTypeManager.associatePattern(ProjectViewFileType, "*.bazelproject")
}
}

// Force refresh the virtual file to pick up new file type
virtualFile.refresh(false, false)

log.info("Auto-fix completed: file type registration updated")
true
} catch (e: ProcessCanceledException) {
throw e
} catch (e: Exception) {
log.warn("Auto-fix failed with exception: ${e.message}", e)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You split problem for user into two parts: issue the line into logs, and throwing the exception upon returning false with loss of failure context. Wouldn't it be better to throw outer IllegalStateException with actual cause?

false
}
}

fun getProjectViewConfigurationHash(): HashCode = ProjectViewHasher.hash(getProjectView())

companion object {
Expand Down