From 6df84d104a4ebc42e09032f25564ea5b71b18bf8 Mon Sep 17 00:00:00 2001 From: maoxian Date: Tue, 12 May 2026 14:27:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=9C=A8=20VS=20Code?= =?UTF-8?q?=20=E4=B8=AD=E6=89=93=E5=BC=80=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持通过右键菜单在 Visual Studio Code 中打开文件夹。macOS 平台使用 `open -a` 命令,其他平台使用 CLI 命令。仅在桌面模式下可用。 --- src-tauri/src/commands/folders.rs | 39 +++++++++++++++++++ src-tauri/src/lib.rs | 1 + .../sidebar-conversation-list.tsx | 25 ++++++++++++ src/i18n/messages/ar.json | 1 + src/i18n/messages/de.json | 1 + src/i18n/messages/en.json | 1 + src/i18n/messages/es.json | 1 + src/i18n/messages/fr.json | 1 + src/i18n/messages/ja.json | 1 + src/i18n/messages/ko.json | 1 + src/i18n/messages/pt.json | 1 + src/i18n/messages/zh-CN.json | 1 + src/i18n/messages/zh-TW.json | 1 + src/lib/api.ts | 4 ++ 14 files changed, 79 insertions(+) diff --git a/src-tauri/src/commands/folders.rs b/src-tauri/src/commands/folders.rs index a68fd2d5..8b2c834c 100644 --- a/src-tauri/src/commands/folders.rs +++ b/src-tauri/src/commands/folders.rs @@ -3689,3 +3689,42 @@ async fn get_unpushed_hashes( Ok((Some(hashes), has_upstream)) } + +#[cfg(feature = "tauri-runtime")] +#[cfg_attr(feature = "tauri-runtime", tauri::command)] +pub async fn open_in_vscode(path: String) -> Result<(), AppCommandError> { + #[cfg(target_os = "macos")] + { + let status = crate::process::tokio_command("open") + .args(["-a", "Visual Studio Code", &path]) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await; + + if let Ok(s) = status { + if s.success() { + return Ok(()); + } + } + } + + let status = crate::process::tokio_command("code") + .arg(&path) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await; + + match status { + Ok(s) if s.success() => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err( + AppCommandError::dependency_missing("VS Code is not installed or available in PATH."), + ), + _ => Err(AppCommandError::dependency_missing( + "Failed to open VS Code.", + )), + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 58981ce0..50d42529 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -531,6 +531,7 @@ mod tauri_app { folders::add_folder_to_history, folders::remove_folder_from_history, folders::create_folder_directory, + folders::open_in_vscode, folders::clone_repository, folders::get_git_branch, folders::git_init, diff --git a/src/components/conversations/sidebar-conversation-list.tsx b/src/components/conversations/sidebar-conversation-list.tsx index 709832ea..8a061134 100644 --- a/src/components/conversations/sidebar-conversation-list.tsx +++ b/src/components/conversations/sidebar-conversation-list.tsx @@ -36,6 +36,7 @@ import { useTerminalContext } from "@/contexts/terminal-context" import { useZoomLevel } from "@/hooks/use-appearance" import { importLocalConversations, + openInVsCode, openProjectBootWindow, updateConversationTitle, updateConversationStatus, @@ -172,6 +173,7 @@ const FolderHeader = memo(function FolderHeader({ onChangeColor, onOpenInSystemExplorer, onOpenInTerminal, + onOpenInVsCode, isDragging, dragControls, t, @@ -191,6 +193,7 @@ const FolderHeader = memo(function FolderHeader({ onChangeColor: (folderId: number, color: string) => void onOpenInSystemExplorer: (folderId: number) => void onOpenInTerminal: (folderId: number) => void + onOpenInVsCode: (folderId: number) => void isDragging?: boolean dragControls: DragControls t: ReturnType @@ -329,6 +332,12 @@ const FolderHeader = memo(function FolderHeader({ onOpenInTerminal(folderId)}> {tFileTree("openInTerminal")} + onOpenInVsCode(folderId)} + > + {tFileTree("openInVsCode")} + @@ -400,6 +409,7 @@ interface FolderGroupItemProps { onChangeColor: (folderId: number, color: string) => void onOpenInSystemExplorer: (folderId: number) => void onOpenInTerminal: (folderId: number) => void + onOpenInVsCode: (folderId: number) => void onSelect: (id: number, agentType: string) => void onDoubleClick: (id: number, agentType: string) => void onRename: (id: number, newTitle: string) => Promise @@ -436,6 +446,7 @@ function FolderGroupItem({ onChangeColor, onOpenInSystemExplorer, onOpenInTerminal, + onOpenInVsCode, onSelect, onDoubleClick, onRename, @@ -512,6 +523,7 @@ function FolderGroupItem({ onChangeColor={onChangeColor} onOpenInSystemExplorer={onOpenInSystemExplorer} onOpenInTerminal={onOpenInTerminal} + onOpenInVsCode={onOpenInVsCode} isDragging={dragging} dragControls={dragControls} t={t} @@ -694,6 +706,18 @@ export function SidebarConversationList({ [folderIndex, createTerminalInDirectory, tFileTree] ) + const handleOpenFolderInVsCode = useCallback( + (folderId: number) => { + const folder = folderIndex.get(folderId) + if (!folder) return + void openInVsCode(folder.path).catch((err) => { + console.error("[openInVsCode] failed:", err) + toast.error(tFileTree("toasts.openDirectoryFailed")) + }) + }, + [folderIndex, tFileTree] + ) + const scrollRootRef = useRef(null) const scrollToActiveRef = useRef<() => void>(() => {}) const pendingScrollRef = useRef(false) @@ -1166,6 +1190,7 @@ export function SidebarConversationList({ handleOpenFolderInSystemExplorer } onOpenInTerminal={handleOpenFolderInTerminal} + onOpenInVsCode={handleOpenFolderInVsCode} onSelect={handleSelect} onDoubleClick={handleDoubleClick} onRename={handleRename} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 0f198afc..d0ff890f 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1427,6 +1427,7 @@ "newDirectory": "مجلد", "openIn": "فتح في", "openInTerminal": "فتح في الطرفية", + "openInVsCode": "فتح في VS Code", "actions": { "select": "تحديد", "unselect": "إلغاء التحديد", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index f74a9af6..1e9f9943 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1427,6 +1427,7 @@ "newDirectory": "Verzeichnis", "openIn": "Öffnen in", "openInTerminal": "Im Terminal öffnen", + "openInVsCode": "In VS Code öffnen", "actions": { "select": "Auswählen", "unselect": "Auswahl aufheben", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index bf780b56..ecd5f358 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1427,6 +1427,7 @@ "newDirectory": "Directory", "openIn": "Open in", "openInTerminal": "Open in terminal", + "openInVsCode": "Open in VS Code", "actions": { "select": "Select", "unselect": "Unselect", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 4b58fa97..9f8f31d0 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1427,6 +1427,7 @@ "newDirectory": "Directorio", "openIn": "Abrir en", "openInTerminal": "Abrir en terminal", + "openInVsCode": "Abrir en VS Code", "actions": { "select": "Seleccionar", "unselect": "Deseleccionar", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index 34e4c04a..5affcc95 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1427,6 +1427,7 @@ "newDirectory": "Répertoire", "openIn": "Ouvrir dans", "openInTerminal": "Ouvrir dans le terminal", + "openInVsCode": "Ouvrir dans VS Code", "actions": { "select": "Sélectionner", "unselect": "Désélectionner", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index c1520585..5ae79aee 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1427,6 +1427,7 @@ "newDirectory": "ディレクトリ", "openIn": "で開く", "openInTerminal": "ターミナルで開く", + "openInVsCode": "VS Code で開く", "actions": { "select": "選択", "unselect": "選択解除", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 1bfc9e90..d6f1ed07 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1427,6 +1427,7 @@ "newDirectory": "디렉터리", "openIn": "열기", "openInTerminal": "터미널에서 열기", + "openInVsCode": "VS Code 에서 열기", "actions": { "select": "선택", "unselect": "선택 해제", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 862343f9..328cc93b 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1427,6 +1427,7 @@ "newDirectory": "Diretório", "openIn": "Abrir em", "openInTerminal": "Abrir no terminal", + "openInVsCode": "Abrir no VS Code", "actions": { "select": "Selecionar", "unselect": "Desmarcar", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index f1261494..04be07ee 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1427,6 +1427,7 @@ "newDirectory": "目录", "openIn": "打开于", "openInTerminal": "在终端打开", + "openInVsCode": "在VS Code中打开", "actions": { "select": "选择", "unselect": "取消选择", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index c3392993..ea2fc84e 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1427,6 +1427,7 @@ "newDirectory": "目錄", "openIn": "開啟於", "openInTerminal": "在終端開啟", + "openInVsCode": "在VS Code中開啟", "actions": { "select": "選擇", "unselect": "取消選擇", diff --git a/src/lib/api.ts b/src/lib/api.ts index e13cc11d..1274c9a1 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -735,6 +735,10 @@ export async function createFolderDirectory(path: string): Promise { return getTransport().call("create_folder_directory", { path }) } +export async function openInVsCode(path: string): Promise { + return getTransport().call("open_in_vscode", { path }) +} + export async function cloneRepository( url: string, targetDir: string,