feat: enhance terminal command handling for macOS (#10362)

- Introduced a helper function to escape strings for AppleScript to ensure proper command execution.
- Updated terminal command definitions to utilize the new escape function, improving compatibility with special characters.
- Adjusted command parameters to use double quotes for directory paths, enhancing consistency and reliability.
This commit is contained in:
beyondkmp 2025-09-25 21:26:04 +08:00 committed by GitHub
parent 05a318225c
commit 499cb52e28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 15 deletions

View File

@ -368,16 +368,27 @@ export const WINDOWS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
}
]
// Helper function to escape strings for AppleScript
const escapeForAppleScript = (str: string): string => {
// In AppleScript strings, backslashes and double quotes need to be escaped
// When passed through osascript -e with single quotes, we need:
// 1. Backslash: \ -> \\
// 2. Double quote: " -> \"
return str
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Then escape double quotes
}
export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
{
id: terminalApps.systemDefault,
name: 'Terminal',
bundleId: 'com.apple.Terminal',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`open -na Terminal && sleep 0.5 && osascript -e 'tell application "Terminal" to activate' -e 'tell application "Terminal" to do script "cd '${directory.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" in front window'`
`open -na Terminal && sleep 0.5 && osascript -e 'tell application "Terminal" to activate' -e 'tell application "Terminal" to do script "${escapeForAppleScript(fullCommand)}" in front window'`
]
})
},
@ -385,11 +396,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.iterm2,
name: 'iTerm2',
bundleId: 'com.googlecode.iterm2',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`open -na iTerm && sleep 0.8 && osascript -e 'on waitUntilRunning()\n repeat 50 times\n tell application "System Events"\n if (exists process "iTerm2") then exit repeat\n end tell\n delay 0.1\n end repeat\nend waitUntilRunning\n\nwaitUntilRunning()\n\ntell application "iTerm2"\n if (count of windows) = 0 then\n create window with default profile\n delay 0.3\n else\n tell current window\n create tab with default profile\n end tell\n delay 0.3\n end if\n tell current session of current window to write text "cd '${directory.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}' && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"\n activate\nend tell'`
`open -na iTerm && sleep 0.8 && osascript -e 'on waitUntilRunning()\n repeat 50 times\n tell application "System Events"\n if (exists process "iTerm2") then exit repeat\n end tell\n delay 0.1\n end repeat\nend waitUntilRunning\n\nwaitUntilRunning()\n\ntell application "iTerm2"\n if (count of windows) = 0 then\n create window with default profile\n delay 0.3\n else\n tell current window\n create tab with default profile\n end tell\n delay 0.3\n end if\n tell current session of current window to write text "${escapeForAppleScript(fullCommand)}"\n activate\nend tell'`
]
})
},
@ -397,11 +408,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.kitty,
name: 'kitty',
bundleId: 'net.kovidgoyal.kitty',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`cd "${directory}" && open -na kitty --args --directory="${directory}" sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "kitty" to activate'`
`cd "${_directory}" && open -na kitty --args --directory="${_directory}" sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "kitty" to activate'`
]
})
},
@ -409,11 +420,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.alacritty,
name: 'Alacritty',
bundleId: 'org.alacritty',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`open -na Alacritty --args --working-directory "${directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Alacritty" to activate'`
`open -na Alacritty --args --working-directory "${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Alacritty" to activate'`
]
})
},
@ -421,11 +432,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.wezterm,
name: 'WezTerm',
bundleId: 'com.github.wez.wezterm',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`open -na WezTerm --args start --new-tab --cwd "${directory}" -- sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "WezTerm" to activate'`
`open -na WezTerm --args start --new-tab --cwd "${_directory}" -- sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "WezTerm" to activate'`
]
})
},
@ -433,11 +444,11 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.ghostty,
name: 'Ghostty',
bundleId: 'com.mitchellh.ghostty',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
`cd "${directory}" && open -na Ghostty --args --working-directory="${directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Ghostty" to activate'`
`cd "${_directory}" && open -na Ghostty --args --working-directory="${_directory}" -e sh -c "${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}; exec \\$SHELL" && sleep 0.5 && osascript -e 'tell application "Ghostty" to activate'`
]
})
},
@ -445,7 +456,7 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
id: terminalApps.tabby,
name: 'Tabby',
bundleId: 'org.tabby',
command: (directory: string, fullCommand: string) => ({
command: (_directory: string, fullCommand: string) => ({
command: 'sh',
args: [
'-c',
@ -453,7 +464,7 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
open -na Tabby --args open && sleep 0.3
else
open -na Tabby --args open && sleep 2
fi && osascript -e 'tell application "Tabby" to activate' -e 'set the clipboard to "cd \\"${directory.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}\\" && clear && ${fullCommand.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"' -e 'tell application "System Events" to tell process "Tabby" to keystroke "v" using {command down}' -e 'tell application "System Events" to key code 36'`
fi && osascript -e 'tell application "Tabby" to activate' -e 'set the clipboard to "${escapeForAppleScript(fullCommand)}"' -e 'tell application "System Events" to tell process "Tabby" to keystroke "v" using {command down}' -e 'tell application "System Events" to key code 36'`
]
})
}

View File

@ -666,7 +666,7 @@ class CodeToolsService {
const command = envPrefix ? `${envPrefix} && ${baseCommand}` : baseCommand
// Combine directory change with the main command to ensure they execute in the same shell session
const fullCommand = `cd '${directory.replace(/'/g, "\\'")}' && clear && ${command}`
const fullCommand = `cd "${directory.replace(/"/g, '\\"')}" && clear && ${command}`
const terminalConfig = await this.getTerminalConfig(options.terminal)
logger.info(`Using terminal: ${terminalConfig.name} (${terminalConfig.id})`)