Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .github/workflows/check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ide: ${{ github.event_name == 'pull_request' && fromJson('["PC"]') || fromJson('["PC", "PY"]') }}
ide: ${{ github.event_name == 'pull_request' && fromJson('["PY"]') || fromJson('["PY"]') }}
steps:
- name: 🧹 Free disk space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # main
Expand Down
80 changes: 55 additions & 25 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

## [Unreleased]

### Added

- Environment type detection for UV, Conda, Poetry, Hatch, and Pipenv virtual environments
- Dynamic icons in tree view and context menu based on detected environment type
- Proper SDK flavor data for each environment type (UV, Poetry, Hatch, etc.)
- Project association for in-project virtual environments
- Support for configurable environment paths via environment variables (HATCH_DATA_DIR, WORKON_HOME, etc.)
- Comprehensive logging for environment detection debugging

### Changed

- Updated to PyCharm 2026.1 platform API
- Minimum supported version is now PyCharm 2026.1 (build 261)
- Removed module-level interpreter action (kept project-level only)
- SDK creation now uses proper SdkModificator API with write actions
- Environment detection checks pyvenv.cfg for UV marker, .gitignore for Hatch marker, and standard cache locations

### Fixed

- Icon loading errors on PyCharm 2026.1 by removing hardcoded icon references
- SDK duplicate registration errors by checking global SDK table before creating new SDKs
- Threading assertions by properly wrapping SDK modifications in write actions

## [2.2.7] - 2026-03-31

${GITHUB_EVENT_RELEASE_BODY}
Expand All @@ -16,8 +39,10 @@ ${GITHUB_EVENT_RELEASE_BODY}
- Standardize .github files to .yaml suffix by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/142
- Clarify the venv selection painfulness by @andrask in https://github.com/tox-dev/PyVenvManage/pull/143
- 🔒 ci(workflows): add zizmor security auditing by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/154
- 🐛 fix(icons): resolve NoSuchFieldError on IntelliJ 2026.1 by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/157
- 🔒 fix(ci): split release workflow for proper credential scoping by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/158
- 🐛 fix(icons): resolve NoSuchFieldError on IntelliJ 2026.1 by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/157
- 🔒 fix(ci): split release workflow for proper credential scoping by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/158

## [2.2.5] - 2026-01-30

Expand All @@ -26,13 +51,15 @@ ${GITHUB_EVENT_RELEASE_BODY}
## [2.2.4] - 2026-01-30

- Bump version to `2.2.4-dev` by @github-actions[bot] in https://github.com/tox-dev/PyVenvManage/pull/123
- Use RELEASE_TOKEN for post-release PR and auto-merge by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/124
- Use RELEASE_TOKEN for post-release PR and auto-merge by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/124

## [2.2.3] - 2026-01-30

- Bump version to `2.2.3-dev` by @github-actions[bot] in https://github.com/tox-dev/PyVenvManage/pull/120
- Add auto-merge workflow for trusted contributors by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/121
- Make Python dependency optional to fix marketplace verification by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/122
- Make Python dependency optional to fix marketplace verification by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/122

## [2.2.2] - 2026-01-29

Expand All @@ -48,13 +75,16 @@ ${GITHUB_EVENT_RELEASE_BODY}
## [2.2.0] - 2026-01-04

- Changelog update - `v2.1.2` by @github-actions[bot] in https://github.com/tox-dev/PyVenvManage/pull/78
- Optimize GitHub Actions: parallelize verification and fix disk space by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/99
- Refactor to modern Kotlin idioms and fix deprecated API by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/100
- Optimize GitHub Actions: parallelize verification and fix disk space by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/99
- Refactor to modern Kotlin idioms and fix deprecated API by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/100
- Add cache invalidation with file watcher by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/101
- Improve error UX with notifications by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/102
- Add plugin settings page by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/103
- Improve plugin description and documentation by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/105
- Enhance project view decorations and add 100% test coverage by @gaborbernat in https://github.com/tox-dev/PyVenvManage/pull/104
- Enhance project view decorations and add 100% test coverage by @gaborbernat in
https://github.com/tox-dev/PyVenvManage/pull/104

## [2.1.2] - 2025-10-23

Expand Down Expand Up @@ -127,22 +157,22 @@ ${GITHUB_EVENT_RELEASE_BODY}

- Removed the usage of the deprecated PythonSdkType.getPythonExecutable API

