Skip to content
Merged
22 changes: 20 additions & 2 deletions apps/web/src/components/chat/OpenInPicker.browser.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,20 @@ afterEach(async () => {
vi.unstubAllGlobals();
});

async function renderPicker(availableEditors: readonly EditorId[] = editors, panel = false) {
async function renderPicker(
availableEditors: readonly EditorId[] = editors,
panel = false,
{ openInCwd = "/work/project", revealFileManager = false } = {},
) {
await act(async () => {
for (const listener of state.listeners) listener();
const node = (
<OpenInPicker
environmentId={selected}
keybindings={DEFAULT_RESOLVED_KEYBINDINGS}
availableEditors={availableEditors}
openInCwd="/work/project"
openInCwd={openInCwd}
revealFileManager={revealFileManager}
displayMode={panel ? "panel" : "toolbar"}
compact={!panel}
/>
Expand Down Expand Up @@ -208,6 +213,19 @@ describe("host editor access", () => {
});
});

it("reveals a file in the file manager instead of opening it", async () => {
state.allowed.add(selected);
await renderPicker(["file-manager"], false, {
openInCwd: "/work/project/README.md",
revealFileManager: true,
});
await click(primaryButton());
expect(state.run).toHaveBeenCalledExactlyOnceWith({
environmentId: selected,
input: { cwd: "/work/project/README.md", editor: "file-manager", reveal: true },
});
});

describe("client editor links", () => {
it("opens an SSH editor URL without host operate permission", async () => {
state.remote = { mode: "remote-links", host: { kind: "ssh-alias", host: "test-host" } };
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/chat/OpenInPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export const OpenInPicker = memo(function OpenInPicker({
compact = false,
enableShortcut = true,
displayMode = "toolbar",
revealFileManager = false,
}: {
environmentId: EnvironmentId;
keybindings: ResolvedKeybindingsConfig;
Expand All @@ -221,6 +222,7 @@ export const OpenInPicker = memo(function OpenInPicker({
compact?: boolean;
enableShortcut?: boolean;
displayMode?: "toolbar" | "panel";
revealFileManager?: boolean;
}) {
const isPanel = displayMode === "panel";
const ActionGroup = isPanel ? "div" : Group;
Expand Down Expand Up @@ -266,6 +268,7 @@ export const OpenInPicker = memo(function OpenInPicker({
input: {
cwd: openInCwd,
editor,
...(revealFileManager && editor === "file-manager" ? { reveal: true } : {}),
},
});
setPreferredEditor(editor);
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/components/files/FilePreviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
isWorkspaceImagePreviewPath,
isWorkspaceVideoPreviewPath,
} from "@t3tools/shared/filePreview";
import { useAtomValue } from "@effect/atom-react";
import {
DEFAULT_TOKENIZE_MAX_LENGTH,
VirtualizedFile,
Expand Down Expand Up @@ -67,6 +68,7 @@ import { assetEnvironment } from "~/state/assets";
import { usePreviewAvailable } from "~/browser/previewRuntime";
import { useEnvironmentHttpBaseUrl, usePrimaryEnvironmentId } from "~/state/environments";
import { previewEnvironment } from "~/state/preview";
import { serverEnvironment } from "~/state/server";
import { useEnvironmentScope } from "~/state/session";
import { useAtomCommand } from "~/state/use-atom-command";
import { useAtomQueryRunner } from "~/state/use-atom-query-runner";
Expand Down Expand Up @@ -991,6 +993,7 @@ export default function FilePreviewPanel({
const canOperatePreview = useEnvironmentScope(environmentId, AuthPreviewOperateScope);
const primaryEnvironmentId = usePrimaryEnvironmentId();
const remoteOpenState = useRemoteOpenState(environmentId);
const serverConfig = useAtomValue(serverEnvironment.configValueAtom(environmentId));
const environmentHttpBaseUrl = useEnvironmentHttpBaseUrl(environmentId);
const previewAvailable = usePreviewAvailable(environmentId);
const createAssetUrl = useAtomQueryRunner(assetEnvironment.createUrl, {
Expand Down Expand Up @@ -1036,6 +1039,12 @@ export default function FilePreviewPanel({
attachment === undefined &&
(file.isNotFile || (folderTrail !== undefined && file.data === null && file.error === null));
const hostFolderPath = isFolder && isHostFile ? relativePath : null;
// A chat-linked folder reads as a file until its read settles, and revealing a
// folder selects it in its parent instead of opening it, so reveal waits.
const revealsInFileManager =
!isFolder &&
(file.data !== null || file.error !== null) &&
serverConfig?.shellRevealInFileManager === true;
// Everything preview-related keys off previewPath; a folder has no preview.
const previewPath = isFolder ? null : relativePath;
const [explorerOpen, setExplorerOpen] = useState(initialExplorerOpen);
Expand Down Expand Up @@ -1229,6 +1238,7 @@ export default function FilePreviewPanel({
availableEditors={availableEditors}
openInCwd={absolutePath}
compact
revealFileManager={revealsInFileManager}
/>
) : null}
{canToggleRendered && renderedMode ? (
Expand Down
Loading