ea84b913b4
* feat: choose worktree when opening IDE on multi-repo tasks On multi-repo tasks the open-in-IDE button always opened the first worktree. The editors menu now shows a worktree picker (repo + branch) when a session has multiple worktrees, and each editor in the dropdown expands into a per-worktree submenu. - open-editor/open-folder accept an optional worktree_id; empty keeps the first-worktree fallback so existing clients are unaffected - session DTOs now serialize the full worktrees list instead of only the flattened first-worktree fields - useSessionWorktrees unions API worktrees with the WS-populated list - new Playwright spec covers the picker on a real multi-repo session * fix: address review feedback on worktree picker - fall back on empty-string worktree fields, not only null/undefined - bind optional open-folder body via EOF check instead of ContentLength so chunked requests keep their worktree_id - wrap disabled editor buttons in a focusable span so tooltips work - add hook test asserting worktreeId is forwarded as worktree_id * fix: map editor service errors to HTTP status, drop e2e sleep - open-editor/open-folder return 404/400/409 for known service errors (unknown worktree or editor, invalid config, unavailable editor) instead of a blanket 500 - replace the picker spec's 750ms sleep with an explicit composer click so the autofocus race is deterministic --------- Co-authored-by: Kandev Agent <agent@kandev.dev>
80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { openSessionInEditor } from "@/lib/api";
|
|
import { useRequest } from "@/lib/http/use-request";
|
|
import { useDockviewStore } from "@/lib/state/dockview-store";
|
|
import { openFileInVscode } from "@/lib/api/domains/vscode-api";
|
|
|
|
type OpenEditorOptions = {
|
|
filePath?: string;
|
|
line?: number;
|
|
column?: number;
|
|
editorId?: string;
|
|
editorType?: string;
|
|
worktreeId?: string;
|
|
};
|
|
|
|
/**
|
|
* Parse an `internal://vscode?goto=file:line:col` sentinel URL.
|
|
* Returns goto info or null if no file param.
|
|
*/
|
|
function parseInternalVscodeURL(url: string): { file: string; line: number; col: number } | null {
|
|
const qIdx = url.indexOf("?goto=");
|
|
if (qIdx === -1) return null;
|
|
|
|
const goto_ = url.slice(qIdx + 6);
|
|
if (!goto_) return null;
|
|
|
|
// Format: file:line:col or file:line or file
|
|
const parts = goto_.split(":");
|
|
return {
|
|
file: parts[0],
|
|
line: parseInt(parts[1] ?? "0", 10) || 0,
|
|
col: parseInt(parts[2] ?? "0", 10) || 0,
|
|
};
|
|
}
|
|
|
|
export function useOpenSessionInEditor(sessionId?: string | null) {
|
|
const request = useRequest(async (options?: OpenEditorOptions) => {
|
|
if (!sessionId) {
|
|
return null;
|
|
}
|
|
const response = await openSessionInEditor(
|
|
sessionId,
|
|
{
|
|
editor_id: options?.editorId,
|
|
editor_type: options?.editorType,
|
|
file_path: options?.filePath,
|
|
line: options?.line,
|
|
column: options?.column,
|
|
worktree_id: options?.worktreeId,
|
|
},
|
|
{ cache: "no-store" },
|
|
);
|
|
|
|
if (response?.url) {
|
|
// Intercept internal VS Code URLs — handle in-app instead of opening a tab.
|
|
if (response.url.startsWith("internal://vscode")) {
|
|
const goto_ = parseInternalVscodeURL(response.url);
|
|
useDockviewStore.getState().openInternalVscode(null);
|
|
|
|
// Open the file via the backend Remote CLI if goto params are provided
|
|
if (goto_ && sessionId) {
|
|
openFileInVscode(sessionId, goto_.file, goto_.line, goto_.col);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
// Editor integrations may return registered custom schemes such as vscode://.
|
|
window.open(response.url, "_blank", "noopener,noreferrer");
|
|
}
|
|
return response ?? null;
|
|
});
|
|
|
|
return {
|
|
open: (options?: OpenEditorOptions) => request.run(options),
|
|
status: request.status,
|
|
isLoading: request.isLoading,
|
|
};
|
|
}
|