Skip to content
Open
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
8 changes: 8 additions & 0 deletions plugins/ztool-vsc-recent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.1.2 — 2026-06-29

### 修复
- 修复未安装应用或未配置环境变量时由于无报错而导致的静默失效问题
- 修复 macOS 下 ZTools GUI 环境中环境变量缺失导致无法拉起 VSCode 的问题(重构使用 ZTools 原生 `isMacOs` 判断并优先通过 `open -b` 原生应用唤醒)
- 为 Remote 连接的 Folder URI 在 Windows shell 模式下增加双引号包裹,防御特殊路径空格导致截断的边缘异常


## 0.1.1 — 2026-06-12

### 修复
Expand Down
43 changes: 2 additions & 41 deletions plugins/ztool-vsc-recent/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/ztool-vsc-recent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ztool-vsc-recent",
"version": "0.1.1",
"version": "0.1.2",
"description": "ZTools 插件:VSCode 最近项目快速启动",
"type": "commonjs",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/ztool-vsc-recent/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ztool-vsc-recent",
"title": "VSCode 最近项目",
"description": "快速打开最近的 VSCode 项目、工作区与远程会话",
"version": "0.1.1",
"version": "0.1.2",
"author": "derekxia1988",
"main": "index.html",
"logo": "logo.png",
Expand Down
47 changes: 41 additions & 6 deletions plugins/ztool-vsc-recent/src/launcher/vscode-stable.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,56 @@ const child_process_1 = require("child_process");
* Windows 下 PATH 中的 code 是 code.cmd,所以必须 shell: true。
*/
function openInVSCode(item) {
const args = item.kind === 'remote'
? ['--folder-uri', item.rawPath]
: [quoteIfNeeded(item.rawPath)];
return new Promise(resolve => {
let settled = false;
const settle = (r) => { if (!settled) {
settled = true;
resolve(r);
} };
Comment on lines 10 to 15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了与 TypeScript 源码保持一致,建议在 JS 编译产物中也引入 timeoutId 并在 settle 中清除定时器。

    return new Promise(resolve => {
        let settled = false;
        let timeoutId;
        const settle = (r) => { if (!settled) {
            settled = true;
            if (timeoutId) clearTimeout(timeoutId);
            resolve(r);
        } };

try {
const child = (0, child_process_1.spawn)('code', args, { detached: true, stdio: 'ignore', shell: true });
const isWin = (typeof ztools !== 'undefined' && typeof ztools.isWindows === 'function')
? ztools.isWindows()
: process.platform === 'win32';
const isMac = (typeof ztools !== 'undefined' && typeof ztools.isMacOs === 'function')
? ztools.isMacOs()
: (typeof ztools !== 'undefined' && typeof ztools.isMacOS === 'function')
? ztools.isMacOS()
: process.platform === 'darwin';
let cmd;
let finalArgs;
let useShell;
const env = { ...process.env };
if (isMac) {
// macOS 平台最佳实践:直接无视环境路径,利用 Bundle ID 原生唤醒应用
cmd = 'open';
useShell = false;
finalArgs = item.kind === 'remote'
? ['-b', 'com.microsoft.VSCode', '--args', '--folder-uri', item.rawPath]
: ['-b', 'com.microsoft.VSCode', item.rawPath];
}
else {
// Windows / Linux 依赖 code 命令唤起
cmd = 'code';
useShell = isWin; // Windows 下开启 shell 兼容 code.cmd
finalArgs = item.kind === 'remote'
? ['--folder-uri', useShell ? quoteIfNeeded(item.rawPath) : item.rawPath]
: [useShell ? quoteIfNeeded(item.rawPath) : item.rawPath];
if (!isWin) {
// 为 Linux 等环境尽力补全常规 PATH
const extraPaths = ['/usr/local/bin', '/usr/bin', '/bin'];
env.PATH = env.PATH ? `${extraPaths.join(':')}:${env.PATH}` : extraPaths.join(':');
}
}
const child = (0, child_process_1.spawn)(cmd, finalArgs, { env, detached: true, stdio: 'ignore', shell: useShell });
child.on('error', e => settle({ ok: false, reason: e.message }));
child.on('exit', code => {
if (code !== 0 && code !== null) {
settle({ ok: false, reason: `进程异常退出(code:${code}),请检查是否已安装或环境变量配置错误` });
}
});
child.unref();
// 给 spawn 一个 tick 触发同步错误(如 ENOENT),再 resolve ok
setTimeout(() => settle({ ok: true }), 50);
// 给 spawn 一定时间触发错误或退出的事件,若 100ms 内没报错则假定成功
setTimeout(() => settle({ ok: true }), 100);
Comment on lines 51 to +59

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议在 JS 编译产物中也同步处理进程正常退出(code === 0)的逻辑,立即返回成功并清除定时器,避免不必要的 100ms 等待。

            child.on('error', e => settle({ ok: false, reason: e.message }));
            child.on('exit', (code, signal) => {
                if (code === 0) {
                    settle({ ok: true });
                } else if (code !== null || signal !== null) {
                    settle({ ok: false, reason: "进程异常退出(code:" + code + (signal ? ", signal:" + signal : "") + "),请检查是否已安装或环境变量配置错误" });
                }
            });
            child.unref();
            // 给 spawn 一定时间触发错误或退出的事件,若 100ms 内没报错则假定成功
            timeoutId = setTimeout(() => settle({ ok: true }), 100);

}
catch (e) {
const reason = e instanceof Error ? e.message : String(e);
Expand Down
55 changes: 48 additions & 7 deletions plugins/ztool-vsc-recent/src/launcher/vscode-stable.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,66 @@
import { spawn } from 'child_process';
import type { OpenResult, RecentItem } from '../types';

declare const ztools: any;

/**
* 用 PATH 上的 `code` 命令打开项目。
* Windows 下 PATH 中的 code 是 code.cmd,所以必须 shell: true。
*/
export function openInVSCode(item: RecentItem): Promise<OpenResult> {
const args = item.kind === 'remote'
? ['--folder-uri', item.rawPath]
: [quoteIfNeeded(item.rawPath)];

return new Promise<OpenResult>(resolve => {
let settled = false;
const settle = (r: OpenResult) => { if (!settled) { settled = true; resolve(r); } };
Comment on lines 11 to 13

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

为了避免在进程成功启动或异常退出后定时器继续运行,以及在成功启动时能够立即响应,建议引入 timeoutId 并在 settle 函数中清除定时器。

  return new Promise<OpenResult>(resolve => {
    let settled = false;
    let timeoutId: any;
    const settle = (r: OpenResult) => {
      if (!settled) {
        settled = true;
        if (timeoutId) clearTimeout(timeoutId);
        resolve(r);
      }
    };


try {
const child = spawn('code', args, { detached: true, stdio: 'ignore', shell: true });
const isWin = (typeof ztools !== 'undefined' && typeof ztools.isWindows === 'function')
? ztools.isWindows()
: process.platform === 'win32';
const isMac = (typeof ztools !== 'undefined' && typeof ztools.isMacOs === 'function')
? ztools.isMacOs()
: (typeof ztools !== 'undefined' && typeof ztools.isMacOS === 'function')
? ztools.isMacOS()
: process.platform === 'darwin';

let cmd: string;
let finalArgs: string[];
let useShell: boolean;
const env = { ...process.env };

if (isMac) {
// macOS 平台最佳实践:直接无视环境路径,利用 Bundle ID 原生唤醒应用
cmd = 'open';
useShell = false;
finalArgs = item.kind === 'remote'
? ['-b', 'com.microsoft.VSCode', '--args', '--folder-uri', item.rawPath]
: ['-b', 'com.microsoft.VSCode', item.rawPath];
} else {
// Windows / Linux 依赖 code 命令唤起
cmd = 'code';
useShell = isWin; // Windows 下开启 shell 兼容 code.cmd
finalArgs = item.kind === 'remote'
? ['--folder-uri', useShell ? quoteIfNeeded(item.rawPath) : item.rawPath]
: [useShell ? quoteIfNeeded(item.rawPath) : item.rawPath];

if (!isWin) {
// 为 Linux 等环境尽力补全常规 PATH
const extraPaths = ['/usr/local/bin', '/usr/bin', '/bin'];
env.PATH = env.PATH ? `${extraPaths.join(':')}:${env.PATH}` : extraPaths.join(':');
}
}

const child = spawn(cmd, finalArgs, { env, detached: true, stdio: 'ignore', shell: useShell });

child.on('error', e => settle({ ok: false, reason: e.message }));
child.on('exit', code => {
if (code !== 0 && code !== null) {
settle({ ok: false, reason: `进程异常退出(code:${code}),请检查是否已安装或环境变量配置错误` });
}
});
child.unref();
// 给 spawn 一个 tick 触发同步错误(如 ENOENT),再 resolve ok
setTimeout(() => settle({ ok: true }), 50);

// 给 spawn 一定时间触发错误或退出的事件,若 100ms 内没报错则假定成功
setTimeout(() => settle({ ok: true }), 100);
Comment on lines 54 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议在进程正常退出(code === 0)时立即调用 settle({ ok: true }),这样可以避免在成功启动时无谓地等待 100ms,提升用户体验。同时,在退出时也应处理 signal,并保存 timeoutId 以便在 settle 中清除。

      child.on('error', e => settle({ ok: false, reason: e.message }));
      child.on('exit', (code, signal) => {
        if (code === 0) {
          settle({ ok: true });
        } else if (code !== null || signal !== null) {
          settle({ ok: false, reason: "进程异常退出(code:" + code + (signal ? ", signal:" + signal : "") + "),请检查是否已安装或环境变量配置错误" });
        }
      });
      child.unref();

      // 给 spawn 一定时间触发错误或退出的事件,若 100ms 内没报错则假定成功
      timeoutId = setTimeout(() => settle({ ok: true }), 100);

} catch (e: unknown) {
const reason = e instanceof Error ? e.message : String(e);
settle({ ok: false, reason });
Expand Down