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 .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ charset = utf-8
indent_style = tab
indent_size = 4

[*.{yml,yaml}]
[*.{yml,yaml,md}]
indent_style = space
indent_size = 2
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ To make use of it, make sure your user settings contain `"python.useEnvironments
- List all configured [Hatch environments]
- Provide controls to set them as active environment for your project, activate them in a terminal, and delete them from disk
- Temporarily modify an environment’s packages using the configured [`installer`]
- Define a `hatch.envInterpreter` command for use in `launch.json` or `tasks.json`, see [below](#commands)

![screenshot](./screenshot.png)

Expand All @@ -18,6 +19,40 @@ Persistent modifications to the installed packages should be done by editing Hat

[`installer`]: https://hatch.pypa.io/latest/how-to/environment/select-installer/

## Commands
- `hatch.envInterpreter`: not an interactive command, but rather for use in `launch.json` or `tasks.json` via [variable substitution], e.g. for `command` in `tasks.json` or `python` in `launch.json`:

```jsonc
{ // tasks.json
"version": "0.2.0",
"tasks": [
{
"label": "Build docs",
"type": "process",
"command": "${input:docsInterpreter}",
"args": ["-m", "sphinx", "docs", "docs/_build"],
"problemMatcher": [],
},
],
"inputs": [
{
"id": "docsInterpreter",
"type": "command",
"command": "hatch.envInterpreter",
"args": { "env": "docs" },
},
],
}
```

The command supports the following `args`:
- `env`: name of the environment (defaults to `"default"`)
- `workspace`: path to the workspace root (defaults to the first currently open workspace)

It can be used without going through `inputs` using just `${command:hatch.envInterpreter}` to always use the `default` environment instead of the currently active one.

[variable substitution]: https://code.visualstudio.com/docs/reference/variables-reference

## Extension Settings
- `hatch.executable`: path to the `hatch` executable (supports `~` expansion). Defaults to the output of `which hatch`.

Expand Down
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
}
}
},
"commands": [
{
"command": "hatch.envInterpreter",
"title": "Hatch: Get an environment’s interpreter path",
"enablement": false
}
],
"icons": {
"hatch-logo": {
"description": "Hatch Logo",
Expand Down
33 changes: 33 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Uri, workspace } from 'vscode'
import type { HatchEnvManager } from './hatch-env-manager.js'

export interface CommandOptions {
/** environment name */
env?: string | undefined
/** workspace directory */
workspace?: string | undefined
}

/** Command to get the interpreter for a given environment.
*
* Modeled after [`python.interpreterPath`].
*
* [`python.interpreterPath`]: https://github.com/microsoft/vscode-python/blob/9ded8032f6a455289113026ed1dca4c5ed81e6e8/src/client/interpreter/interpreterPathCommand.ts
*/
export async function getEnvInterpreter(
envManager: HatchEnvManager,
{ env: envName = 'default', workspace: wsDir }: CommandOptions = {},
): Promise<string> {
const workspaceUri = wsDir
? Uri.file(wsDir)
: workspace.workspaceFolders?.[0]?.uri
if (!workspaceUri) throw new Error('No workspace open')
await envManager.refresh(workspaceUri)
const envs = await envManager.getEnvironments(workspaceUri)
const env = envs.find((e) => e.name === envName)
if (!env)
throw new Error(
`Environment “${envName}” not found in workspace “${workspaceUri.fsPath}”`,
)
return env.execInfo.run.executable
}
2 changes: 2 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export const EXE_CONFIG_SECTION = 'hatch' // dotted section name
export const EXE_CONFIG_SETTING = 'executable' // last element

export const ENVS_EXT_ID = 'ms-python.vscode-python-envs'

export const CMD_ENV_INTERPRETER = 'hatch.envInterpreter'
8 changes: 6 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type ExtensionContext, window } from 'vscode'
import { commands, type ExtensionContext, window } from 'vscode'
import { HatchExecutableTracker } from './cli/index.js'
import { EXTENSION_ID } from './common/constants.js'
import { type CommandOptions, getEnvInterpreter } from './commands.js'
import { CMD_ENV_INTERPRETER, EXTENSION_ID } from './common/constants.js'
import { registerLogger } from './common/logging.js'
import { setWorkspacePersistentState } from './common/persistent-state.js'
import { HatchEnvManager } from './hatch-env-manager.js'
Expand Down Expand Up @@ -28,6 +29,9 @@ export async function activate(context: ExtensionContext): Promise<Api> {
extensionId: EXTENSION_ID,
}),
api.registerPackageManager(pkgManager, { extensionId: EXTENSION_ID }),
commands.registerCommand(CMD_ENV_INTERPRETER, (args?: CommandOptions) =>
getEnvInterpreter(envManager, args),
),
)

return {
Expand Down
26 changes: 22 additions & 4 deletions test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import type {
} from '@vscode/python-environments'
import { before, beforeEach } from 'mocha'
import * as vscode from 'vscode'
import { ENVS_EXT_ID, EXTENSION_ID } from '../src/common/constants'
import type { CommandOptions } from '../src/commands'
import {
CMD_ENV_INTERPRETER,
ENVS_EXT_ID,
EXTENSION_ID,
} from '../src/common/constants'
import type * as extension from '../src/extension'
import MockExec from './mock-exec'
import { tmpdir, waitForCondition } from './test-utils'
Expand Down Expand Up @@ -69,19 +74,32 @@ describe('Env Manager', () => {
envManager = ext.envManager
})

it('should return environments', async () => {
await using dir = await tmpdir('hatch-')
async function testProj(): ReturnType<typeof tmpdir> {
const dir = await tmpdir('hatch-')
api.addPythonProject({ name: 'test', uri: dir.uri })

exec.reset(
[['env', 'show', '--json'], { mockenv: { type: 'virtual' } }],
[['env', 'find', 'mockenv'], 'mockpath\n'],
)
return dir
}

it('should return environments', async () => {
await using dir = await testProj()
//This gets called automatically: await envManager.refresh(dir.uri)
const envs = await envManager.getEnvironments(dir.uri)

assert.ok(envs.length > 0, 'No environments found')
assert.equal(envs[0].name, 'mockenv')
assert.equal(envs[0].sysPrefix, 'mockpath')
})

it('should implement an env interpreter path command', async () => {
await using _ = await testProj()
//This gets called automatically: await envManager.refresh(dir.uri)
const intp = await vscode.commands.executeCommand(CMD_ENV_INTERPRETER, {
env: 'mockenv',
} satisfies CommandOptions)
assert.equal(intp, 'mockpath/bin/python')
})
})