11import * as vscode from "vscode"
2+ import * as path from "path"
23import { arePathsEqual } from "../../utils/path"
34import { Terminal } from "./Terminal"
45import { TerminalProcess } from "./TerminalProcess"
@@ -9,6 +10,7 @@ export class TerminalRegistry {
910 private static terminals : Terminal [ ] = [ ]
1011 private static nextTerminalId = 1
1112 private static disposables : vscode . Disposable [ ] = [ ]
13+ private static terminalTmpDirs : Map < number , string > = new Map ( )
1214 private static isInitialized = false
1315
1416 static initialize ( ) {
@@ -17,6 +19,18 @@ export class TerminalRegistry {
1719 }
1820 this . isInitialized = true
1921
22+ // Register handler for terminal close events to clean up temporary directories
23+ const closeDisposable = vscode . window . onDidCloseTerminal ( ( terminal ) => {
24+ const terminalInfo = this . getTerminalByVSCETerminal ( terminal )
25+ if ( terminalInfo ) {
26+ // Clean up temporary directory if it exists
27+ if ( this . terminalTmpDirs . has ( terminalInfo . id ) ) {
28+ this . zshCleanupTmpDir ( terminalInfo . id )
29+ }
30+ }
31+ } )
32+ this . disposables . push ( closeDisposable )
33+
2034 try {
2135 // onDidStartTerminalShellExecution
2236 const startDisposable = vscode . window . onDidStartTerminalShellExecution ?.(
@@ -141,6 +155,11 @@ export class TerminalRegistry {
141155 env . PROMPT_EOL_MARK = ""
142156 }
143157
158+ // Handle ZDOTDIR for zsh if enabled
159+ if ( Terminal . getTerminalZdotdir ( ) ) {
160+ env . ZDOTDIR = this . zshInitTmpDir ( env )
161+ }
162+
144163 const terminal = vscode . window . createTerminal ( {
145164 cwd,
146165 name : "Shenma" ,
@@ -151,6 +170,13 @@ export class TerminalRegistry {
151170 const cwdString = cwd . toString ( )
152171 const newTerminal = new Terminal ( this . nextTerminalId ++ , terminal , cwdString )
153172
173+ if ( Terminal . getTerminalZdotdir ( ) ) {
174+ this . terminalTmpDirs . set ( newTerminal . id , env . ZDOTDIR )
175+ console . info (
176+ `[TerminalRegistry] Stored temporary directory path for terminal ${ newTerminal . id } : ${ env . ZDOTDIR } ` ,
177+ )
178+ }
179+
154180 this . terminals . push ( newTerminal )
155181 return newTerminal
156182 }
@@ -191,6 +217,8 @@ export class TerminalRegistry {
191217 }
192218
193219 static removeTerminal ( id : number ) {
220+ this . zshCleanupTmpDir ( id )
221+
194222 this . terminals = this . terminals . filter ( ( t ) => t . id !== id )
195223 }
196224
@@ -279,10 +307,156 @@ export class TerminalRegistry {
279307 }
280308
281309 static cleanup ( ) {
310+ // Clean up all temporary directories
311+ this . terminalTmpDirs . forEach ( ( _ , terminalId ) => {
312+ this . zshCleanupTmpDir ( terminalId )
313+ } )
314+ this . terminalTmpDirs . clear ( )
315+
282316 this . disposables . forEach ( ( disposable ) => disposable . dispose ( ) )
283317 this . disposables = [ ]
284318 }
285319
320+ /**
321+ * Gets the path to the shell integration script for a given shell type
322+ * @param shell The shell type
323+ * @returns The path to the shell integration script
324+ */
325+ private static getShellIntegrationPath ( shell : "bash" | "pwsh" | "zsh" | "fish" ) : string {
326+ let filename : string
327+
328+ switch ( shell ) {
329+ case "bash" :
330+ filename = "shellIntegration-bash.sh"
331+ break
332+ case "pwsh" :
333+ filename = "shellIntegration.ps1"
334+ break
335+ case "zsh" :
336+ filename = "shellIntegration-rc.zsh"
337+ break
338+ case "fish" :
339+ filename = "shellIntegration.fish"
340+ break
341+ default :
342+ throw new Error ( `Invalid shell type: ${ shell } ` )
343+ }
344+
345+ // This is the same path used by the CLI command
346+ return path . join (
347+ vscode . env . appRoot ,
348+ "out" ,
349+ "vs" ,
350+ "workbench" ,
351+ "contrib" ,
352+ "terminal" ,
353+ "common" ,
354+ "scripts" ,
355+ filename ,
356+ )
357+ }
358+
359+ /**
360+ * Initialize a temporary directory for ZDOTDIR
361+ * @param env The environment variables object to modify
362+ * @returns The path to the temporary directory
363+ */
364+ private static zshInitTmpDir ( env : Record < string , string > ) : string {
365+ // Create a temporary directory with the sticky bit set for security
366+ const os = require ( "os" )
367+ const path = require ( "path" )
368+ const tmpDir = path . join ( os . tmpdir ( ) , `roo-zdotdir-${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) } ` )
369+ console . info ( `[TerminalRegistry] Creating temporary directory for ZDOTDIR: ${ tmpDir } ` )
370+
371+ // Save original ZDOTDIR as ROO_ZDOTDIR
372+ if ( process . env . ZDOTDIR ) {
373+ env . ROO_ZDOTDIR = process . env . ZDOTDIR
374+ }
375+
376+ // Create the temporary directory
377+ vscode . workspace . fs
378+ . createDirectory ( vscode . Uri . file ( tmpDir ) )
379+ . then ( ( ) => {
380+ console . info ( `[TerminalRegistry] Created temporary directory for ZDOTDIR at ${ tmpDir } ` )
381+
382+ // Create .zshrc in the temporary directory
383+ const zshrcPath = `${ tmpDir } /.zshrc`
384+
385+ // Get the path to the shell integration script
386+ const shellIntegrationPath = this . getShellIntegrationPath ( "zsh" )
387+
388+ const zshrcContent = `
389+ source "${ shellIntegrationPath } "
390+ ZDOTDIR=\${ROO_ZDOTDIR:-$HOME}
391+ unset ROO_ZDOTDIR
392+ [ -f "$ZDOTDIR/.zshenv" ] && source "$ZDOTDIR/.zshenv"
393+ [ -f "$ZDOTDIR/.zprofile" ] && source "$ZDOTDIR/.zprofile"
394+ [ -f "$ZDOTDIR/.zshrc" ] && source "$ZDOTDIR/.zshrc"
395+ [ -f "$ZDOTDIR/.zlogin" ] && source "$ZDOTDIR/.zlogin"
396+ [ "$ZDOTDIR" = "$HOME" ] && unset ZDOTDIR
397+ `
398+ console . info ( `[TerminalRegistry] Creating .zshrc file at ${ zshrcPath } with content:\n${ zshrcContent } ` )
399+ vscode . workspace . fs . writeFile ( vscode . Uri . file ( zshrcPath ) , Buffer . from ( zshrcContent ) ) . then (
400+ // Success handler
401+ ( ) => {
402+ console . info ( `[TerminalRegistry] Successfully created .zshrc file at ${ zshrcPath } ` )
403+ } ,
404+ // Error handler
405+ ( error : Error ) => {
406+ console . error ( `[TerminalRegistry] Error creating .zshrc file at ${ zshrcPath } : ${ error } ` )
407+ } ,
408+ )
409+ } )
410+ . then ( undefined , ( error : Error ) => {
411+ console . error ( `[TerminalRegistry] Error creating temporary directory at ${ tmpDir } : ${ error } ` )
412+ } )
413+
414+ return tmpDir
415+ }
416+
417+ /**
418+ * Clean up a temporary directory used for ZDOTDIR
419+ */
420+ private static zshCleanupTmpDir ( terminalId : number ) : boolean {
421+ const tmpDir = this . terminalTmpDirs . get ( terminalId )
422+ if ( ! tmpDir ) {
423+ return false
424+ }
425+
426+ const logPrefix = `[TerminalRegistry] Cleaning up temporary directory for terminal ${ terminalId } `
427+ console . info ( `${ logPrefix } : ${ tmpDir } ` )
428+
429+ try {
430+ // Use fs to remove the directory and its contents
431+ const fs = require ( "fs" )
432+ const path = require ( "path" )
433+
434+ // Remove .zshrc file
435+ const zshrcPath = path . join ( tmpDir , ".zshrc" )
436+ if ( fs . existsSync ( zshrcPath ) ) {
437+ console . info ( `${ logPrefix } : Removing .zshrc file at ${ zshrcPath } ` )
438+ fs . unlinkSync ( zshrcPath )
439+ }
440+
441+ // Remove the directory
442+ if ( fs . existsSync ( tmpDir ) ) {
443+ console . info ( `${ logPrefix } : Removing directory at ${ tmpDir } ` )
444+ fs . rmdirSync ( tmpDir )
445+ }
446+
447+ // Remove it from the map
448+ this . terminalTmpDirs . delete ( terminalId )
449+ console . info ( `${ logPrefix } : Removed terminal ${ terminalId } from temporary directory map` )
450+
451+ return true
452+ } catch ( error : unknown ) {
453+ console . error (
454+ `[TerminalRegistry] Error cleaning up temporary directory ${ tmpDir } : ${ error instanceof Error ? error . message : String ( error ) } ` ,
455+ )
456+ return false
457+ }
458+ }
459+
286460 /**
287461 * Releases all terminals associated with a task
288462 * @param taskId The task ID
0 commit comments