Skip to content

Commit 440b87b

Browse files
authored
fix: show interpreter action only on virtualenvs (#221)
1 parent 72084e8 commit 440b87b

2 files changed

Lines changed: 32 additions & 41 deletions

File tree

src/main/kotlin/com/github/pyvenvmanage/actions/ConfigurePythonActionAbstract.kt

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
66
import com.intellij.openapi.actionSystem.AnAction
77
import com.intellij.openapi.actionSystem.AnActionEvent
88
import com.intellij.openapi.actionSystem.CommonDataKeys
9-
import com.intellij.openapi.diagnostic.Logger
109
import com.intellij.openapi.project.Project
1110
import com.intellij.openapi.projectRoots.ProjectJdkTable
1211
import com.intellij.openapi.projectRoots.Sdk
@@ -16,44 +15,30 @@ import com.jetbrains.python.sdk.PythonSdkUtil
1615
import com.jetbrains.python.statistics.executionType
1716
import com.jetbrains.python.statistics.interpreterType
1817

18+
import com.github.pyvenvmanage.VenvUtils
1919
import com.github.pyvenvmanage.sdk.EnvironmentDetector
2020
import com.github.pyvenvmanage.sdk.PythonEnvironmentType
2121
import com.github.pyvenvmanage.sdk.SdkFactory
2222

2323
abstract class ConfigurePythonActionAbstract : AnAction() {
24-
companion object {
25-
private val LOG = Logger.getInstance(ConfigurePythonActionAbstract::class.java)
26-
}
27-
2824
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
2925

3026
override fun update(e: AnActionEvent) {
31-
val selectedPath = e.getData(CommonDataKeys.VIRTUAL_FILE)
32-
LOG.info("Update called for path: $selectedPath")
33-
val isValid =
34-
selectedPath?.let { path ->
35-
val dir = if (path.isDirectory) path else path.parent
36-
LOG.info("Checking directory: ${dir.path}")
37-
PythonSdkUtil.getPythonExecutable(dir.path)?.let { pythonExe ->
38-
LOG.info("Found python executable: $pythonExe")
39-
val envType = EnvironmentDetector.detectEnvironmentType(pythonExe)
40-
val icon = SdkFactory.getIconForEnvironmentType(envType)
41-
LOG.info("Setting icon for type $envType: $icon")
42-
e.presentation.icon = icon
43-
true
44-
} ?: false.also { LOG.info("No python executable found") }
45-
} ?: false.also { LOG.info("No selected path") }
46-
47-
e.presentation.isEnabledAndVisible = isValid
48-
LOG.info("Action visible: $isValid")
27+
// Offer the action only on directories that are actual virtual environments, using the same
28+
// pyvenv.cfg check as VenvProjectViewNodeDecorator. getPythonExecutable alone also matches
29+
// non-venv directories (e.g. a project root with a configured interpreter), which surfaced the
30+
// action on ordinary files via their parent directory.
31+
val venvDir = e.getData(CommonDataKeys.VIRTUAL_FILE)?.takeIf { VenvUtils.getPyVenvCfg(it) != null }
32+
venvDir?.let { PythonSdkUtil.getPythonExecutable(it.path) }?.let { pythonExecutable ->
33+
val envType = EnvironmentDetector.detectEnvironmentType(pythonExecutable)
34+
e.presentation.icon = SdkFactory.getIconForEnvironmentType(envType)
35+
}
36+
e.presentation.isEnabledAndVisible = venvDir != null
4937
}
5038

5139
override fun actionPerformed(e: AnActionEvent) {
5240
val project = e.project ?: return
53-
val selectedPath =
54-
e.getData(CommonDataKeys.VIRTUAL_FILE)?.let {
55-
if (it.isDirectory) it else it.parent
56-
} ?: return
41+
val selectedPath = e.getData(CommonDataKeys.VIRTUAL_FILE)?.takeIf { it.isDirectory } ?: return
5742

5843
val pythonExecutable = PythonSdkUtil.getPythonExecutable(selectedPath.path)
5944
if (pythonExecutable == null) {

src/test/kotlin/com/github/pyvenvmanage/actions/ConfigurePythonActionAbstractTest.kt

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.intellij.python.venv.icons.PythonVenvIcons
3535
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
3636
import com.jetbrains.python.sdk.PythonSdkUtil
3737

38+
import com.github.pyvenvmanage.VenvUtils
3839
import com.github.pyvenvmanage.sdk.EnvironmentDetector
3940
import com.github.pyvenvmanage.sdk.PythonEnvironmentType
4041
import com.github.pyvenvmanage.sdk.SdkFactory
@@ -68,6 +69,7 @@ class ConfigurePythonActionAbstractTest {
6869
@BeforeEach
6970
fun setUpMocks() {
7071
mockkStatic(PythonSdkUtil::class)
72+
mockkObject(VenvUtils)
7173
mockkObject(EnvironmentDetector)
7274
mockkObject(SdkFactory)
7375
}
@@ -87,10 +89,10 @@ class ConfigurePythonActionAbstractTest {
8789
}
8890

8991
@Test
90-
fun `enables action for directory with Python executable`() {
92+
fun `enables action for virtualenv directory`() {
9193
every { event.getData(CommonDataKeys.VIRTUAL_FILE) } returns virtualFile
92-
every { virtualFile.isDirectory } returns true
9394
every { virtualFile.path } returns "/some/venv"
95+
every { VenvUtils.getPyVenvCfg(virtualFile) } returns Path.of("/some/venv/pyvenv.cfg")
9496
every { PythonSdkUtil.getPythonExecutable("/some/venv") } returns "/some/venv/bin/python"
9597
every { EnvironmentDetector.detectEnvironmentType("/some/venv/bin/python") } returns
9698
PythonEnvironmentType.UV
@@ -103,22 +105,31 @@ class ConfigurePythonActionAbstractTest {
103105
}
104106

105107
@Test
106-
fun `disables action for directory without Python executable`() {
108+
fun `disables action for non-venv directory`() {
107109
every { event.getData(CommonDataKeys.VIRTUAL_FILE) } returns virtualFile
108-
every { virtualFile.isDirectory } returns true
109-
every { virtualFile.path } returns "/some/dir"
110-
every { PythonSdkUtil.getPythonExecutable("/some/dir") } returns null
110+
every { VenvUtils.getPyVenvCfg(virtualFile) } returns null
111111

112112
action.update(event)
113113

114114
verify { presentation.isEnabledAndVisible = false }
115115
}
116116

117+
@Test
118+
fun `disables action for non-directory selection`() {
119+
every { event.getData(CommonDataKeys.VIRTUAL_FILE) } returns virtualFile
120+
every { VenvUtils.getPyVenvCfg(virtualFile) } returns null
121+
122+
action.update(event)
123+
124+
verify { presentation.isEnabledAndVisible = false }
125+
verify(exactly = 0) { PythonSdkUtil.getPythonExecutable(any()) }
126+
}
127+
117128
@Test
118129
fun `sets icon based on detected environment type`() {
119130
every { event.getData(CommonDataKeys.VIRTUAL_FILE) } returns virtualFile
120-
every { virtualFile.isDirectory } returns true
121131
every { virtualFile.path } returns "/some/venv"
132+
every { VenvUtils.getPyVenvCfg(virtualFile) } returns Path.of("/some/venv/pyvenv.cfg")
122133
every { PythonSdkUtil.getPythonExecutable("/some/venv") } returns "/some/venv/bin/python"
123134
every { EnvironmentDetector.detectEnvironmentType("/some/venv/bin/python") } returns
124135
PythonEnvironmentType.UV
@@ -133,7 +144,6 @@ class ConfigurePythonActionAbstractTest {
133144

134145
@Nested
135146
inner class ActionPerformedTest {
136-
private lateinit var parentDir: VirtualFile
137147
private lateinit var interpreterList: PyConfigurableInterpreterList
138148
private lateinit var notificationGroupManager: NotificationGroupManager
139149
private lateinit var notificationGroup: NotificationGroup
@@ -142,7 +152,6 @@ class ConfigurePythonActionAbstractTest {
142152

143153
@BeforeEach
144154
fun setUpMocks() {
145-
parentDir = mockk(relaxed = true)
146155
interpreterList = mockk(relaxed = true)
147156
notificationGroupManager = mockk(relaxed = true)
148157
notificationGroup = mockk(relaxed = true)
@@ -191,16 +200,13 @@ class ConfigurePythonActionAbstractTest {
191200
}
192201

193202
@Test
194-
fun `uses parent directory when file is not a directory`() {
203+
fun `ignores non-directory selection`() {
195204
every { event.getData(CommonDataKeys.VIRTUAL_FILE) } returns virtualFile
196205
every { virtualFile.isDirectory } returns false
197-
every { virtualFile.parent } returns parentDir
198-
every { parentDir.path } returns "/some/venv"
199-
every { PythonSdkUtil.getPythonExecutable("/some/venv") } returns null
200206

201207
action.actionPerformed(event)
202208

203-
verify { PythonSdkUtil.getPythonExecutable("/some/venv") }
209+
verify(exactly = 0) { PythonSdkUtil.getPythonExecutable(any()) }
204210
}
205211

206212
@Test

0 commit comments

Comments
 (0)