Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/#277 create missing env file #445

Merged
merged 17 commits into from
Jan 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
16 changes: 16 additions & 0 deletions src/main/kotlin/com/jetbrains/snakecharm/SmkNotifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@ object SmkNotifier {
}).notify(module.project)
}

fun notifyImpossibleToCreateFileOrDirectory(name: String, project: Project){
NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP_ID).createNotification(
title = SnakemakeBundle.message("notifier.msg.create.env.file.title"),
content = SnakemakeBundle.message("notifier.msg.create.env.file.io.exception", name),
type = NotificationType.ERROR
).notify(project)
}

fun notifyTargetFileIsInvalid(name: String, project: Project) {
NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP_ID).createNotification(
title = SnakemakeBundle.message("notifier.msg.create.env.file.title"),
content = SnakemakeBundle.message("notifier.msg.create.env.file.invalid.file.exception", name),
type = NotificationType.ERROR
).notify(project)
}

fun notify(content: String, type: NotificationType = NotificationType.INFORMATION, project: Project? = null) =
NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP_ID)
.createNotification(content, type).also {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.jetbrains.snakecharm.inspections

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.psi.*
import com.jetbrains.python.inspections.PyUnresolvedReferenceQuickFixProvider
import com.jetbrains.python.psi.PyStringLiteralExpression
import com.jetbrains.snakecharm.inspections.quickfix.CreateEnvFile
import com.jetbrains.snakecharm.lang.SnakemakeNames
import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection

class SmkUnresolvedReferenceInspectionExtension : PyUnresolvedReferenceQuickFixProvider {
override fun registerQuickFixes(reference: PsiReference, existing: MutableList<LocalQuickFix>) {
val sec = reference.element as? SmkRuleOrCheckpointArgsSection ?: return
if (sec.sectionKeyword == SnakemakeNames.SECTION_CONDA) {
handleCondaSection(sec, existing)
}
}

private fun handleCondaSection(st: SmkRuleOrCheckpointArgsSection, existing: MutableList<LocalQuickFix>) {
val arg = st.argumentList?.arguments
?.firstOrNull { it is PyStringLiteralExpression }
as? PyStringLiteralExpression ?: return
val name = arg.text.replace("\"", "")
existing.add(CreateEnvFile(st, name))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.jetbrains.snakecharm.inspections.quickfix

import com.intellij.codeInspection.LocalQuickFixOnPsiElement
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.InvalidVirtualFileAccessException
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.jetbrains.snakecharm.SmkNotifier
import com.jetbrains.snakecharm.SnakemakeBundle
import java.io.IOException

class CreateEnvFile(expr: PsiElement, private val fileName: String) : LocalQuickFixOnPsiElement(expr) {
private val defaultContext = """
channels:
dependencies:
""".trimIndent()

override fun getFamilyName() = SnakemakeBundle.message("INSP.NAME.conda.env.missing.fix", fileName)

override fun getText() = familyName

override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
val tokens = fileName.split('/')
val targetName = tokens.lastOrNull() ?: return
var currentFile = file.virtualFile.parent
try {
tokens.forEach {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is reasonable to expect, that in such complicated application, like IntelliJ platform the task of creating file by relative path + create intermediate dirs isn't a rare case and already solved via some API. The code below is bicycle invention in a rather complected way. E.g. java.io already have methods for creating files and folders. So here we could use smth like:

        val targetFile = file.virtualFile.parent.findFileByRelativePath(fileName)
        // if targetFile == null ....
        val targetFileFolder = targetFile.parent
        VfsUtil.createDirectoryIfMissing(targetFileFolder.path)
        LocalFileSystem.getInstance().createChildFile(this, targetFileFolder, targetFile.name)

or just:

        val targetFile = Paths.get(file.virtualFile.parent.path, fileName)
        Files.createDirectories(targetFile.parent)
        Files.createFile(targetFile)

if (it == targetName) {
val resultVirtualFile = currentFile.createChildData(this, targetName)
val resultPsiFile = PsiManager.getInstance(project).findFile(resultVirtualFile) ?: return
val doc = PsiDocumentManager.getInstance(project).getDocument(resultPsiFile) ?: return
doc.insertString(0, defaultContext)
return
}
currentFile = if (it == "..") {
currentFile.parent ?: return
} else {
currentFile.findChild(it) ?: currentFile.createChildDirectory(this, it)
}
}
} catch (e: InvalidVirtualFileAccessException) {
SmkNotifier.notifyTargetFileIsInvalid(currentFile.name, project)
} catch (e: IOException) {
SmkNotifier.notifyImpossibleToCreateFileOrDirectory(currentFile.name, project)
}
}

}
2 changes: 2 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -633,5 +633,7 @@
/>
<inspectionExtension
implementation="com.jetbrains.snakecharm.inspections.SmkIgnorePyInspectionExtension"/>
<unresolvedReferenceQuickFixProvider
implementation="com.jetbrains.snakecharm.inspections.SmkUnresolvedReferenceInspectionExtension"/>
</extensions>
</idea-plugin>
6 changes: 6 additions & 0 deletions src/main/resources/SnakemakeBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ INSP.NAME.wrapper.args.missed.message=Argument ''{0}'' missed in ''{1}''
INSP.NAME.wrapper.args.section.missed.message=Section ''{0}'' is missed
INSP.NAME.wrapper.args.section.with.args.missed.message=Section ''{0}'' with args ''{1}'' is missed

# SmkUnresolvedReferenceInspectionExtension
INSP.NAME.conda.env.missing.fix=Create ''{0}''

# SmkSubworkflowRedeclarationInspection
INSP.NAME.subworkflow.redeclaration=Only last subworkflow with the same name will be executed

Expand Down Expand Up @@ -253,6 +256,9 @@ notifier.group.title=SnakeCharm plugin notifications
notifier.msg.framework.by.snakefile.title=Snakemake framework detected
notifier.msg.framework.by.snakefile.action.configure=Configure Framework...
notifier.msg.framework.by.snakefile=Snakefile was found in ''{0}''.
notifier.msg.create.env.file.title=Impossible to create .yaml/.yml file
notifier.msg.create.env.file.io.exception=Impossible to create ''{0}''. Check permissions and the target path correctness.
iromeo marked this conversation as resolved.
Show resolved Hide resolved
notifier.msg.create.env.file.invalid.file.exception=One of path parts with name ''{0}'' was changed during its handling. Please try again later.

#######################
# Facet
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Feature: Inspection: SmkUnresolvedReferenceInspectionExtension
Checks, that extension + inspection work

Scenario Outline: Unresolved conda file
Given a snakemake project
Given I open a file "foo.smk" with text
"""
rule NAME:
conda:
"<path>"
"""
And PyUnresolvedReferencesInspection inspection is enabled
Then I expect inspection error on <<path>> with message
"""
Unresolved reference '<path>'
"""
When I check highlighting warnings
And I invoke quick fix Create '<path>' and see text:
"""
rule NAME:
conda:
"<path>"
"""
Then the file "<path>" should have text
"""
channels:
dependencies:
"""
Examples:
| path |
| NAME.yaml |
| envs/NAME.yaml |
| ../envs/NAME.yaml |