Repository: microsoft/vscode (TypeScript)
Date: 2022-05-03 14:44:19+00:00
Task type: Bug Fix

Issue:
setting of ZDOTDIR when shell integration is enabled
>
- Shell integration doesn't work if `ZDOTDIR` is set in the global `zshenv`.
- Shell integration breaks shell if `~/.zshenv` sets `ZDOTDIR`.


_Originally posted by @romkatv in https://github.com/microsoft/vscode/issues/142004#issuecomment-1073263418_


Issue discussion:

Solution patch:
diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js
--- a/build/gulpfile.reh.js
+++ b/build/gulpfile.reh.js
@@ -80,7 +80,8 @@ const serverResources = [
 	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh',
 	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh',
 	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh',
-	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh',
+	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh',
+	'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh',
 
 	'!**/test/**'
 ];
diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts
--- a/src/vs/platform/terminal/node/terminalEnvironment.ts
+++ b/src/vs/platform/terminal/node/terminalEnvironment.ts
@@ -193,7 +193,7 @@ export function getShellIntegrationInjection(
 			envMixin['ZDOTDIR'] = zdotdir;
 			const filesToCopy: IShellIntegrationConfigInjection['filesToCopy'] = [];
 			filesToCopy.push({
-				source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh'),
+				source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh'),
 				dest: path.join(zdotdir, '.zshrc')
 			});
 			filesToCopy.push({
@@ -204,6 +204,10 @@ export function getShellIntegrationInjection(
 				source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh'),
 				dest: path.join(zdotdir, '.zshenv')
 			});
+			filesToCopy.push({
+				source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh'),
+				dest: path.join(zdotdir, '.zlogin')
+			});
 			if (!options.showWelcome) {
 				envMixin['VSCODE_SHELL_HIDE_WELCOME'] = '1';
 			}
diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts
--- a/src/vs/platform/terminal/node/terminalProcess.ts
+++ b/src/vs/platform/terminal/node/terminalProcess.ts
@@ -220,7 +220,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
 		if (this.shellLaunchConfig.env?.['_ZDOTDIR'] === '1') {
 			const zdotdir = path.join(tmpdir(), 'vscode-zsh');
 			await fs.mkdir(zdotdir, { recursive: true });
-			const source = path.join(path.dirname(FileAccess.asFileUri('', require).fsPath), 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh');
+			const source = path.join(path.dirname(FileAccess.asFileUri('', require).fsPath), 'out/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh');
 			await fs.copyFile(source, path.join(zdotdir, '.zshrc'));
 			this._ptyOptions.env = this._ptyOptions.env || {};
 			this._ptyOptions.env['ZDOTDIR'] = zdotdir;
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh
@@ -2,7 +2,13 @@
 #   Copyright (c) Microsoft Corporation. All rights reserved.
 #   Licensed under the MIT License. See License.txt in the project root for license information.
 # ---------------------------------------------------------------------------------------------
-
+VSCODE_ZDOTDIR=$ZDOTDIR
 if [[ -f ~/.zshenv ]]; then
 	. ~/.zshenv
 fi
+if [[ "$ZDOTDIR" != "$VSCODE_ZDOTDIR" ]]; then
+	USER_ZDOTDIR=$ZDOTDIR
+	ZDOTDIR=$VSCODE_ZDOTDIR
+else
+	USER_ZDOTDIR=$HOME
+fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh
new file mode 100644
--- /dev/null
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh
@@ -0,0 +1,9 @@
+# ---------------------------------------------------------------------------------------------
+#   Copyright (c) Microsoft Corporation. All rights reserved.
+#   Licensed under the MIT License. See License.txt in the project root for license information.
+# ---------------------------------------------------------------------------------------------
+if [[ $options[norcs] = off && -o "login" &&  -f $USER_ZDOTDIR/.zlogin ]]; then
+	VSCODE_ZDOTDIR=$ZDOTDIR
+	ZDOTDIR=$USER_ZDOTDIR
+	. $USER_ZDOTDIR/.zlogin
+fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh
@@ -2,7 +2,9 @@
 #   Copyright (c) Microsoft Corporation. All rights reserved.
 #   Licensed under the MIT License. See License.txt in the project root for license information.
 # ---------------------------------------------------------------------------------------------
-
-if [[ $options[norcs] = off && -o "login" &&  -f ~/.zprofile ]]; then
-	. ~/.zprofile
+if [[ $options[norcs] = off && -o "login" &&  -f $USER_ZDOTDIR/.zprofile ]]; then
+	VSCODE_ZDOTDIR=$ZDOTDIR
+	ZDOTDIR=$USER_ZDOTDIR
+	. $USER_ZDOTDIR/.zprofile
+	ZDOTDIR=$VSCODE_ZDOTDIR
 fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
similarity index 90%
rename from src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh
rename to src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
@@ -4,17 +4,15 @@
 # ---------------------------------------------------------------------------------------------
 builtin autoload -Uz add-zsh-hook
 
-# Now that the init script is running, unset ZDOTDIR to ensure ~/.zlogout runs as expected as well
-# as prevent problems that may occur if the user's init scripts depend on ZDOTDIR not being set.
-builtin unset ZDOTDIR
-
 # This variable allows the shell to both detect that VS Code's shell integration is enabled as well
 # as disable it by unsetting the variable.
 VSCODE_SHELL_INTEGRATION=1
 
-
-if [[ $options[norcs] = off  && -f ~/.zshrc ]]; then
-	. ~/.zshrc
+if [[ $options[norcs] = off  && -f $USER_ZDOTDIR/.zshrc ]]; then
+	VSCODE_ZDOTDIR=$ZDOTDIR
+	ZDOTDIR=$USER_ZDOTDIR
+	. $USER_ZDOTDIR/.zshrc
+	ZDOTDIR=$VSCODE_ZDOTDIR
 fi
 
 # Shell integration was disabled by the shell, exit without warning assuming either the shell has


Test patch:
diff --git a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts
--- a/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts
+++ b/src/vs/platform/terminal/test/node/terminalEnvironment.test.ts
@@ -89,22 +89,25 @@ suite('platform - terminalEnvironment', () => {
 			suite('zsh', () => {
 				suite('should override args', () => {
 					const expectedDir = /.+\/vscode-zsh/;
-					const expectedDests = [/.+\/vscode-zsh\/.zshrc/, /.+\/vscode-zsh\/.zprofile/, /.+\/vscode-zsh\/.zshenv/];
+					const expectedDests = [/.+\/vscode-zsh\/.zshrc/, /.+\/vscode-zsh\/.zprofile/, /.+\/vscode-zsh\/.zshenv/, /.+\/vscode-zsh\/.zlogin/];
 					const expectedSources = [
-						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration.zsh/,
+						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration-rc.zsh/,
 						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration-profile.zsh/,
-						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration-env.zsh/
+						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration-env.zsh/,
+						/.+\/out\/vs\/workbench\/contrib\/terminal\/browser\/media\/shellIntegration-login.zsh/
 					];
 					function assertIsEnabled(result: IShellIntegrationConfigInjection) {
 						strictEqual(Object.keys(result.envMixin!).length, 1);
 						ok(result.envMixin!['ZDOTDIR']?.match(expectedDir));
-						strictEqual(result.filesToCopy?.length, 3);
+						strictEqual(result.filesToCopy?.length, 4);
 						ok(result.filesToCopy[0].dest.match(expectedDests[0]));
 						ok(result.filesToCopy[1].dest.match(expectedDests[1]));
 						ok(result.filesToCopy[2].dest.match(expectedDests[2]));
+						ok(result.filesToCopy[3].dest.match(expectedDests[3]));
 						ok(result.filesToCopy[0].source.match(expectedSources[0]));
 						ok(result.filesToCopy[1].source.match(expectedSources[1]));
 						ok(result.filesToCopy[2].source.match(expectedSources[2]));
+						ok(result.filesToCopy[3].source.match(expectedSources[3]));
 					}
 					test('when undefined, []', () => {
 						const result1 = getShellIntegrationInjection({ executable: 'zsh', args: [] }, enabledProcessOptions, logService);