diff --git a/CHANGELOG.md b/CHANGELOG.md index b9bc2c46..91b16c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Released on ### Fixed - Improve parser error message when rule/module is declared with name but lacks ':' (see [#515](https://github.com/JetBrains-Research/snakecharm/issues/515)) - Support for `update` and `before_update` flags. Update inspection that warns if flag functions from `snakemake.io` is used in a wrong section, added info for all flags up to 8.23.1 version (see [#537](https://github.com/JetBrains-Research/snakecharm/issues/537)) +- Inject some modules in Snakefile file resolve scope w/o import declaration, e.g. os, sys,.. (see [#553](https://github.com/JetBrains-Research/snakecharm/issues/553) ### Changed - TODO diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SmkImplicitPySymbolsProvider.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SmkImplicitPySymbolsProvider.kt index 8b95b411..5906a4c0 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SmkImplicitPySymbolsProvider.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SmkImplicitPySymbolsProvider.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiElement import com.intellij.psi.impl.source.resolve.ResolveCache import com.intellij.psi.util.QualifiedName +import com.intellij.util.PlatformIcons import com.intellij.util.SlowOperations import com.jetbrains.python.extensions.inherits import com.jetbrains.python.packaging.PyPackage @@ -122,6 +123,49 @@ class SmkImplicitPySymbolsProvider( val elementsCache = ArrayList() val syntheticElementsCache = ArrayList>() + /////////////////////////////////////// + // Implicit requires: e.g. 'os', 'sys' + val resolveContext = fromSdk(project, sdk) + listOf("os", "sys").forEach { moduleName -> + var module = resolveQualifiedName(QualifiedName.fromDottedString(moduleName), resolveContext).filterIsInstance().firstOrNull() + if (module == null) { + module = + resolveQualifiedName(QualifiedName.fromDottedString("${moduleName}.__init__"), resolveContext).filterIsInstance() + .firstOrNull() + } + if (module != null) { + usedFiles.add(module.virtualFile) + + syntheticElementsCache.add( + SmkCodeInsightScope.TOP_LEVEL to SmkCompletionUtil.createPrioritizedLookupElement( + moduleName, + module, + icon = PlatformIcons.VARIABLE_ICON, + typeText = moduleName, + priority = SmkCompletionUtil.WORKFLOW_GLOBALS_PRIORITY + ) + ) + } + } + // Add 'Path' from pathlib + listOf("pathlib.Path").forEach() { fqn -> + val qualifiedName = QualifiedName.fromDottedString(fqn) + val pyClass = resolveTopLevelMember(qualifiedName, resolveContext) as? PyClass + if (pyClass != null) { + usedFiles.add(pyClass.containingFile.virtualFile) + + syntheticElementsCache.add( + SmkCodeInsightScope.TOP_LEVEL to SmkCompletionUtil.createPrioritizedLookupElement( + qualifiedName.lastComponent.toString(), + pyClass, + icon = PlatformIcons.CLASS_ICON, + typeText = fqn, + priority = SmkCompletionUtil.WORKFLOW_GLOBALS_PRIORITY + ) + ) + } + } + /////////////////////////////////////// // E.g. rules, config, ... defined in Workflow code as global variables @@ -485,6 +529,7 @@ class SmkImplicitPySymbolsProvider( classFQN.forEach { fqn -> val pyElement = resolveTopLevelMember(QualifiedName.fromDottedString(fqn), resolveContext) if (pyElement is PyClass) { + usedFiles.add(pyElement.containingFile.virtualFile) processor(pyElement) } } diff --git a/src/test/kotlin/features/glue/CompletionResolveSteps.kt b/src/test/kotlin/features/glue/CompletionResolveSteps.kt index 39c720bb..5719fede 100644 --- a/src/test/kotlin/features/glue/CompletionResolveSteps.kt +++ b/src/test/kotlin/features/glue/CompletionResolveSteps.kt @@ -134,7 +134,7 @@ class CompletionResolveSteps { } } - @Then("^reference should resolve to \"(.+)\" in \"(.+)\"$") + @Then("^reference should resolve to \"(.+|[SKIP])\" in \"(.+)\"$") fun referenceShouldResolveToIn(targetPrefix: String, file: String) { DumbService.getInstance(SnakemakeWorld.fixture().project).waitForSmartMode() ApplicationManager.getApplication().runReadAction { @@ -180,19 +180,21 @@ class CompletionResolveSteps { ) assertEquals(file, actualSubString, msg) - assertTrue( - targetPrefix.length <= result.textLength, - "Expected result prefix <$targetPrefix> is longer than element <${result.text}>\n$msg" - ) + if (targetPrefix != "[SKIP]") { + assertTrue( + targetPrefix.length <= result.textLength, + "Expected result prefix <$targetPrefix> is longer than element <${result.text}>\n$msg" + ) - val elementText = TextRange.from(result.textOffset, targetPrefix.length).substring(result.containingFile.text) + val elementText = + TextRange.from(result.textOffset, targetPrefix.length).substring(result.containingFile.text) // val elementText = TextRange.from(0, targetPrefix.length).substring(result.text) - assertEquals(targetPrefix, elementText, msg) - - val text = - TextRange.from(injectionStartOffset + result.textOffset, context.length).substring(containingFile.text) - assertEquals(context, text, msg) + assertEquals(targetPrefix, elementText, msg) + val text = + TextRange.from(injectionStartOffset + result.textOffset, context.length).substring(containingFile.text) + assertEquals(context, text, msg) + } } @Then("^reference in injection should multi resolve to name, file in same order$") diff --git a/src/test/resources/features/completion/implicit_py_symbols_completion.feature b/src/test/resources/features/completion/implicit_py_symbols_completion.feature index be099f46..5c0d2d6e 100644 --- a/src/test/resources/features/completion/implicit_py_symbols_completion.feature +++ b/src/test/resources/features/completion/implicit_py_symbols_completion.feature @@ -35,6 +35,19 @@ Feature: Completion in python part of snakemake file | gitlab | | gitfile | + Scenario: Complete imported python modules/classes at top-level + Given a snakemake project + Given I open a file "foo.smk" with text + """ + foo = 1; + """ + When I put the caret after foo = 1; + And I invoke autocompletion popup + Then completion list should contain: + | os | + | sys | + | Path | + Scenario: Complete at top-level (GTE 6.1) Given a snakemake:6.1 project Given I open a file "foo.smk" with text diff --git a/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature b/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature index da213c8d..fc916abb 100644 --- a/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature +++ b/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature @@ -81,6 +81,21 @@ Feature: Resolve implicitly imported python names | snakemake | pe | pep | __init__ | project.py | | snakemake | pe | pep.config | __init__ | project.py | + Scenario Outline: Resolve implicit python modules/classes at top-level + Given a snakemake project + Given I open a file "foo.smk" with text + """ + + """ + When I put the caret at + Then reference should resolve to "" in "" + + Examples: + | ptn | text | symbol_name | file | + | os | os | [SKIP] | os/__init__.pyi | + | sy | sys | [SKIP] | sys.py | + | Pat | Path | Path | pathlib.pyi | + Scenario: Resolve at top-level: shell() Given a snakemake project Given I open a file "foo.smk" with text