Skip to content

Commit 349866d

Browse files
feat: map run button to uv run
Agent-Logs-Url: https://github.com/microsoft/vscode-python-environments/sessions/5f628821-9c7b-48a6-9f5f-9fccb9a33bf6 Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com>
1 parent c5a1430 commit 349866d

3 files changed

Lines changed: 70 additions & 3 deletions

File tree

package.nls.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@
4545
"python-envs.revealProjectInExplorer.title": "Reveal Project in Explorer",
4646
"python-envs.revealEnvInManagerView.title": "Reveal in Environment Managers View",
4747
"python-envs.runPetInTerminal.title": "Run Python Environment Tool (PET) in Terminal...",
48-
"python-envs.alwaysUseUv.description": "When set to true, uv will be used to manage all virtual environments if available. When set to false, uv will only manage virtual environments explicitly created by uv."
48+
"python-envs.alwaysUseUv.description": "When set to true, uv will be used to manage all virtual environments if available, and the run button will execute files with uv run. When set to false, uv will only manage environments explicitly created by uv, including for the run button."
4949
}

src/features/execution/runAsTask.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PythonEnvironment, PythonTaskExecutionOptions } from '../../api';
1212
import { traceInfo, traceWarn } from '../../common/logging';
1313
import { executeTask } from '../../common/tasks.apis';
1414
import { getWorkspaceFolder } from '../../common/workspace.apis';
15+
import { shouldUseUv } from '../../managers/builtin/helpers';
1516
import { quoteStringIfNecessary } from './execUtils';
1617

1718
function getWorkspaceFolderOrDefault(uri?: Uri): WorkspaceFolder | TaskScope {
@@ -31,11 +32,19 @@ export async function runAsTask(
3132
traceWarn('No Python executable found in environment; falling back to "python".');
3233
executable = 'python';
3334
}
34-
// Check and quote the executable path if necessary
35-
executable = quoteStringIfNecessary(executable);
3635

3736
const args = environment.execInfo?.activatedRun?.args ?? environment.execInfo?.run.args ?? [];
3837
const allArgs = [...args, ...options.args];
38+
const useUv = await shouldUseUv(undefined, environment.environmentPath.fsPath);
39+
40+
if (useUv) {
41+
allArgs.unshift('--python', executable);
42+
allArgs.unshift('run');
43+
executable = 'uv';
44+
}
45+
46+
// Check and quote the executable path if necessary
47+
executable = quoteStringIfNecessary(executable);
3948
traceInfo(`Running as task: ${executable} ${allArgs.join(' ')}`);
4049

4150
const task = new Task(

src/test/features/execution/runAsTask.unit.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@ import * as tasksApi from '../../../common/tasks.apis';
77
import * as workspaceApis from '../../../common/workspace.apis';
88
import * as execUtils from '../../../features/execution/execUtils';
99
import { runAsTask } from '../../../features/execution/runAsTask';
10+
import * as builtinHelpers from '../../../managers/builtin/helpers';
1011

1112
suite('runAsTask Tests', () => {
1213
let mockTraceInfo: sinon.SinonStub;
1314
let mockTraceWarn: sinon.SinonStub;
1415
let mockExecuteTask: sinon.SinonStub;
1516
let mockGetWorkspaceFolder: sinon.SinonStub;
1617
let mockQuoteStringIfNecessary: sinon.SinonStub;
18+
let mockShouldUseUv: sinon.SinonStub;
1719

1820
setup(() => {
1921
mockTraceInfo = sinon.stub(logging, 'traceInfo');
2022
mockTraceWarn = sinon.stub(logging, 'traceWarn');
2123
mockExecuteTask = sinon.stub(tasksApi, 'executeTask');
2224
mockGetWorkspaceFolder = sinon.stub(workspaceApis, 'getWorkspaceFolder');
2325
mockQuoteStringIfNecessary = sinon.stub(execUtils, 'quoteStringIfNecessary');
26+
mockShouldUseUv = sinon.stub(builtinHelpers, 'shouldUseUv').resolves(false);
2427
});
2528

2629
teardown(() => {
@@ -113,6 +116,61 @@ suite('runAsTask Tests', () => {
113116
assert.ok(mockTraceWarn.notCalled, 'Should not log warnings for valid environment');
114117
});
115118

119+
test('should use uv run when uv mode applies', async () => {
120+
const environment: PythonEnvironment = {
121+
envId: { id: 'test-env', managerId: 'test-manager' },
122+
name: 'Test Environment',
123+
displayName: 'Test Environment',
124+
shortDisplayName: 'TestEnv',
125+
displayPath: '/path/to/env',
126+
version: '3.9.0',
127+
environmentPath: Uri.file('/path/to/env'),
128+
execInfo: {
129+
run: {
130+
executable: '/path/to/python',
131+
args: ['--default'],
132+
},
133+
activatedRun: {
134+
executable: '/activated/python',
135+
args: ['--activated'],
136+
},
137+
},
138+
sysPrefix: '/path/to/env',
139+
};
140+
141+
const options: PythonTaskExecutionOptions = {
142+
name: 'UV Task',
143+
args: ['script.py', '--arg1'],
144+
};
145+
146+
const mockTaskExecution = {} as TaskExecution;
147+
148+
mockGetWorkspaceFolder.returns(undefined);
149+
mockShouldUseUv.withArgs(undefined, environment.environmentPath.fsPath).resolves(true);
150+
mockQuoteStringIfNecessary.withArgs('uv').returns('uv');
151+
mockExecuteTask.resolves(mockTaskExecution);
152+
153+
const result = await runAsTask(environment, options);
154+
155+
assert.strictEqual(result, mockTaskExecution, 'Should return the task execution result');
156+
157+
const taskArg = mockExecuteTask.firstCall.args[0] as Task;
158+
const execution = taskArg.execution as ShellExecution;
159+
160+
assert.strictEqual(execution.command, 'uv', 'Should execute uv when uv mode is enabled');
161+
assert.deepStrictEqual(
162+
execution.args,
163+
['run', '--python', '/activated/python', '--activated', 'script.py', '--arg1'],
164+
'Should prepend uv run arguments before the file arguments',
165+
);
166+
assert.ok(
167+
mockTraceInfo.calledWith(
168+
sinon.match(/Running as task: uv run --python \/activated\/python --activated script\.py --arg1/),
169+
),
170+
'Should log the uv run command',
171+
);
172+
});
173+
116174
test('should create and execute task with regular run configuration when no activatedRun', async () => {
117175
// Mock - Environment without activatedRun
118176
const environment: PythonEnvironment = {

0 commit comments

Comments
 (0)