[Unreleased]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.7...HEAD
[2.2.7]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.6...v2.2.7
[2.2.6]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.5...v2.2.6
[2.2.5]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.4...v2.2.5
[2.2.4]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.3...v2.2.4
[2.2.3]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.2...v2.2.3
[2.2.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.1...v2.2.2
[2.2.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.1.2...v2.2.0
[2.1.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.1.0...v2.1.2
[2.1.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.0.1...v2.1.0
[2.0.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.0.0...v2.0.1
[2.0.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.4.0...v2.0.0
[1.4.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.4...v1.4.0
[1.3.4]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.3...v1.3.4
[1.3.3]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.2...v1.3.3
[1.3.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/pyvenvmanage/PyVenvManage/commits/v1.3.0
[1.3.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.0...v1.3.1
[1.3.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.1...v1.3.2
[1.3.3]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.2...v1.3.3
[1.3.4]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.3...v1.3.4
[1.4.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.3.4...v1.4.0
[2.0.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v1.4.0...v2.0.0
[2.0.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.0.0...v2.0.1
[2.1.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.0.1...v2.1.0
[2.1.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.1.0...v2.1.2
[2.2.0]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.1.2...v2.2.0
[2.2.1]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.0...v2.2.1
[2.2.2]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.1...v2.2.2
[2.2.3]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.2...v2.2.3
[2.2.4]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.3...v2.2.4
[2.2.5]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.4...v2.2.5
[2.2.6]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.5...v2.2.6
[2.2.7]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.6...v2.2.7
[unreleased]: https://github.com/pyvenvmanage/PyVenvManage/compare/v2.2.7...HEAD
26 changes: 17 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,42 @@
**PyVenvManage** simplifies Python virtual environment management in JetBrains IDEs.

Managing multiple Python interpreters across different virtual environments (for testing against various Python versions
with tools like `tox` or `nox`) traditionally requires navigating through multiple dialogs in PyCharm or other JetBrains IDEs.
PyVenvManage streamlines this by enabling quick interpreter selection directly from the project view with just a right-click.
with tools like `tox` or `nox`) traditionally requires navigating through multiple dialogs in PyCharm or other JetBrains
IDEs. PyVenvManage streamlines this by enabling quick interpreter selection directly from the project view with just a
right-click.

## Features

- **Quick interpreter switching**: Right-click any virtual environment folder to set it as your project or module
interpreter instantly
- **Visual identification**: Virtual environment folders display with a distinctive icon and customizable decoration
(e.g., `.venv [3.11.5 - CPython]`) in the project view
- **Smart environment detection**: Automatically detects environment types (UV, Conda, Poetry, Hatch, Pipenv,
virtualenv) and sets appropriate metadata and icons
- **Dynamic icons**: Tree view and context menus display environment-specific icons (UV, Conda, Poetry, Hatch, Pipenv)
based on detection
- **Visual identification**: Virtual environment folders display with customizable decoration (e.g.,
`.venv [3.11.5 - CPython]`) in the project view
- **Customizable decorations**: Configure which fields to show (Python version, implementation, system site-packages,
creator tool), their order, and the format via Settings
- **Multi-IDE support**: Works with PyCharm (Community and Professional), IntelliJ IDEA, GoLand, CLion, and RustRover
- **Smart detection**: Automatically detects Python virtual environments by recognizing `pyvenv.cfg` files
- **Smart association**: In-project virtual environments are associated with the current project; external environments
(Poetry cache, Hatch cache, Pipenv virtualenvs) remain global
- **Cached version display**: Python version information is cached for performance and automatically refreshed when
`pyvenv.cfg` files change

<!-- Plugin description end -->

## Supported IDEs

Version 2025.1 or later of:
Version 2026.1 or later of:

- PyCharm (Community and Professional)
- IntelliJ IDEA (Community and Ultimate)
- GoLand
- CLion
- RustRover

**Note**: Version 2.2.x supports PyCharm 2025.1 and earlier.

## Install

In your JetBrains IDE, open **Settings** -> **Plugins**, search for "PyVenv Manage", and click **Install**.
Expand All @@ -51,9 +59,9 @@ The official plugin page is at https://plugins.jetbrains.com/plugin/20536-pyvenv
![usage video](anim.gif?raw=true)

1. Create or navigate to a Python virtual environment folder in your project
2. Right-click the virtual environment folder (e.g., `venv`, `.venv`, or any folder with a `pyvenv.cfg`)
3. Select **Set as Project Interpreter** or **Set as Module Interpreter**
4. The interpreter is configured instantly with a confirmation notification
1. Right-click the virtual environment folder (e.g., `venv`, `.venv`, or any folder with a `pyvenv.cfg`)
1. Select **Set as Project Interpreter** or **Set as Module Interpreter**
1. The interpreter is configured instantly with a confirmation notification

## Settings

Expand Down
16 changes: 14 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ dependencies {
testImplementation(libs.remoteRobot)
testImplementation(libs.remoteRobotFixtures)
intellijPlatform {
pycharmCommunity(platformVersion)
pycharm(platformVersion)
bundledPlugin("PythonCore")
pluginVerifier()
zipSigner()
Expand Down Expand Up @@ -124,7 +124,7 @@ intellijPlatform {
listOf(IntelliJPlatformType.fromCode(verifyIde))
} else {
listOf(
IntelliJPlatformType.PyCharmCommunity,
IntelliJPlatformType.PyCharm,
IntelliJPlatformType.PyCharmProfessional,
)
}
Expand Down Expand Up @@ -152,6 +152,17 @@ kover {
onCheck = true
}
}
filters {
excludes {
// SdkFactory.createSdk requires full IntelliJ platform (WriteAction, ProjectJdkTable);
// EnvironmentDetector has platform-specific branches (Windows/Linux) untestable on macOS.
// Both are covered by UI tests.
classes(
"com.github.pyvenvmanage.sdk.SdkFactory",
"com.github.pyvenvmanage.sdk.EnvironmentDetector",
)
}
}
verify {
rule {
minBound(100)
Expand Down Expand Up @@ -222,6 +233,7 @@ val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
add("-Djb.consents.confirmation.enabled=false")
add("-Didea.trust.all.projects=true")
add("-Dide.show.tips.on.startup.default.value=false")
add("-Dskiko.renderApi=SOFTWARE")
val isMac =
org.gradle.internal.os.OperatingSystem
.current()
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ nl.littlerobots.vcu.resolver=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.welcome=never
platformVersion=2025.1
platformVersion=2026.1
pluginGroup=com.github.pyvenvmanage
pluginName=PyVenv Manage 2
pluginRepositoryUrl=https://github.com/pyvenvmanage/PyVenvManage
pluginSinceBuild=251
pluginSinceBuild=261
pluginVersion=2.2.8-dev
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip
networkTimeout=10000
networkTimeout=60000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.github.pyvenvmanage

import javax.swing.Icon
import java.nio.file.Path

import com.intellij.ide.projectView.PresentationData
import com.intellij.ide.projectView.ProjectViewNode
import com.intellij.ide.projectView.ProjectViewNodeDecorator
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.ui.SimpleTextAttributes

import com.jetbrains.python.sdk.PythonSdkUtil

import com.github.pyvenvmanage.sdk.EnvironmentDetector
import com.github.pyvenvmanage.sdk.SdkFactory
import com.github.pyvenvmanage.settings.PyVenvManageSettings

class VenvProjectViewNodeDecorator : ProjectViewNodeDecorator {
Expand All @@ -23,6 +27,17 @@ class VenvProjectViewNodeDecorator : ProjectViewNodeDecorator {
val settings = PyVenvManageSettings.getInstance()
val venvInfo = VenvVersionCache.getInstance().getInfo(pyVenvCfgPath.toString())
thisLogger().debug("VenvInfo from cache: $venvInfo")

val venvRoot = Path.of(pyVenvCfgPath.toString()).parent
val pythonExecutable = venvRoot?.let { PythonSdkUtil.getPythonExecutable(it.toString()) }

if (pythonExecutable != null) {
val envType = EnvironmentDetector.detectEnvironmentType(pythonExecutable)
val icon = SdkFactory.getIconForEnvironmentType(envType)
thisLogger().debug("Setting icon for environment type: $envType")
data.setIcon(icon)
}

venvInfo?.let { info ->
data.presentableText?.let { fileName ->
val decoration = settings.formatDecoration(info)
Expand All @@ -32,25 +47,6 @@ class VenvProjectViewNodeDecorator : ProjectViewNodeDecorator {
data.addText(decoration, SimpleTextAttributes.GRAY_ATTRIBUTES)
} ?: thisLogger().debug("No presentableText for decoration")
} ?: thisLogger().debug("No venvInfo found for $pyVenvCfgPath")
virtualenvIcon?.let { data.setIcon(it) }
}
}

companion object {
val virtualenvIcon: Icon? by lazy {
loadIcon("com.intellij.python.venv.icons.PythonVenvIcons", "VirtualEnv")
?: loadIcon("com.jetbrains.python.icons.PythonIcons\$Python", "Virtualenv")
}

private fun loadIcon(
className: String,
fieldName: String,
): Icon? =
try {
val clazz = Class.forName(className)
clazz.getField(fieldName).get(null) as? Icon
} catch (_: Exception) {
null
}
}
}
Loading
Loading