-
Notifications
You must be signed in to change notification settings - Fork 57
fix: hide Python activity bar icon in non-Python workspaces #1663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
88f9c13
96e6814
293f0ec
96e0494
f922e06
d72d5df
df4e496
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,7 +93,8 @@ | |
| import { PythonStatusBarImpl } from './features/views/pythonStatusBar'; | ||
| import { updateViewsAndStatus } from './features/views/revealHandler'; | ||
| import { TemporaryStateManager } from './features/views/temporaryStateManager'; | ||
| import { PythonEnvTreeItem } from './features/views/treeViewItems'; | ||
| import { ProjectItem, PythonEnvTreeItem } from './features/views/treeViewItems'; | ||
|
Check failure on line 96 in src/extension.ts
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| import { registerWorkspacePythonContext } from './features/views/workspacePythonContext'; | ||
| import { collectEnvironmentInfo, getEnvManagerAndPackageManagerConfigLevels, runPetInTerminalImpl } from './helpers'; | ||
| import { EnvironmentManagers, ProjectCreators, PythonProjectManager } from './internal.api'; | ||
| import { registerInlineScriptFeatures } from './managers/builtin/inlineScript/main'; | ||
|
|
@@ -113,6 +114,7 @@ | |
| import { registerPyenvFeatures } from './managers/pyenv/main'; | ||
|
|
||
| export async function activate(context: ExtensionContext): Promise<PythonEnvironmentApi | undefined> { | ||
| registerWorkspacePythonContext(context.subscriptions); | ||
| // Only skip activation if user explicitly set useEnvironmentsExtension to false. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Registration occurs before the |
||
| // When disabled, the main Python extension handles environments instead (legacy mode). | ||
| const config = getConfiguration('python'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { Disposable } from 'vscode'; | ||
| import { executeCommand } from '../../common/command.api'; | ||
| import { createFileSystemWatcher, findFiles, onDidChangeWorkspaceFolders } from '../../common/workspace.apis'; | ||
|
|
||
| export const PYTHON_WORKSPACE_KEY = 'python-envs.workspaceHasPython'; | ||
|
|
||
| const MARKER_GLOB = '**/{*.py,pyproject.toml,setup.py,requirements.txt,Pipfile,manage.py,app.py,.venv,.conda,mspythonconfig.json}'; | ||
| const EXCLUDE = '**/{node_modules,.git,site-packages}/**'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The classifier omits legitimate notebook-only and stub-only workspaces ( |
||
|
|
||
| async function refresh(): Promise<void> { | ||
| const hits = await findFiles(MARKER_GLOB, EXCLUDE, 1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this only searching inside open workspaces folders. What if user opens a standalone Python file without opening the folder?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
async function refresh(): Promise<void> {
const hits = await findFiles(MARKER_GLOB, EXCLUDE, 1);
if (hits.length > 0) {
await executeCommand('setContext', PYTHON_WORKSPACE_KEY, true);
return;
}
const hasPythonDoc = workspace.textDocuments.some(
(doc) => doc.languageId === 'python',
);
await executeCommand('setContext', PYTHON_WORKSPACE_KEY, hasPythonDoc);
}And subscribe to |
||
| await executeCommand('setContext', PYTHON_WORKSPACE_KEY, hits.length > 0); | ||
| } | ||
|
|
||
| export function registerWorkspacePythonContext(disposables: Disposable[]): void { | ||
| const watcher = createFileSystemWatcher(MARKER_GLOB, false, true, false); | ||
| disposables.push( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct-- const EXCLUDE_RE = /[\\/](node_modules|\.git|site-packages)[\\/]/;
watcher.onDidCreate((uri) => { if (!EXCLUDE_RE.test(uri.fsPath)) void refresh(); }),
watcher.onDidDelete((uri) => { if (!EXCLUDE_RE.test(uri.fsPath)) void refresh(); }),This avoids unnecessary |
||
| watcher, | ||
| watcher.onDidCreate(() => void refresh()), | ||
| watcher.onDidDelete(() => void refresh()), | ||
| onDidChangeWorkspaceFolders(() => void refresh()), | ||
| ); | ||
| void refresh(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Independent refreshes can overlap, allowing an older There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Each There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This persistent UI policy has no regression coverage. Add focused tests for initial discovery, marker creation and last-marker deletion, exclusions, workspace-folder changes, directory markers, and overlapping refreshes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Could concurrent fire-and-forget refreshes complete out of order and let an older scan overwrite newer workspace state? Serialize refreshes or use a generation token, and cover reverse-order |
||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This adds asynchronous initial discovery and event-driven context transitions without regression tests. Add coverage for initial state, marker creation, deletion of the final marker, workspace-folder changes, directory markers, and overlapping refreshes. [verified] |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR hides the activity-bar icon unless this context key is true:
python-envs.workspaceHasPythonThat key is not defined initially. It only gets set after the extension’s
activate()function runs:registerWorkspacePythonContext(context.subscriptions);However,
package.jsoncurrently activates the extension only when VS Code opens a Python-language document:"activationEvents": [ "onLanguage:python" ]This creates a circular dependency:
onLanguage:pythonnever activates the extension.Example
A user opens a repository containing:
my-project/
├── pyproject.toml
├── requirements.txt
└── README.md
The repository is clearly a Python project, but the user has not opened a .py file yet.
Expected: The Python activity-bar icon appears because pyproject.toml identifies the workspace as Python.
Actual: The extension does not activate, so it never searches for pyproject.toml . The context key remains unset, and the icon stays hidden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Fix is to add
workspaceContainsactivation events topackage.jsonso the extension activates even before a.pyfile is opened:"activationEvents": [
"onLanguage:python",
"workspaceContains:**/*.py",
"workspaceContains:pyproject.toml",
"workspaceContains:requirements.txt",
"workspaceContains:Pipfile",
"workspaceContains:setup.py",
"workspaceContains:mspythonconfig.json",
"workspaceContains:.venv",
"workspaceContains:.conda"
]
This breaks the circular dependency - extension activates when any marker file is present in the workspace, sets the context key, and the icon appears without needing a .py file open first.