新增quickcommand.runCode,quickcommand.runInTerminal支持warp

This commit is contained in:
fofolee 2025-01-13 01:15:42 +08:00
parent 4053f7a9c2
commit 0a8c24374a
9 changed files with 366 additions and 105 deletions

View File

@ -0,0 +1,150 @@
const fs = require("fs");
const path = require("path");
// 终端配置
const DEFAULT_TERMINALS = {
windows: ["wt", "cmd"],
macos: ["warp", "iterm", "terminal"],
};
// Windows 终端命令生成器
const getWindowsTerminalCommand = (cmdline, options = {}) => {
const { dir, terminal = "wt" } = options;
const appPath = path.join(
window.utools.getPath("home"),
"/AppData/Local/Microsoft/WindowsApps/"
);
const terminalCommands = {
wt: () => {
if (
fs.existsSync(appPath) &&
fs.readdirSync(appPath).includes("wt.exe")
) {
const escapedCmd = cmdline.replace(/"/g, `\\"`);
const cd = dir ? `-d "${dir.replace(/\\/g, "/")}"` : "";
return `${appPath}wt.exe ${cd} cmd /k "${escapedCmd}"`;
}
return null;
},
cmd: () => {
const escapedCmd = cmdline.replace(/"/g, `^"`);
const cd = dir ? `cd /d "${dir.replace(/\\/g, "/")}" &&` : "";
return `${cd} start "" cmd /k "${escapedCmd}"`;
},
};
// 按优先级尝试不同终端
const terminalPriority =
terminal === "default"
? DEFAULT_TERMINALS.windows
: [terminal, ...DEFAULT_TERMINALS.windows];
for (const term of terminalPriority) {
const command = terminalCommands[term]?.();
if (command) return command;
}
// 如果都失败了,返回默认的 cmd 命令
return terminalCommands.cmd();
};
// macOS 终端命令生成器
const getMacTerminalCommand = (cmdline, options = {}) => {
const { dir, terminal = "warp" } = options;
const terminalCommands = {
warp: () => {
if (fs.existsSync("/Applications/Warp.app")) {
const workingDir = dir || process.cwd();
// 创建临时的 launch configuration
const configName = `temp_${Date.now()}`;
const configPath = path.join(
window.utools.getPath("home"),
".warp/launch_configurations",
`${configName}.yml`
);
// 确保目录存在
const configDir = path.dirname(configPath);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
// 创建配置文件,对于 Warp命令不需要转义因为是通过 YAML 配置传递
const config = `---
name: ${configName}
windows:
- tabs:
- layout:
cwd: "${workingDir}"
commands:
- exec: ${cmdline}`;
fs.writeFileSync(configPath, config);
// 使用配置文件启动 Warp
return `open "warp://launch/${configName}" && sleep 0.5 && rm "${configPath}"`;
}
return null;
},
iterm: () => {
const escapedCmd = cmdline.replace(/"/g, `\\"`);
const cd = dir ? `cd ${dir.replace(/ /g, "\\\\ ")} &&` : "";
if (fs.existsSync("/Applications/iTerm.app")) {
return `osascript -e 'tell application "iTerm"
if application "iTerm" is running then
create window with default profile
end if
tell current session of first window to write text "clear && ${cd} ${escapedCmd}"
activate
end tell'`;
}
return null;
},
terminal: () => {
const escapedCmd = cmdline.replace(/"/g, `\\"`);
const cd = dir ? `cd ${dir.replace(/ /g, "\\\\ ")} &&` : "";
return `osascript -e 'tell application "Terminal"
if application "Terminal" is running then
do script "clear && ${cd} ${escapedCmd}"
else
do script "clear && ${cd} ${escapedCmd}" in window 1
end if
activate
end tell'`;
},
};
// 按优先级尝试不同终端
const terminalPriority =
terminal === "default"
? DEFAULT_TERMINALS.macos
: [terminal, ...DEFAULT_TERMINALS.macos];
for (const term of terminalPriority) {
const command = terminalCommands[term]?.();
if (command) return command;
}
// 如果都失败了,返回默认终端命令
return terminalCommands.terminal();
};
// 主函数
const createTerminalCommand = (cmdline, options = {}) => {
const { windows = "default", macos = "default" } = options;
if (window.utools.isWindows()) {
return getWindowsTerminalCommand(cmdline, {
...options,
terminal: windows,
});
} else if (window.utools.isMacOs()) {
return getMacTerminalCommand(cmdline, { ...options, terminal: macos });
}
throw new Error("Unsupported operating system");
};
module.exports = createTerminalCommand;

View File

@ -1,44 +0,0 @@
const fs = require("fs");
const path = require("path");
const getCommandToLaunchTerminal = (cmdline, dir) => {
let cd, command;
if (window.utools.isWindows()) {
let appPath = path.join(
window.utools.getPath("home"),
"/AppData/Local/Microsoft/WindowsApps/"
);
// 直接 existsSync wt.exe 无效
if (fs.existsSync(appPath) && fs.readdirSync(appPath).includes("wt.exe")) {
cmdline = cmdline.replace(/"/g, `\\"`);
cd = dir ? `-d "${dir.replace(/\\/g, "/")}"` : "";
command = `${appPath}wt.exe ${cd} cmd /k "${cmdline}"`;
} else {
cmdline = cmdline.replace(/"/g, `^"`);
cd = dir ? `cd /d "${dir.replace(/\\/g, "/")}" &&` : "";
command = `${cd} start "" cmd /k "${cmdline}"`;
}
} else if (window.utools.isMacOs()) {
cmdline = cmdline.replace(/"/g, `\\"`);
cd = dir ? `cd ${dir.replace(/ /g, "\\\\ ")} &&` : "";
command = fs.existsSync("/Applications/iTerm.app")
? `osascript -e 'tell application "iTerm"
if application "iTerm" is running then
create window with default profile
end if
tell current session of first window to write text "clear && ${cd} ${cmdline}"
activate
end tell'`
: `osascript -e 'tell application "Terminal"
if application "Terminal" is running then
do script "clear && ${cd} ${cmdline}"
else
do script "clear && ${cd} ${cmdline}" in window 1
end if
activate
end tell'`;
}
return command;
};
module.exports = getCommandToLaunchTerminal;

View File

@ -10,7 +10,7 @@ const systemDialog = require("./systemDialog");
const { getQuickcommandTempFile } = require("./getQuickcommandFile");
const getCommandToLaunchTerminal = require("./getCommandToLaunchTerminal");
const createTerminalCommand = require("./createTerminalCommand");
const ctlKey = window.utools.isMacOs() ? "command" : "control";
@ -302,8 +302,10 @@ window.runPythonCommand = (py) => {
if (process.platform !== "linux") {
// 在终端中执行
quickcommand.runInTerminal = function (cmdline, dir) {
let command = getCommandToLaunchTerminal(cmdline, dir);
quickcommand.runInTerminal = function (cmdline, options) {
// 兼容老版本接口, 老版本第二个参数是dir
if (typeof options === "string") options = { dir: options };
let command = createTerminalCommand(cmdline, options);
child_process.exec(command);
};
// 系统级弹窗

View File

@ -18,7 +18,7 @@ const md5 = (input) => {
window.lodashM = require("./lib/lodashMini");
const getCommandToLaunchTerminal = require("./lib/getCommandToLaunchTerminal");
const createTerminalCommand = require("./lib/createTerminalCommand");
const shortCodes = require("./lib/shortCodes");
const { pluginInfo, getUtoolsPlugins } = require("./lib/getUtoolsPlugins");
const {
@ -42,7 +42,8 @@ window.getuToolsLite = require("./lib/utoolsLite");
window.quickcommand = require("./lib/quickcommand");
window.quickcomposer = require("./lib/quickcomposer");
window.showUb = require("./lib/showDocs");
window.getQuickcommandTempFile = require("./lib/getQuickcommandFile").getQuickcommandTempFile;
window.getQuickcommandTempFile =
require("./lib/getQuickcommandFile").getQuickcommandTempFile;
window.getSharedQcById = async (id) => {
const url = "https://qc.qaz.ink/home/quick/script/getScript";
@ -211,66 +212,95 @@ window.runCodeInSandbox = (code, callback, addVars = {}) => {
}
};
window.runCodeFile = (cmd, option, terminal, callback, realTime = true) => {
let { bin, argv, ext, charset, scptarg, envPath, alias } = option;
let script = getQuickcommandTempFile(ext, "quickcommandTempScript");
// 批处理和 powershell 默认编码为 GBK, 解决批处理的换行问题
if (charset.scriptCode)
cmd = iconv.encode(cmd.replace(/\n/g, "\r\n"), charset.scriptCode);
fs.writeFileSync(script, cmd);
// var argvs = [script]
// if (argv) {
// argvs = argv.split(' ')
// argvs.push(script);
// }
let child, cmdline;
if (bin.slice(-7) == "csc.exe") {
cmdline = `${bin} ${argv} /out:"${
script.slice(0, -2) + "exe"
}" "${script}" && "${script.slice(0, -2) + "exe"}" ${scptarg}`;
} else if (bin == "gcc") {
var suffix = utools.isWindows() ? ".exe" : "";
cmdline = `${bin} ${argv} "${script.slice(0, -2)}" "${script}" && "${
script.slice(0, -2) + suffix
}" ${scptarg}`;
} else if (utools.isWindows() && bin == "bash") {
cmdline = `${bin} ${argv} "${script
.replace(/\\/g, "/")
.replace(/C:/i, "/mnt/c")}" ${scptarg}`;
} else {
cmdline = `${bin} ${argv} "${script}" ${scptarg}`;
// 构建命令行字符串的工具函数
const buildCommandLine = (bin, argv, script, scptarg) => {
if (bin.slice(-7) === "csc.exe") {
const outFile = script.slice(0, -2) + "exe";
return `${bin} ${argv} /out:"${outFile}" "${script}" && "${outFile}" ${scptarg}`;
}
let processEnv = window.lodashM.cloneDeep(process.env);
if (bin === "gcc") {
const suffix = utools.isWindows() ? ".exe" : "";
const outFile = script.slice(0, -2) + suffix;
return `${bin} ${argv} "${script.slice(
0,
-2
)}" "${script}" && "${outFile}" ${scptarg}`;
}
if (utools.isWindows() && bin === "bash") {
const wslPath = script.replace(/\\/g, "/").replace(/C:/i, "/mnt/c");
return `${bin} ${argv} "${wslPath}" ${scptarg}`;
}
return `${bin} ${argv} "${script}" ${scptarg}`;
};
// 处理进程输出的工具函数
const handleProcessOutput = (child, charset, callback, realTime) => {
const chunks = [];
const errChunks = [];
child.stdout.on("data", (chunk) => {
const decodedChunk = charset.outputCode
? iconv.decode(chunk, charset.outputCode)
: chunk;
realTime
? callback(decodedChunk.toString(), null)
: chunks.push(decodedChunk);
});
child.stderr.on("data", (errChunk) => {
const decodedChunk = charset.outputCode
? iconv.decode(errChunk, charset.outputCode)
: errChunk;
realTime
? callback(null, decodedChunk.toString())
: errChunks.push(decodedChunk);
});
if (!realTime) {
child.on("close", () => {
callback(chunks.join(""), errChunks.join(""));
});
}
};
window.runCodeFile = (
cmd,
option,
terminalOptions,
callback,
realTime = true
) => {
const { bin, argv, ext, charset, scptarg, envPath, alias } = option;
const script = getQuickcommandTempFile(ext, "quickcommandTempScript");
// 处理编码和换行
const processedCmd = charset.scriptCode
? iconv.encode(cmd.replace(/\n/g, "\r\n"), charset.scriptCode)
: cmd;
fs.writeFileSync(script, processedCmd);
// 构建命令行
let cmdline = buildCommandLine(bin, argv, script, scptarg);
// 处理环境变量
const processEnv = window.lodashM.cloneDeep(process.env);
if (envPath) processEnv.PATH = envPath;
if (alias) cmdline = alias + "\n" + cmdline;
// 在终端中输出
if (terminal) cmdline = getCommandToLaunchTerminal(cmdline);
child = child_process.spawn(cmdline, {
if (alias) cmdline = `${alias}\n${cmdline}`;
if (!!terminalOptions) {
cmdline = createTerminalCommand(cmdline, terminalOptions);
}
// 创建子进程
const child = child_process.spawn(cmdline, {
encoding: "buffer",
shell: true,
env: processEnv,
});
let chunks = [],
err_chunks = [];
console.log("Running: " + cmdline);
child.stdout.on("data", (chunk) => {
if (charset.outputCode) chunk = iconv.decode(chunk, charset.outputCode);
realTime ? callback(chunk.toString(), null) : chunks.push(chunk);
});
child.stderr.on("data", (err_chunk) => {
if (charset.outputCode)
err_chunk = iconv.decode(err_chunk, charset.outputCode);
realTime
? callback(null, err_chunk.toString())
: err_chunks.push(err_chunk);
});
if (!realTime) {
child.on("close", (code) => {
let stdout = chunks.join("");
let stderr = err_chunks.join("");
callback(stdout, stderr);
});
}
handleProcessOutput(child, charset, callback, realTime);
return child;
};

View File

@ -159,7 +159,7 @@ export default {
this.childProcess = window.runCodeFile(
currentCommand.cmd,
this.getCommandOpt(currentCommand),
currentCommand.output === "terminal",
currentCommand.output === "terminal" ? {} : false,
(stdout, stderr) => this.handleResult(stdout, stderr, resultOpts)
);
this.listenStopSign();

View File

@ -36,6 +36,7 @@ import ButtonBox from "components/quickcommandUI/ButtonBox";
import ConfirmBox from "components/quickcommandUI/ConfirmBox";
import TextArea from "components/quickcommandUI/TextArea";
import SelectList from "components/quickcommandUI/SelectList";
import programs from "js/options/programs";
export default {
components: {
@ -256,8 +257,26 @@ export default {
});
},
};
// quickcommandUIquickcommand
Object.assign(window.quickcommand, quickcommandUI);
//
window.quickcommand.userData = this.$root.utools.userData;
// runCodepreloadprogramssrc-_-
quickcommand.runCode = (code, program, runInTerminal = false) => {
return new Promise((reslove, reject) => {
window.runCodeFile(
code,
{ ...programs[program], charset: {}, scptarg: "" },
runInTerminal,
(result, err) => (err ? reject(err) : reslove(result))
);
false;
});
};
// quickcommand
Object.freeze(quickcommand);
},
methods: {

View File

@ -17,7 +17,7 @@ import { imageCommands } from "./imageCommands";
import { windowsCommands } from "./windowsCommands";
import { statusCommands } from "./statusCommands";
import { macosCommands } from "./macosCommands";
import { scriptCommands } from "./scriptCommands";
let commands = [
fileCommands,
networkCommands,
@ -30,6 +30,7 @@ let commands = [
dataCommands,
codingCommands,
controlCommands,
scriptCommands,
uiCommands,
simulateCommands,
mathCommands,

View File

@ -0,0 +1,53 @@
export const scriptCommands = {
label: "编程相关",
icon: "integration_instructions",
commands: [
{
value: "",
label: "赋值",
icon: "script",
outputVariable: "value",
saveOutput: true,
config: [
{
label: "值或表达式",
component: "VariableInput",
width: 12,
},
],
},
{
value: "(function(code){new Function(code)()})",
label: "注入JS脚本",
icon: "script",
config: [
{
label: "JS脚本",
component: "CodeEditor",
width: 12,
},
],
},
{
value: "quickcommand.runAppleScript",
label: "执行 AppleScript",
icon: "script",
outputVariable: "result",
saveOutput: true,
config: [
{
label: "脚本",
component: "CodeEditor",
width: 12,
},
],
},
{
value: "quickcommand.runCsharp",
label: "执行C#脚本",
icon: "script",
outputVariable: "result",
saveOutput: true,
},
],
};

View File

@ -378,11 +378,23 @@ interface quickcommandApi {
* Linux
*
* @param command
* @param options
* @param options.dir
* @param options.windows wt
* @param options.macos warp
*
* ```js
* quickcommand.runInTerminal(`whoami`)
* ```
*/
runInTerminal(command: string);
runInTerminal(
command: string,
options?: {
dir?: string;
windows?: "wt" | "cmd";
macos?: "warp" | "iterm" | "terminal";
}
);
/**
* utools.onPluginEnter `code` `type` `payload`
@ -597,6 +609,44 @@ interface quickcommandApi {
defaultText?: string,
title?: string
): Promise<string | null>;
/**
*
* @param code
* @param program
* @param runInTerminal
* @param runInTerminal.dir
* @param runInTerminal.windows windows使用的终端wt
* @param runInTerminal.macos macos使用的终端warp
*
*
* shell, applescript, cmd, python, powershell, javascript, ruby, php, lua, perl, csharp, c
*
* ```js
* quickcommand.runCode("print('Hello, World!');", "python");
* ```
*/
runCode(
code: string,
program:
| "shell"
| "applescript"
| "cmd"
| "python"
| "powershell"
| "javascript"
| "ruby"
| "php"
| "lua"
| "perl"
| "csharp"
| "c",
runInTerminal?: {
dir?: string;
windows?: "wt" | "cmd";
macos?: "warp" | "iterm" | "terminal";
}
): Promise<string>;
}
declare var quickcommand: quickcommandApi;