дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
feat(run): browse for .env files from the Environment variables field (#1106)
A folder button next to the Environment variables input opens a file dialog at the project root and appends the chosen files to the configuration's .env files (relative to the root inside the project, duplicates skipped). Hidden in remote windows, where the dialog would pick local paths.
Цей коміт міститься в:
1 джерело
9a6b84e287
коміт
c347e331ab
3 files changed
+73
-1
No files matched your search
@@ -6,8 +6,10 @@
|
||||
import { parseJavaArgs } from "@/platform/javaLaunch";
|
||||
import { useMemo, useRef, useState, type KeyboardEvent, type ReactNode, type RefObject } from "react";
|
||||
import clsx from "clsx";
|
||||
import type { BeforeLaunchTask, RunConfig } from "@/platform/ipc";
|
||||
import { isTauri, type BeforeLaunchTask, type RunConfig } from "@/platform/ipc";
|
||||
import { useWorkflowSettings } from "@/platform/workflowSettings";
|
||||
import { relativePath, useProject } from "@/platform/project";
|
||||
import { isRemoteWindow } from "@/platform/remote";
|
||||
import { useTx, useTxTemplate } from "@/platform/i18n";
|
||||
import {
|
||||
Button,
|
||||
@@ -169,6 +171,14 @@ export interface ConfigFormProps {
|
||||
modifyRequest: number;
|
||||
}
|
||||
|
||||
/** Picks `.env` files; paths inside the project are returned relative to its root. */
|
||||
async function chooseEnvFiles(root: string | null, title: string): Promise<string[]> {
|
||||
const { open } = await import("@tauri-apps/plugin-dialog");
|
||||
const picked = await open({ multiple: true, title, defaultPath: root ?? undefined });
|
||||
const paths = picked === null ? [] : Array.isArray(picked) ? picked : [picked];
|
||||
return paths.map((p) => (root ? relativePath(root, p) : p));
|
||||
}
|
||||
|
||||
export function ConfigForm({ config, all, type, errors, template, onChange, modifyRequest }: ConfigFormProps) {
|
||||
const openByDefault = useWorkflowSettings((s) => s.run.showOnStart);
|
||||
const externalTools = useWorkflowSettings((s) => s.externalTools);
|
||||
@@ -176,6 +186,7 @@ export function ConfigForm({ config, all, type, errors, template, onChange, modi
|
||||
const toolWindows = useContributions((s) => s.toolWindows);
|
||||
const tx = useTx();
|
||||
const txTemplate = useTxTemplate();
|
||||
const root = useProject((s) => s.project?.root ?? null);
|
||||
const [shown, setShown] = useState<ReadonlySet<OptionId>>(() => new Set());
|
||||
const [argsText, setArgsText] = useState<string | null>(null);
|
||||
const [envText, setEnvText] = useState<string | null>(null);
|
||||
@@ -480,6 +491,21 @@ export function ConfigForm({ config, all, type, errors, template, onChange, modi
|
||||
title="Edit Environment Variables"
|
||||
onClick={() => setEnvOpen(true)}
|
||||
/>
|
||||
{isTauri() && !isRemoteWindow() && (
|
||||
<IconButton
|
||||
icon="folder-open"
|
||||
size="small"
|
||||
iconSize={14}
|
||||
title="Browse for .env Files"
|
||||
onClick={() =>
|
||||
void chooseEnvFiles(root, tx("Browse for .env Files")).then((files) => {
|
||||
const current = config.envFiles ?? [];
|
||||
const added = files.filter((f) => !current.includes(f));
|
||||
if (added.length) update({ envFiles: [...current, ...added] });
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{config.includeSystemEnv === false && (
|
||||
<span className={styles.chooserMuted}>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
|
||||
const open = vi.hoisted(() => vi.fn());
|
||||
vi.mock("@tauri-apps/plugin-dialog", () => ({ open }));
|
||||
vi.mock("@/platform/ipc", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/platform/ipc")>()),
|
||||
isTauri: () => true,
|
||||
}));
|
||||
|
||||
import type { RunConfig } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { ConfigForm } from "./ConfigForm";
|
||||
|
||||
const config: RunConfig = {
|
||||
id: "app",
|
||||
name: "app",
|
||||
kind: "shell",
|
||||
command: "python3",
|
||||
args: [],
|
||||
cwd: null,
|
||||
env: {},
|
||||
detected: false,
|
||||
isTest: false,
|
||||
debugAdapter: null,
|
||||
envFiles: [".env"],
|
||||
};
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("Environment variables", () => {
|
||||
it("adds the .env files chosen in the file dialog, relative to the project root", async () => {
|
||||
useProject.setState({ project: { root: "/work/app" } as never });
|
||||
open.mockResolvedValue(["/work/app/.env", "/work/app/config/dev.env", "/elsewhere/shared.env"]);
|
||||
const onChange = vi.fn();
|
||||
render(<ConfigForm config={config} all={[]} type={undefined} errors={{}} onChange={onChange} modifyRequest={0} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Browse for .env Files" }));
|
||||
await waitFor(() =>
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ envFiles: [".env", "config/dev.env", "/elsewhere/shared.env"] }),
|
||||
),
|
||||
);
|
||||
expect(open).toHaveBeenCalledWith(expect.objectContaining({ multiple: true, defaultPath: "/work/app" }));
|
||||
});
|
||||
});
|
||||
@@ -2935,6 +2935,7 @@
|
||||
"Modify options": "修改选项",
|
||||
"e.g. ./gradlew or cargo": "例如 ./gradlew 或 cargo",
|
||||
"Edit Environment Variables": "编辑环境变量",
|
||||
"Browse for .env Files": "浏览 .env 文件",
|
||||
"System environment variables are not passed": "不传递系统环境变量",
|
||||
"Redirect input from": "重定向输入自",
|
||||
"File relative to the project root": "相对于项目根目录的文件",
|
||||
|
||||
Reference in new issue
Заблокувати користувача