Skip to content

Commit

Permalink
feat: Inject some modules in Snakefile file resolve scope w/o import …
Browse files Browse the repository at this point in the history
…declaration, e.g. os, sys,..

Closes #553
  • Loading branch information
iromeo committed Oct 22, 2024
1 parent af1ccb1 commit 5ab8462
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Released on <not released>
### 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -122,6 +123,49 @@ class SmkImplicitPySymbolsProvider(
val elementsCache = ArrayList<ImplicitPySymbol>()
val syntheticElementsCache = ArrayList<Pair<SmkCodeInsightScope, LookupElement>>()

///////////////////////////////////////
// Implicit requires: e.g. 'os', 'sys'
val resolveContext = fromSdk(project, sdk)
listOf("os", "sys").forEach { moduleName ->
var module = resolveQualifiedName(QualifiedName.fromDottedString(moduleName), resolveContext).filterIsInstance<PyFile>().firstOrNull()
if (module == null) {
module =
resolveQualifiedName(QualifiedName.fromDottedString("${moduleName}.__init__"), resolveContext).filterIsInstance<PyFile>()
.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

Expand Down Expand Up @@ -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)
}
}
Expand Down
24 changes: 13 additions & 11 deletions src/test/kotlin/features/glue/CompletionResolveSteps.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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$")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
<text>
"""
When I put the caret at <ptn>
Then reference should resolve to "<symbol_name>" in "<file>"

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
Expand Down

0 comments on commit 5ab8462

Please sign in to comment.