fix: clear ZSH EOL mark to prevent command output interpretation issues

Added a new configuration option 'terminalZshClearEolMark' (default: true) that
sets PROMPT_EOL_MARK='' in the terminal environment. This prevents issues with
command output interpretation when the output ends with special characters like '%'.

Added translations for all supported languages.

Fixes: #2194
Signed-off-by: Eric Wheeler <roo-code@z.ewheeler.org>
This commit is contained in:
Eric Wheeler 2025-04-09 22:12:14 -07:00
parent 211e31b8f6
commit b020e46076
27 changed files with 136 additions and 5 deletions

View file

@ -351,11 +351,23 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
// Initialize out-of-scope variables that need to recieve persistent global state values
this.getState().then(({ soundEnabled, terminalShellIntegrationTimeout, terminalCommandDelay }) => {
setSoundEnabled(soundEnabled ?? false)
Terminal.setShellIntegrationTimeout(terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT)
Terminal.setCommandDelay(terminalCommandDelay ?? 0)
})
this.getState().then(
({
soundEnabled,
terminalShellIntegrationTimeout,
terminalCommandDelay,
terminalZshClearEolMark,
terminalPowershellCounter,
}) => {
setSoundEnabled(soundEnabled ?? false)
Terminal.setShellIntegrationTimeout(
terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
)
Terminal.setCommandDelay(terminalCommandDelay ?? 0)
Terminal.setTerminalZshClearEolMark(terminalZshClearEolMark ?? true)
Terminal.setPowershellCounter(terminalPowershellCounter ?? false)
},
)
// Initialize tts enabled state
this.getState().then(({ ttsEnabled }) => {
@ -1200,6 +1212,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
terminalShellIntegrationTimeout,
terminalCommandDelay,
terminalPowershellCounter,
terminalZshClearEolMark,
fuzzyMatchThreshold,
mcpEnabled,
enableMcpServerCreation,
@ -1269,6 +1282,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
terminalCommandDelay: terminalCommandDelay ?? 0,
terminalPowershellCounter: terminalPowershellCounter ?? false,
terminalZshClearEolMark: terminalZshClearEolMark ?? true,
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
mcpEnabled: mcpEnabled ?? true,
enableMcpServerCreation: enableMcpServerCreation ?? true,
@ -1357,6 +1371,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
stateValues.terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT,
terminalCommandDelay: stateValues.terminalCommandDelay ?? 0,
terminalPowershellCounter: stateValues.terminalPowershellCounter ?? false,
terminalZshClearEolMark: stateValues.terminalZshClearEolMark ?? true,
mode: stateValues.mode ?? defaultModeSlug,
language: stateValues.language ?? formatLanguage(vscode.env.language),
mcpEnabled: stateValues.mcpEnabled ?? true,

View file

@ -750,6 +750,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
Terminal.setPowershellCounter(message.bool)
}
break
case "terminalZshClearEolMark":
await updateGlobalState("terminalZshClearEolMark", message.bool)
await provider.postStateToWebview()
if (message.bool !== undefined) {
Terminal.setTerminalZshClearEolMark(message.bool)
}
break
case "mode":
await provider.handleModeSwitch(message.text as Mode)
break

View file

@ -268,6 +268,7 @@ type GlobalSettings = {
terminalShellIntegrationTimeout?: number | undefined
terminalCommandDelay?: number | undefined
terminalPowershellCounter?: boolean | undefined
terminalZshClearEolMark?: boolean | undefined
rateLimitSeconds?: number | undefined
diffEnabled?: boolean | undefined
fuzzyMatchThreshold?: number | undefined

View file

@ -271,6 +271,7 @@ type GlobalSettings = {
terminalShellIntegrationTimeout?: number | undefined
terminalCommandDelay?: number | undefined
terminalPowershellCounter?: boolean | undefined
terminalZshClearEolMark?: boolean | undefined
rateLimitSeconds?: number | undefined
diffEnabled?: boolean | undefined
fuzzyMatchThreshold?: number | undefined

View file

@ -9,6 +9,7 @@ export class Terminal {
private static shellIntegrationTimeout: number = TERMINAL_SHELL_INTEGRATION_TIMEOUT
private static commandDelay: number = 0
private static powershellCounter: boolean = false
private static terminalZshClearEolMark: boolean = true
public terminal: vscode.Terminal
public busy: boolean
@ -294,6 +295,22 @@ export class Terminal {
return Terminal.powershellCounter
}
/**
* Sets whether to clear the ZSH EOL mark
* @param enabled Whether to clear the ZSH EOL mark
*/
public static setTerminalZshClearEolMark(enabled: boolean): void {
Terminal.terminalZshClearEolMark = enabled
}
/**
* Gets whether to clear the ZSH EOL mark
* @returns Whether the ZSH EOL mark clearing is enabled
*/
public static getTerminalZshClearEolMark(): boolean {
return Terminal.terminalZshClearEolMark
}
public static compressTerminalOutput(input: string, lineLimit: number): string {
return truncateOutput(applyRunLengthEncoding(input), lineLimit)
}

View file

@ -125,6 +125,12 @@ export class TerminalRegistry {
env.PROMPT_COMMAND = `sleep ${Terminal.getCommandDelay() / 1000}`
}
// Clear the ZSH EOL mark to prevent issues with command output interpretation
// when output ends with special characters like '%'
if (Terminal.getTerminalZshClearEolMark()) {
env.PROMPT_EOL_MARK = ""
}
const terminal = vscode.window.createTerminal({
cwd,
name: "Roo Code",

View file

@ -33,6 +33,7 @@ describe("TerminalRegistry", () => {
env: {
PAGER: "cat",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
},
})
})
@ -53,6 +54,7 @@ describe("TerminalRegistry", () => {
PAGER: "cat",
PROMPT_COMMAND: "sleep 0.05",
VTE_VERSION: "0",
PROMPT_EOL_MARK: "",
},
})
} finally {

View file

@ -534,6 +534,7 @@ export const globalSettingsSchema = z.object({
terminalShellIntegrationTimeout: z.number().optional(),
terminalCommandDelay: z.number().optional(),
terminalPowershellCounter: z.boolean().optional(),
terminalZshClearEolMark: z.boolean().optional(),
rateLimitSeconds: z.number().optional(),
diffEnabled: z.boolean().optional(),
@ -606,6 +607,7 @@ const globalSettingsRecord: GlobalSettingsRecord = {
terminalShellIntegrationTimeout: undefined,
terminalCommandDelay: undefined,
terminalPowershellCounter: undefined,
terminalZshClearEolMark: undefined,
rateLimitSeconds: undefined,
diffEnabled: undefined,

View file

@ -155,6 +155,7 @@ export type ExtensionState = Pick<
| "terminalShellIntegrationTimeout"
| "terminalCommandDelay"
| "terminalPowershellCounter"
| "terminalZshClearEolMark"
| "diffEnabled"
| "fuzzyMatchThreshold"
// | "experiments" // Optional in GlobalSettings, required here.

View file

@ -84,6 +84,7 @@ export interface WebviewMessage {
| "terminalShellIntegrationTimeout"
| "terminalCommandDelay"
| "terminalPowershellCounter"
| "terminalZshClearEolMark"
| "mcpEnabled"
| "enableMcpServerCreation"
| "searchCommits"

View file

@ -131,6 +131,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
terminalShellIntegrationTimeout,
terminalCommandDelay,
terminalPowershellCounter,
terminalZshClearEolMark,
writeDelayMs,
showRooIgnoredFiles,
remoteBrowserEnabled,
@ -241,6 +242,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "terminalShellIntegrationTimeout", value: terminalShellIntegrationTimeout })
vscode.postMessage({ type: "terminalCommandDelay", value: terminalCommandDelay })
vscode.postMessage({ type: "terminalPowershellCounter", bool: terminalPowershellCounter })
vscode.postMessage({ type: "terminalZshClearEolMark", bool: terminalZshClearEolMark })
vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
@ -487,6 +489,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
terminalShellIntegrationTimeout={terminalShellIntegrationTimeout}
terminalCommandDelay={terminalCommandDelay}
terminalPowershellCounter={terminalPowershellCounter}
terminalZshClearEolMark={terminalZshClearEolMark}
setCachedStateField={setCachedStateField}
/>
</div>

View file

@ -15,11 +15,13 @@ type TerminalSettingsProps = HTMLAttributes<HTMLDivElement> & {
terminalShellIntegrationTimeout?: number
terminalCommandDelay?: number
terminalPowershellCounter?: boolean
terminalZshClearEolMark?: boolean
setCachedStateField: SetCachedStateField<
| "terminalOutputLineLimit"
| "terminalShellIntegrationTimeout"
| "terminalCommandDelay"
| "terminalPowershellCounter"
| "terminalZshClearEolMark"
>
}
@ -28,6 +30,7 @@ export const TerminalSettings = ({
terminalShellIntegrationTimeout,
terminalCommandDelay,
terminalPowershellCounter,
terminalZshClearEolMark,
setCachedStateField,
className,
...props
@ -116,6 +119,18 @@ export const TerminalSettings = ({
{t("settings:terminal.powershellCounter.description")}
</div>
</div>
<div>
<VSCodeCheckbox
checked={terminalZshClearEolMark ?? true}
onChange={(e: any) => setCachedStateField("terminalZshClearEolMark", e.target.checked)}
data-testid="terminal-zsh-clear-eol-mark-checkbox">
<span className="font-medium">{t("settings:terminal.zshClearEolMark.label")}</span>
</VSCodeCheckbox>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:terminal.zshClearEolMark.description")}
</div>
</div>
</Section>
</div>
)

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Habilita la solució temporal del comptador PowerShell",
"description": "Quan està habilitat, afegeix un comptador a les comandes PowerShell per assegurar l'execució correcta de les comandes. Això ajuda amb els terminals PowerShell que poden tenir problemes amb la captura de sortida."
},
"zshClearEolMark": {
"label": "Neteja la marca EOL de ZSH",
"description": "Quan està habilitat, neteja la marca de final de línia de ZSH establint PROMPT_EOL_MARK=''. Això evita problemes amb la interpretació de la sortida de comandes quan acaba amb caràcters especials com '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "PowerShell-Zähler-Workaround aktivieren",
"description": "Wenn aktiviert, fügt einen Zähler zu PowerShell-Befehlen hinzu, um die korrekte Befehlsausführung sicherzustellen. Dies hilft bei PowerShell-Terminals, die Probleme mit der Ausgabeerfassung haben könnten."
},
"zshClearEolMark": {
"label": "ZSH-Zeilenende-Markierung löschen",
"description": "Wenn aktiviert, wird die ZSH-Zeilenende-Markierung durch Setzen von PROMPT_EOL_MARK='' gelöscht. Dies verhindert Probleme bei der Interpretation der Befehlsausgabe, wenn diese mit Sonderzeichen wie '%' endet."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Enable PowerShell counter workaround",
"description": "When enabled, adds a counter to PowerShell commands to ensure proper command execution. This helps with PowerShell terminals that might have issues with command output capture."
},
"zshClearEolMark": {
"label": "Clear ZSH EOL mark",
"description": "When enabled, clears the ZSH end-of-line mark by setting PROMPT_EOL_MARK=''. This prevents issues with command output interpretation when output ends with special characters like '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Habilitar solución temporal del contador de PowerShell",
"description": "Cuando está habilitado, agrega un contador a los comandos de PowerShell para garantizar la ejecución correcta de los comandos. Esto ayuda con las terminales PowerShell que pueden tener problemas con la captura de salida de comandos."
},
"zshClearEolMark": {
"label": "Limpiar marca de fin de línea de ZSH",
"description": "Cuando está habilitado, limpia la marca de fin de línea de ZSH estableciendo PROMPT_EOL_MARK=''. Esto evita problemas con la interpretación de la salida de comandos cuando termina con caracteres especiales como '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Activer le contournement du compteur PowerShell",
"description": "Lorsqu'activé, ajoute un compteur aux commandes PowerShell pour assurer une exécution correcte des commandes. Cela aide avec les terminaux PowerShell qui peuvent avoir des problèmes de capture de sortie."
},
"zshClearEolMark": {
"label": "Effacer la marque de fin de ligne ZSH",
"description": "Lorsqu'activé, efface la marque de fin de ligne ZSH en définissant PROMPT_EOL_MARK=''. Cela évite les problèmes d'interprétation de la sortie des commandes lorsqu'elle se termine par des caractères spéciaux comme '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "PowerShell काउंटर समाधान सक्षम करें",
"description": "सक्षम होने पर, कमांड के सही निष्पादन को सुनिश्चित करने के लिए PowerShell कमांड में एक काउंटर जोड़ता है। यह उन PowerShell टर्मिनलों के साथ मदद करता है जिनमें आउटपुट कैप्चर करने में समस्याएं हो सकती हैं।"
},
"zshClearEolMark": {
"label": "ZSH EOL मार्क साफ़ करें",
"description": "सक्षम होने पर, PROMPT_EOL_MARK='' सेट करके ZSH लाइन-समाप्ति मार्क को साफ़ करता है। यह कमांड आउटपुट की व्याख्या में समस्याओं को रोकता है जब आउटपुट '%' जैसे विशेष वर्णों के साथ समाप्त होता है।"
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Abilita soluzione temporanea contatore PowerShell",
"description": "Quando abilitato, aggiunge un contatore ai comandi PowerShell per garantire la corretta esecuzione dei comandi. Questo aiuta con i terminali PowerShell che potrebbero avere problemi con la cattura dell'output."
},
"zshClearEolMark": {
"label": "Cancella marcatore fine riga ZSH",
"description": "Quando abilitato, cancella il marcatore di fine riga ZSH impostando PROMPT_EOL_MARK=''. Questo previene problemi con l'interpretazione dell'output dei comandi quando termina con caratteri speciali come '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "PowerShellカウンター回避策を有効化",
"description": "有効にすると、PowerShellコマンドにカウンターを追加して、コマンドの正しい実行を確保します。これは出力のキャプチャに問題がある可能性のあるPowerShellターミナルで役立ちます。"
},
"zshClearEolMark": {
"label": "ZSH行末マークをクリア",
"description": "有効にすると、PROMPT_EOL_MARK=''を設定してZSHの行末マークをクリアします。これにより、'%'などの特殊文字で終わるコマンド出力の解釈に関する問題を防ぎます。"
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "PowerShell 카운터 해결 방법 활성화",
"description": "활성화하면 PowerShell 명령에 카운터를 추가하여 명령이 올바르게 실행되도록 합니다. 이는 명령 출력 캡처에 문제가 있을 수 있는 PowerShell 터미널에서 도움이 됩니다."
},
"zshClearEolMark": {
"label": "ZSH 줄 끝 표시 지우기",
"description": "활성화하면 PROMPT_EOL_MARK=''를 설정하여 ZSH 줄 끝 표시를 지웁니다. 이는 '%'와 같은 특수 문자로 끝나는 명령 출력 해석의 문제를 방지합니다."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Włącz obejście licznika PowerShell",
"description": "Po włączeniu dodaje licznik do poleceń PowerShell, aby zapewnić prawidłowe wykonanie poleceń. Pomaga to w terminalach PowerShell, które mogą mieć problemy z przechwytywaniem wyjścia."
},
"zshClearEolMark": {
"label": "Wyczyść znacznik końca linii ZSH",
"description": "Po włączeniu czyści znacznik końca linii ZSH poprzez ustawienie PROMPT_EOL_MARK=''. Zapobiega to problemom z interpretacją wyjścia poleceń, gdy kończy się ono znakami specjalnymi jak '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Ativar solução alternativa do contador PowerShell",
"description": "Quando ativado, adiciona um contador aos comandos PowerShell para garantir a execução correta dos comandos. Isso ajuda com terminais PowerShell que podem ter problemas com a captura de saída."
},
"zshClearEolMark": {
"label": "Limpar marca de fim de linha do ZSH",
"description": "Quando ativado, limpa a marca de fim de linha do ZSH definindo PROMPT_EOL_MARK=''. Isso evita problemas com a interpretação da saída de comandos quando termina com caracteres especiais como '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "PowerShell sayaç geçici çözümünü etkinleştir",
"description": "Etkinleştirildiğinde, komutların doğru şekilde yürütülmesini sağlamak için PowerShell komutlarına bir sayaç ekler. Bu, çıktı yakalama sorunları yaşayabilecek PowerShell terminallerinde yardımcı olur."
},
"zshClearEolMark": {
"label": "ZSH satır sonu işaretini temizle",
"description": "Etkinleştirildiğinde, PROMPT_EOL_MARK='' ayarlanarak ZSH satır sonu işaretini temizler. Bu, '%' gibi özel karakterlerle biten komut çıktılarının yorumlanmasında sorun yaşanmasını önler."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "Bật giải pháp bộ đếm PowerShell",
"description": "Khi được bật, thêm một bộ đếm vào các lệnh PowerShell để đảm bảo thực thi lệnh chính xác. Điều này giúp ích với các terminal PowerShell có thể gặp vấn đề về ghi lại đầu ra."
},
"zshClearEolMark": {
"label": "Xóa dấu cuối dòng ZSH",
"description": "Khi được bật, xóa dấu cuối dòng ZSH bằng cách đặt PROMPT_EOL_MARK=''. Điều này ngăn chặn các vấn đề về diễn giải đầu ra lệnh khi kết thúc bằng các ký tự đặc biệt như '%'."
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "启用 PowerShell 计数器解决方案",
"description": "启用后,会在 PowerShell 命令中添加计数器以确保命令正确执行。这有助于解决可能存在输出捕获问题的 PowerShell 终端。"
},
"zshClearEolMark": {
"label": "清除 ZSH 行尾标记",
"description": "启用后,通过设置 PROMPT_EOL_MARK='' 清除 ZSH 行尾标记。这可以防止命令输出以特殊字符(如 '%')结尾时的解析问题。"
}
},
"advanced": {

View file

@ -309,6 +309,10 @@
"powershellCounter": {
"label": "啟用 PowerShell 計數器解決方案",
"description": "啟用後,會在 PowerShell 命令中加入計數器以確保命令正確執行。這有助於解決可能存在輸出擷取問題的 PowerShell 終端機。"
},
"zshClearEolMark": {
"label": "清除 ZSH 行尾標記",
"description": "啟用後,透過設定 PROMPT_EOL_MARK='' 清除 ZSH 行尾標記。這可以防止命令輸出以特殊字元(如 '%')結尾時的解析問題。"
}
},
"advanced": {