direnv: Allow disabling env integration entirely (#43764)

Ian Chamberlain created

Relates to #35759, but maybe doesn't entirely fix it? I think it will
improve the situation, at least.
Also provides a workaround for the issue described in
https://github.com/zed-industries/zed/issues/40094#issuecomment-3559808526
for users of WSL + `nix-direnv`.

Rationale: there are cases where automatic direnv integration is not
always desirable, but Zed currently has no way of opting out of this
integration besides `direnv revoke` (which is often not desirable).

This PR provides such an opt-out for users who run into problems with
the existing direnv integration methods. Some reasons why disabling
might be useful:
- Security concerns about auto-loading `.envrc` (arguably, `direnv
revoke` should cover this most of the time)
- As in #35759, for users who use different shells/envs for
interactive/non-interactive cases and want to manually control the
environment Zed uses
- As in #40094, to workaround OS limits on environment variable /
command-line parameter size


Release Notes:

- Added the ability to disable direnv integration entirely

Change summary

assets/settings/default.json                    | 2 ++
crates/project/src/environment.rs               | 5 +++++
crates/settings/src/settings_content/project.rs | 2 ++
docs/src/configuring-zed.md                     | 3 ++-
4 files changed, 11 insertions(+), 1 deletion(-)

Detailed changes

assets/settings/default.json 🔗

@@ -1350,6 +1350,8 @@
   //      "load_direnv": "direct"
   // 2. Load direnv configuration through the shell hook, works for POSIX shells and fish.
   //      "load_direnv": "shell_hook"
+  // 3. Don't load direnv configuration at all.
+  //      "load_direnv": "disabled"
   "load_direnv": "direct",
   "edit_predictions": {
     // A list of globs representing files that edit predictions should be disabled for.

crates/project/src/environment.rs 🔗

@@ -314,6 +314,10 @@ async fn load_directory_shell_environment(
     load_direnv: DirenvSettings,
     tx: mpsc::UnboundedSender<String>,
 ) -> anyhow::Result<HashMap<String, String>> {
+    if let DirenvSettings::Disabled = load_direnv {
+        return Ok(HashMap::default());
+    }
+
     let meta = smol::fs::metadata(&abs_path).await.with_context(|| {
         tx.unbounded_send(format!("Failed to open {}", abs_path.display()))
             .ok();
@@ -355,6 +359,7 @@ async fn load_directory_shell_environment(
     // even if direnv direct mode is enabled.
     let direnv_environment = match load_direnv {
         DirenvSettings::ShellHook => None,
+        DirenvSettings::Disabled => bail!("direnv integration is disabled"),
         // Note: direnv is not available on Windows, so we skip direnv processing
         // and just return the shell environment
         DirenvSettings::Direct if cfg!(target_os = "windows") => None,

docs/src/configuring-zed.md 🔗

@@ -584,10 +584,11 @@ Note: Dirty files (files with unsaved changes) will not be automatically closed
 
 **Options**
 
-There are two options to choose from:
+There are three options to choose from:
 
 1. `shell_hook`: Use the shell hook to load direnv. This relies on direnv to activate upon entering the directory. Supports POSIX shells and fish.
 2. `direct`: Use `direnv export json` to load direnv. This will load direnv directly without relying on the shell hook and might cause some inconsistencies. This allows direnv to work with any shell.
+3. `disabled`: No shell environment will be loaded automatically; direnv must be invoked manually (e.g. with `direnv exec`) to be used.
 
 ## Double Click In Multibuffer