intellij-plugin/features/code-insight-yaml/src/com/jetbrains/edu/yaml/EduYamlSchemaProviderFactory.kt (115 lines of code) (raw):

package com.jetbrains.edu.yaml import com.intellij.openapi.application.runReadAction import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiManager import com.jetbrains.edu.coursecreator.CCUtils import com.jetbrains.edu.learning.EduNames import com.jetbrains.edu.learning.courseFormat.EduFormatNames.FRAMEWORK import com.jetbrains.edu.learning.courseFormat.tasks.choice.ChoiceTask import com.jetbrains.edu.learning.getContainingTask import com.jetbrains.edu.learning.yaml.YamlConfigSettings import com.jetbrains.edu.learning.yaml.format.YamlMixinNames.LESSON import com.jetbrains.edu.learning.yaml.format.YamlMixinNames.SECTION import com.jetbrains.edu.learning.yaml.format.YamlMixinNames.TASK import com.jetbrains.edu.yaml.messages.EduYAMLBundle import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory import com.jetbrains.jsonSchema.extension.SchemaType import com.jetbrains.jsonSchema.impl.JsonSchemaVersion import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.yaml.psi.YAMLFile import org.jetbrains.yaml.psi.YAMLMapping class EduYamlSchemaProviderFactory : JsonSchemaProviderFactory { override fun getProviders(project: Project): List<StudyItemConfigSchemaProvider> { return listOf( CourseConfigSchemaProvider(project), SectionConfigSchemaProvider(project), LessonConfigSchemaProvider(project), FrameworkLessonConfigSchemaProvider(project), TaskGeneralConfigSchemaProvider(project), *getTaskSpecificProviderNames().keys .map { TaskSpecificConfigSchemaProvider(project, it) } .toTypedArray() ) } abstract class StudyItemConfigSchemaProvider(protected val project: Project) : JsonSchemaFileProvider { abstract val itemKind: String override fun isAvailable(file: VirtualFile): Boolean = CCUtils.isCourseCreator(project) && YamlConfigSettings.getLocalConfigFileName(itemKind) == file.name override fun getSchemaFile(): VirtualFile? { return JsonSchemaProviderFactory.getResourceFile(EduYamlSchemaProviderFactory::class.java, getSchemaResourcePath()) } override fun getSchemaType(): SchemaType = SchemaType.embeddedSchema override fun getSchemaVersion(): JsonSchemaVersion = JsonSchemaVersion.SCHEMA_7 @VisibleForTesting open fun getSchemaResourcePath(): String = "/yaml/${itemKind}-schema.json" } class CourseConfigSchemaProvider(project: Project) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = EduNames.COURSE override fun getName(): String { return EduYAMLBundle.message("yaml.schema.provider.for.course.name") } } class SectionConfigSchemaProvider(project: Project) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = SECTION override fun getName(): String { return EduYAMLBundle.message("yaml.schema.provider.for.section.name") } } class TaskSpecificConfigSchemaProvider(project: Project, private val taskType: String) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = TASK override fun getName(): String { return getTaskSpecificProviderNames()[taskType] ?: error("Task specific provider wasn't found for $taskType") } override fun isAvailable(file: VirtualFile): Boolean { val task = file.getContainingTask(project) ?: return false return super.isAvailable(file) && task.itemType == taskType } @VisibleForTesting override fun getSchemaResourcePath(): String = "/yaml/${taskType}-${itemKind}-schema.json" } class TaskGeneralConfigSchemaProvider(project: Project) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = TASK override fun isAvailable(file: VirtualFile): Boolean { // We need to exclude task types with specific Config Schema Provider // to make providers mapped one to one for every yaml file. val task = file.getContainingTask(project) ?: return false return super.isAvailable(file) && task.itemType !in getTaskSpecificProviderNames() } override fun getName(): String { return EduYAMLBundle.message("yaml.schema.provider.for.task.name") } } class FrameworkLessonConfigSchemaProvider(project: Project) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = LESSON override fun getName(): String = EduYAMLBundle.message("yaml.schema.provider.for.framework.lesson.name") override fun isAvailable(file: VirtualFile): Boolean { if (!super.isAvailable(file)) return false return file.isFrameworkLessonConfig(project) } override fun getSchemaResourcePath(): String = "/yaml/framework-lesson-schema.json" } class LessonConfigSchemaProvider(project: Project) : StudyItemConfigSchemaProvider(project) { override val itemKind: String = LESSON override fun isAvailable(file: VirtualFile): Boolean { if (!super.isAvailable(file)) return false return !file.isFrameworkLessonConfig(project) } override fun getName(): String { return EduYAMLBundle.message("yaml.schema.provider.for.lesson.name") } } companion object { private fun getTaskSpecificProviderNames(): Map<String, String> = mapOf( ChoiceTask.CHOICE_TASK_TYPE to EduYAMLBundle.message("yaml.schema.provider.for.choice.task.name") ) } } private fun VirtualFile.isFrameworkLessonConfig(project: Project): Boolean { return runReadAction { val psiFile = PsiManager.getInstance(project).findFile(this) as? YAMLFile ?: return@runReadAction false val mapping = psiFile.documents.firstOrNull()?.topLevelValue as? YAMLMapping ?: return@runReadAction false val typeKeyValue = mapping.getKeyValueByKey("type") ?: return@runReadAction false typeKeyValue.valueText == FRAMEWORK } }