Skip to content

Commit

Permalink
Merge branch 'master' into features/#452-unused-log-file
Browse files Browse the repository at this point in the history
  • Loading branch information
dakochik authored Jan 22, 2022
2 parents e3714ac + 9e8abc6 commit c677071
Show file tree
Hide file tree
Showing 26 changed files with 600 additions and 74 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ Released on ...

### Fixed
- Quick fix for unused log file inspection (see [#452](https://github.com/JetBrains-Research/snakecharm/issues/452))
- Resolve for rule names in `use rule` section (see [#455](https://github.com/JetBrains-Research/snakecharm/issues/455))
- Multiple args inspection in `workdir` case (see [#140](https://github.com/JetBrains-Research/snakecharm/issues/140))
- `localrules` and `ruleorder` now take into account `use rule` (see [#448](https://github.com/JetBrains-Research/snakecharm/issues/448))
- Keyword arguments highlighting (see [#454](https://github.com/JetBrains-Research/snakecharm/issues/454))
- Resolve for `rules` keyword if `snakemake` version less than `6.1` (see [#359](https://github.com/JetBrains-Research/snakecharm/issues/359))
- TODO (see [#NNN](https://github.com/JetBrains-Research/snakecharm/issues/NNN))

### Added
- Inspection: warns that that all docstrings except the first one will be ignored (see [#341](https://github.com/JetBrains-Research/snakecharm/issues/341))
- Quick fix for unresolved files (conda, configfile. etc.) (see [#277](https://github.com/JetBrains-Research/snakecharm/issues/277))
- Warning: Snakemake support is disabled (see [#109](https://github.com/JetBrains-Research/snakecharm/issues/109))
- TODO (see [#NNN](https://github.com/JetBrains-Research/snakecharm/issues/NNN))

Expand Down
11 changes: 7 additions & 4 deletions src/main/kotlin/com/jetbrains/snakecharm/SmkNotifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@ object SmkNotifier {
}).notify(module.project)
}

fun notify(content: String, type: NotificationType = NotificationType.INFORMATION, project: Project? = null) =
fun notify(
title: String = "",
content: String,
type: NotificationType = NotificationType.INFORMATION,
project: Project? = null
) =
NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_GROUP_ID)
.createNotification(content, type).also {
it.notify(project)
}
.createNotification(title, content, type).notify(project)
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ object SnakemakeAPI {
*/
val SINGLE_ARGUMENT_WORKFLOWS_KEYWORDS = setOf(
WORKFLOW_CONTAINERIZED_KEYWORD, WORKFLOW_CONTAINER_KEYWORD,
WORKFLOW_SINGULARITY_KEYWORD
WORKFLOW_SINGULARITY_KEYWORD, WORKFLOW_WORKDIR_KEYWORD
)

// List of top-level sections
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.jetbrains.snakecharm.inspections

import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemsHolder
import com.jetbrains.snakecharm.SnakemakeBundle
import com.jetbrains.snakecharm.lang.psi.*

class SmkDocstringsWillBeIgnoredInspection : SnakemakeInspection() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession
) = object : SnakemakeInspectionVisitor(holder, getContext(session)) {

override fun visitSmkRule(rule: SmkRule) {
visitSMKRuleLike(rule)
}

override fun visitSmkCheckPoint(checkPoint: SmkCheckPoint) {
visitSMKRuleLike(checkPoint)
}

fun visitSMKRuleLike(rule: SmkRuleLike<SmkArgsSection>) {
val docstrings = rule.getStringLiteralExpressions().drop(1)
if (docstrings.isEmpty()) {
return
}
for (docstring in docstrings) {
registerProblem(docstring, SnakemakeBundle.message("INSP.NAME.docstrings.will.be.ignored"))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ class SmkLocalrulesRuleorderConfusingReference : SnakemakeInspection() {
?.filterIsInstance(SmkReferenceExpression::class.java)

if (args != null) {
val rules = file.collectRules().map { it.first }
val checkPoints = file.collectCheckPoints().map { it.first }
val ruleLike = rules.plus(checkPoints).toSet()
val ruleLike = file.advancedCollectRules(mutableSetOf()).map { it.first }.toSet()
args.forEach { expr ->
val name = expr.name
if (name != null && name !in ruleLike) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.jetbrains.snakecharm.inspections

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.psi.PsiReference
import com.jetbrains.python.inspections.PyUnresolvedReferenceQuickFixProvider
import com.jetbrains.snakecharm.inspections.quickfix.CreateMissedFileQuickFix
import com.jetbrains.snakecharm.inspections.quickfix.CreateMissedFileUndoableAction
import com.jetbrains.snakecharm.lang.psi.SmkArgsSection
import com.jetbrains.snakecharm.lang.psi.SmkFileReference

class SmkUnresolvedReferenceInspectionExtension : PyUnresolvedReferenceQuickFixProvider {

override fun registerQuickFixes(reference: PsiReference, existing: MutableList<LocalQuickFix>) {
val section = reference.element as? SmkArgsSection ?: return
val sectionName = section.sectionKeyword ?: return
if (CreateMissedFileUndoableAction.sectionToDefaultFileContent.containsKey(sectionName)) {
val fileReference = (section.reference as? SmkFileReference) ?: return
if (!fileReference.hasAppropriateSuffix()) {
return
}
val name = fileReference.path
existing.add(CreateMissedFileQuickFix(section,
name,
sectionName,
fileReference.searchRelativelyToCurrentFolder))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.jetbrains.snakecharm.inspections.quickfix

import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
import com.intellij.notification.NotificationType
import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.jetbrains.snakecharm.SmkNotifier
import com.jetbrains.snakecharm.SnakemakeBundle
import java.nio.file.InvalidPathException
import java.nio.file.Paths
import kotlin.io.path.Path

class CreateMissedFileQuickFix(
element: PsiElement,
private val fileName: String,
private val sectionName: String,
private val searchRelativelyToCurrentFolder: Boolean,
) : LocalQuickFixAndIntentionActionOnPsiElement(element) {
companion object {
private val LOGGER = Logger.getInstance(CreateMissedFileQuickFix::class.java)
}

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

override fun getText() = familyName

override fun invoke(
project: Project,
file: PsiFile,
editor: Editor?,
startElement: PsiElement,
endElement: PsiElement,
) {
val vFile = file.virtualFile ?: return

val fileIndex = ProjectRootManager.getInstance(project).fileIndex
val relativeDirectory = when {
searchRelativelyToCurrentFolder -> vFile.parent
else -> fileIndex.getContentRootForFile(vFile)
} ?: return

val fileToCreatePath = try {
when {
Path(fileName).isAbsolute -> Path(fileName)
else -> Paths.get(relativeDirectory.path, fileName)
}
} catch (e: InvalidPathException) {
LOGGER.error(e)
SmkNotifier.notify(
title = SnakemakeBundle.message("notifier.msg.create.env.file.title"),
content = SnakemakeBundle.message("notifier.msg.create.env.file.name.invalid.file.exception", fileName),
type = NotificationType.ERROR,
project = project
)
return
}
val action = CreateMissedFileUndoableAction(file, fileToCreatePath, sectionName)
action.redo()
UndoManager.getInstance(project).undoableActionPerformed(action)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package com.jetbrains.snakecharm.inspections.quickfix

import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.notification.NotificationType
import com.intellij.openapi.command.undo.DocumentReference
import com.intellij.openapi.command.undo.DocumentReferenceManager
import com.intellij.openapi.command.undo.UndoableAction
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
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.jetbrains.snakecharm.SmkNotifier
import com.jetbrains.snakecharm.SnakemakeBundle
import com.jetbrains.snakecharm.lang.SnakemakeNames
import org.apache.commons.io.FileUtils
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.appendText
import kotlin.io.path.isDirectory
import kotlin.io.path.name
import kotlin.io.path.notExists

class CreateMissedFileUndoableAction(
private val actionInvocationTarget: PsiFile,
private val fileToCreatePath: Path,
private val sectionName: String,
) : UndoableAction {

companion object {
private val LOGGER = Logger.getInstance(CreateMissedFileUndoableAction::class.java)
private val condaDefaultContent = """
channels:
dependencies:
""".trimIndent()

// Update it if wee need default text
val sectionToDefaultFileContent = mapOf(
SnakemakeNames.SECTION_CONDA to condaDefaultContent,
SnakemakeNames.SECTION_NOTEBOOK to null,
SnakemakeNames.SECTION_SCRIPT to null,
SnakemakeNames.MODULE_SNAKEFILE_KEYWORD to null,

SnakemakeNames.WORKFLOW_CONFIGFILE_KEYWORD to null,
SnakemakeNames.WORKFLOW_PEPFILE_KEYWORD to null,
SnakemakeNames.WORKFLOW_PEPSCHEMA_KEYWORD to null
)
}

private val actionInvocationTargetVFile = actionInvocationTarget.virtualFile!!
private val project = actionInvocationTarget.project
private val firstCreatedFileOrDir: Path
private val firstCreatedFileOrDirParent: VirtualFile?

init {
// Memorize first directory that will be created & delete it on "undo" step
var fileOrDirToCreate = fileToCreatePath
while (fileOrDirToCreate.parent.notExists()) {
fileOrDirToCreate = fileOrDirToCreate.parent ?: break
}
firstCreatedFileOrDir = fileOrDirToCreate

val parent = fileOrDirToCreate.parent
firstCreatedFileOrDirParent = when (parent) {
null -> null
else -> VirtualFileManager.getInstance().findFileByNioPath(parent)
}
}

override fun undo() {
// invoke in such way because:
// (1) changing document inside undo/redo is not allowed (see ChangeFileEncodingAction)
// (2) we don't want to use UI thread, e.g. if disk IO operation will be slow due to NFS, or etc.
ProgressManager.getInstance().run(object : Task.Backgroundable(
project,
SnakemakeBundle.message("notifier.msg.deleting.env.file", fileToCreatePath.name),
false
) {
override fun run(indicator: ProgressIndicator) {
doUndo()
// XXX: Sometimes VFS refresh in onSuccess() called to early and doesn't see FS changes
Thread.sleep(1000)
}

override fun onSuccess() {
// Here is AWT thread!
refreshFS()
}
})
}

override fun redo() {
// invoke in such way because:
// (1) changing document inside undo/redo is not allowed (see ChangeFileEncodingAction)
// (2) we don't want to use UI thread, e.g. if disk IO operation will be slow due to NFS, or etc.
ProgressManager.getInstance().run(object : Task.Backgroundable(
project,
SnakemakeBundle.message("notifier.msg.creating.env.file", fileToCreatePath.name),
false
) {
override fun run(indicator: ProgressIndicator) {
doRedo(fileToCreatePath, project)
// XXX: Sometimes VFS refresh in onSuccess() called to early and doesn't see FS changes
Thread.sleep(1000)
}

override fun onSuccess() {
// Here is AWT thread!
refreshFS()
}
})
}

private fun refreshFS() {
firstCreatedFileOrDirParent?.refresh(true, true) {
// Post action is called in AWT thread:
DaemonCodeAnalyzer.getInstance(project).restart(actionInvocationTarget)
}
}

override fun getAffectedDocuments(): Array<DocumentReference> {
// * If not affected files - action not in undo/redo
// * If 'all' affected files - action undo doesn't work

// So:mark only the current editor file as affected to make feature convenient.
// Otherwise, new file opening will be regarded as a new action
// and 'undone' will be banned
return arrayOf(DocumentReferenceManager.getInstance().create(actionInvocationTargetVFile))
}

override fun isGlobal() = true

private fun doRedo(
fileToCreate: Path,
project: Project,
) {
try {
// file could be created in background by someone else or if action triggred twice
if (fileToCreate.notExists()) {
Files.createDirectories(fileToCreate.parent)
val targetFile = Files.createFile(fileToCreate)

val context = sectionToDefaultFileContent[sectionName]
if (context != null) {
// We don't use the result of 'createChildFile()' because it has inappropriate
// type (and throws UnsupportedOperationException)
targetFile.appendText(context)
}
}
} catch (e: SecurityException) {
val message = e.message ?: "Error: ${e.javaClass.name}"
SmkNotifier.notify(
title = SnakemakeBundle.message("notifier.msg.create.env.file.title"),
content = message,
type = NotificationType.ERROR,
project = project
)
} catch (e: IOException) {
LOGGER.warn(e)
SmkNotifier.notify(
title = SnakemakeBundle.message("notifier.msg.create.env.file.title"),
content = SnakemakeBundle.message(
"notifier.msg.create.env.file.io.exception", fileToCreate.name
),
type = NotificationType.ERROR,
project = project
)
}
}

private fun doUndo() {
if (firstCreatedFileOrDir.notExists()) {
return
}

when {
firstCreatedFileOrDir.isDirectory() -> FileUtils.deleteDirectory(
firstCreatedFileOrDir.toFile()
)
else -> Files.delete(firstCreatedFileOrDir)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,5 @@ object SmkSyntaxAnnotator : SmkAnnotator() {
st.getSectionKeywordNode()?.let {
addHighlightingAnnotation(it, SnakemakeSyntaxHighlighterFactory.SMK_DECORATOR)
}
if (st is SmkArgsSection) {
st.keywordArguments?.forEach {
it.keywordNode?.psi?.let { name ->
addHighlightingAnnotation(
name,
SnakemakeSyntaxHighlighterFactory.SMK_KEYWORD_ARGUMENT
)
}
}
}
}
}
Loading

0 comments on commit c677071

Please sign in to comment.