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
39 changes: 27 additions & 12 deletions extension/src/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ function handleMissingPackageAlert(
},
): Effect.Effect<void, never, never> {
const { context, code, uv, config } = deps;
const installPackages = (venvPath: string, packages: ReadonlyArray<string>) =>

const installPackages = (venv: string, packages: ReadonlyArray<string>) =>
code.window.useInfallible((api) =>
api.withProgress(
{
Expand All @@ -211,7 +212,20 @@ function handleMissingPackageAlert(
progress.report({
message: `Installing ${packages.join(", ")}...`,
});
yield* uv.pipInstall(packages, { venv: venvPath });
yield* Effect.logDebug("Attempting `uv add`.").pipe(
Effect.annotateLogs({ packages, directory: venv }),
);
yield* uv.add(packages, { directory: venv }).pipe(
Effect.catchTag(
"MissingPyProjectError",
Effect.fnUntraced(function* () {
yield* Effect.logWarning(
"Failed to `uv add`, attempting `uv pip install`.",
);
yield* uv.pipInstall(packages, { venv });
}),
),
);
progress.report({
message: `Successfully installed ${packages.join(", ")}`,
});
Expand Down Expand Up @@ -249,21 +263,21 @@ function handleMissingPackageAlert(
const venv = findVenvPath(context.controller.env.path);

if (Option.isNone(venv)) {
// no venv so can't do anything
yield* Effect.logWarning("Could not find venv.Skipping install.");
return;
}

const choice = yield* code.window
.useInfallible((api) =>
const choice = Option.fromNullable(
yield* code.window.useInfallible((api) =>
api.showInformationMessage(
operation.packages.length === 1
? `Missing package: ${operation.packages[0]}. Install with \`uv pip\`?`
: `Missing packages: ${operation.packages.join(", ")}. Install with \`uv pip\`?`,
? `Missing package: ${operation.packages[0]}. Install with uv?`
: `Missing packages: ${operation.packages.join(", ")}. Install with uv?`,
"Install All",
"Customize...",
),
)
.pipe(Effect.map(Option.fromNullable));
),
);

if (Option.isNone(choice)) {
// dismissed
Expand All @@ -275,7 +289,9 @@ function handleMissingPackageAlert(
Effect.annotateLogs("packages", operation.packages),
);
yield* installPackages(venv.value, operation.packages);
} else if (choice.value === "Customize...") {
}

if (choice.value === "Customize...") {
const response = yield* code.window
.useInfallible((api) =>
api.showInputBox({
Expand All @@ -294,10 +310,9 @@ function handleMissingPackageAlert(
yield* Effect.logInfo("Install packages").pipe(
Effect.annotateLogs("packages", newPackages),
);

yield* installPackages(venv.value, newPackages);
}

// Cancel - do nothing
}).pipe(Effect.catchAllCause(Effect.logError));
}

Expand Down
38 changes: 34 additions & 4 deletions extension/src/services/Uv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,49 @@ class UvError extends Data.TaggedError("UvError")<{
stderr: string;
}> {}

class MissingPyProjectError extends Data.TaggedError("MissingPyProjectError")<{
directory: string;
cause: UvError;
}> {}

export class Uv extends Effect.Service<Uv>()("Uv", {
dependencies: [NodeContext.layer, LoggerLive],
scoped: Effect.gen(function* () {
const executor = yield* CommandExecutor.CommandExecutor;
const uv = createUv(executor);
return {
add(
packages: ReadonlyArray<string>,
options: {
readonly directory: string;
},
) {
const { directory } = options;
return uv({
args: ["add", "--directory", directory, ...packages],
}).pipe(
Effect.catchTag("UvError", (cause) =>
Effect.fail(
cause.stderr.includes(
"error: No `pyproject.toml` found in current directory or any parent directory",
)
? new MissingPyProjectError({ directory, cause })
: cause,
),
),
);
},
pipInstall(
packages: ReadonlyArray<string>,
options: { readonly venv: string },
options: {
readonly venv: string;
},
) {
return uv({
args: ["pip", "install", ...packages],
venv: options.venv,
env: {
VIRTUAL_ENV: options.venv,
},
});
},
};
Expand All @@ -38,10 +68,10 @@ export class Uv extends Effect.Service<Uv>()("Uv", {
function createUv(executor: CommandExecutor.CommandExecutor) {
return Effect.fn("uv")(function* (options: {
readonly args: ReadonlyArray<string>;
readonly venv: string;
readonly env?: Record<string, string>;
}) {
const command = Command.make("uv", ...options.args).pipe(
Command.env({ NO_COLOR: "1", VIRTUAL_ENV: options.venv }),
Command.env({ NO_COLOR: "1", ...options.env }),
);
yield* Effect.logDebug("Running command").pipe(
Effect.annotateLogs({ command }),
Expand Down