Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

linux: Fix "Failed to start language server" errors when starting Zed from .desktop file #22335

Conversation

0xtimsb
Copy link
Contributor

@0xtimsb 0xtimsb commented Dec 21, 2024

Closes #21406

Context:

A few weeks ago on Linux, we resolved an issue where users could not open more than one file from the file explorer. This was fixed by replacing zed-editor (zed binary in the code) with zed (cli binary in the code) in the .desktop file. The reason for this change was that using the cli to open files is more convenient - it determines weather to spawn a new Zed instance or use an existing one, if we use main binary instead it would throw error Zed is already running.

You can read the complete PR here: linux: Fix file not opening from file explorer.

While this fix resolved the original issue, it introduced a new one.

Problem:

When the cli binary is used, it assumes it is always being invoked from a terminal and relies on std::env::vars() to retrieve the environment variables needed to spawn Zed. These env vars are then passed to the worktree, and eventually, languages use the PATH from this env to find binaries. This leads to the "Failed to start language server" error when the .desktop entry is used on Linux.

Solution:

When the zed-editor binary is used, it uses some clever Unix-specific logic to retrieve the default shell (load_shell_from_passwd) and then fetch the env vars from that shell (load_login_shell_environment). This same logic should be used in the cli binary when it is invoked via a .desktop entry rather than from a terminal.

Approach:

I moved these two functions mentioned above to a utils file and reused them in cli binary to fetch env vars only on Linux when it is not run from a terminal. This provides missing paths, and fix the issue.

It is also possible to handle this in the zed-editor binary by modifying the logic in handle_cli_connection, where CliRequest::Open is processed. There we can discard incoming env, and use our logic. But discarding incoming envs felt weird, and I thought it's better to handle this at source.

Release Notes:

  • Fixed Failed to start language server errors when starting from dekstop entry on Linux

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Dec 21, 2024
@zed-industries-bot
Copy link

zed-industries-bot commented Dec 21, 2024

Messages
📖

This PR includes links to the following GitHub Issues: #20070
If this PR aims to close an issue, please include a Closes #ISSUE line at the top of the PR body.

Generated by 🚫 dangerJS against 8d2f042

crates/cli/Cargo.toml Outdated Show resolved Hide resolved
crates/cli/src/main.rs Outdated Show resolved Hide resolved
crates/cli/src/main.rs Outdated Show resolved Hide resolved
crates/cli/src/main.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much simpler, thank you.

@SomeoneToIgnore SomeoneToIgnore added this pull request to the merge queue Dec 22, 2024
Merged via the queue into zed-industries:main with commit 204af9c Dec 22, 2024
13 checks passed
CharlesChen0823 pushed a commit to CharlesChen0823/zed that referenced this pull request Dec 24, 2024
… from .desktop file (zed-industries#22335)

Closes zed-industries#21406

Context:

A few weeks ago on Linux, we resolved an
[issue](zed-industries#20070) where users
could not open more than one file from the file explorer. This was fixed
by replacing `zed-editor` (zed binary in the code) with `zed` (cli
binary in the code) in the `.desktop` file. The reason for this change
was that using the cli to open files is more convenient - it determines
weather to spawn a new Zed instance or use an existing one, if we use
main binary instead it would throw error `Zed is already running`.

You can read the complete PR here: [linux: Fix file not opening from
file explorer](zed-industries#21137).

While this fix resolved the original issue, it introduced a new one.

Problem:

When the cli binary is used, it assumes it is always being invoked from
a terminal and relies on `std::env::vars()` to retrieve the environment
variables needed to spawn Zed. These env vars are then passed to the
worktree, and eventually, languages use the `PATH` from this env to find
binaries. This leads to the "Failed to start language server" error when
the `.desktop` entry is used on Linux.

Solution:

When the `zed-editor` binary is used, it uses some clever Unix-specific
logic to retrieve the default shell (`load_shell_from_passwd`) and then
fetch the env vars from that shell (`load_login_shell_environment`).
This same logic should be used in the cli binary when it is invoked via
a `.desktop` entry rather than from a terminal.

Approach:

I moved these two functions mentioned above to a utils file and reused
them in cli binary to fetch env vars only on Linux when it is not run
from a terminal. This provides missing paths, and fix the issue.

It is also possible to handle this in the `zed-editor` binary by
modifying the logic in `handle_cli_connection`, where `CliRequest::Open`
is processed. There we can discard incoming env, and use our logic. But
discarding incoming envs felt weird, and I thought it's better to handle
this at source.

Release Notes:

- Fixed `Failed to start language server` errors when starting from
dekstop entry on Linux
helgemahrt pushed a commit to helgemahrt/zed that referenced this pull request Jan 1, 2025
… from .desktop file (zed-industries#22335)

Closes zed-industries#21406

Context:

A few weeks ago on Linux, we resolved an
[issue](zed-industries#20070) where users
could not open more than one file from the file explorer. This was fixed
by replacing `zed-editor` (zed binary in the code) with `zed` (cli
binary in the code) in the `.desktop` file. The reason for this change
was that using the cli to open files is more convenient - it determines
weather to spawn a new Zed instance or use an existing one, if we use
main binary instead it would throw error `Zed is already running`.

You can read the complete PR here: [linux: Fix file not opening from
file explorer](zed-industries#21137).

While this fix resolved the original issue, it introduced a new one.

Problem:

When the cli binary is used, it assumes it is always being invoked from
a terminal and relies on `std::env::vars()` to retrieve the environment
variables needed to spawn Zed. These env vars are then passed to the
worktree, and eventually, languages use the `PATH` from this env to find
binaries. This leads to the "Failed to start language server" error when
the `.desktop` entry is used on Linux.

Solution:

When the `zed-editor` binary is used, it uses some clever Unix-specific
logic to retrieve the default shell (`load_shell_from_passwd`) and then
fetch the env vars from that shell (`load_login_shell_environment`).
This same logic should be used in the cli binary when it is invoked via
a `.desktop` entry rather than from a terminal.

Approach:

I moved these two functions mentioned above to a utils file and reused
them in cli binary to fetch env vars only on Linux when it is not run
from a terminal. This provides missing paths, and fix the issue.

It is also possible to handle this in the `zed-editor` binary by
modifying the logic in `handle_cli_connection`, where `CliRequest::Open`
is processed. There we can discard incoming env, and use our logic. But
discarding incoming envs felt weird, and I thought it's better to handle
this at source.

Release Notes:

- Fixed `Failed to start language server` errors when starting from
dekstop entry on Linux
@mrnugget
Copy link
Member

mrnugget commented Jan 7, 2025

Hey! Found this commit after #9786 (comment) and I'm wondering whether we couldn't get away with a shorter solution.

Here's how the CLI/main-binary env handling works, essentially: the CLI passes an environment to the zed executable, if the zed executable doesn't get that, it runs the login-shell to get the environment, per project in the best case.

So what I'm thinking here is that instead of loading the login-shell env in the CLI and then passing it on, why not pass None instead if we detect that we're not in a TTY?

This, basically:

diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index 32c5bbb21b..4cb47c1f24 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -167,16 +167,15 @@ fn main() -> Result<()> {
         None
     };
 
+    let mut env = Some(std::env::vars().collect::<HashMap<_, _>>());
+
     // On Linux, desktop entry uses `cli` to spawn `zed`, so we need to load env vars from the shell
     // since it doesn't inherit env vars from the terminal.
     #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     if !std::io::stdout().is_terminal() {
-        load_shell_from_passwd().log_err();
-        load_login_shell_environment().log_err();
+        env = None;
     }
 
-    let env = Some(std::env::vars().collect::<HashMap<_, _>>());
-
     let exit_status = Arc::new(Mutex::new(None));
     let mut paths = vec![];
     let mut urls = vec![];

Then we don't need to whole env-loading logic shared.

What do you think @SomeoneToIgnore @0xtimsb?

@SomeoneToIgnore
Copy link
Contributor

Sounds great too, I'm somewhat limited in this field and do not see all possible ways to get it working.

@0xtimsb
Copy link
Contributor Author

0xtimsb commented Jan 7, 2025

Hi @mrnugget,

I think what you're saying makes sense. I somehow missed the code below while working on the fix and assumed the only way to pass the right envs to the LSP was through the CLI. I didn’t realize there was already a fallback setup if env is None. This is a lot cleaner. Let me know if you’d like me to update or test this.

/// Returns the project environment, if possible.
/// If the project was opened from the CLI, then the inherited CLI environment is returned.
/// If it wasn't opened from the CLI, and a worktree is given, then a shell is spawned in
/// the worktree's path, to get environment variables as if the user has `cd`'d into
/// the worktrees path.
pub(crate) fn get_environment(

@mrnugget
Copy link
Member

mrnugget commented Jan 7, 2025

This is a lot cleaner. Let me know if you’d like me to update or test this.

If you want, that'd be neat! I think that comment ~could stay the same, if we add the note that "opened from the CLI" can mean that the Zed CLI binary was used, but not on the CLI.

@0xtimsb
Copy link
Contributor Author

0xtimsb commented Jan 7, 2025

On it.

github-merge-queue bot pushed a commit that referenced this pull request Jan 8, 2025
… (direnv) (#22803)

Closes #18908

This PR started as a cleanup of redundant logic for setting up envs when
Zed is launched as a desktop entry on Linux. More on this can be read
[here](#22335 (comment)).
The TLDR is that desktop entries on Linux sometimes might not have the
correct envs (as they don't `cwd` into your project directory). To
address this, we initially tried to fix it by loading the default shell
and its env vars.

However, a better solution, as recommended by @mrnugget, is to pass
`env` as `None`. Internally, if `env` is `None`, it falls back to the
project's working dir envs. This removes the need to manually load the
envs and is cleaner.

Additionally, it also fixes an issue with Zed not loading
project-specific envs because now we are actually doing so (albeit
unintentionally?).

I don't have macOS to test, but I believe this is not an issue on macOS
since it uses the Zed binary instead of the CLI, which essentially sets
the CLI `env` to `None` automatically.

Before:

Here, I have `/home/tims/go/bin` set up in `.envrc`, which only loads in
that project directory.

When launching Zed via the CLI in the project directory, notice
`/home/tims/go/bin` is in the `PATH`. As a result, we use the
user-installed `gopls` server.

```sh
[INFO] attempting to start language server "gopls", path: "/home/tims/temp/go-proj", id: 1
[INFO] using project environment variables from CLI. PATH="/home/tims/go/bin:/usr/local/go/bin"
[INFO] found user-installed language server for gopls. path: "/home/tims/go/bin/gopls", arguments: ["-mode=stdio"]
[INFO] starting language server process. binary path: "/home/tims/go/bin/gopls", working directory: "/home/tims/temp/go-proj", args: ["-mode=stdio"]
```

However, when using the desktop entry and attempting to load envs from
the default shell, notice `/home/tims/go/bin` is no longer there since
it's not in the project directory. Zed cannot find the user-installed
language server and starts downloading its own `gopls`.

```sh
[INFO] attempting to start language server "gopls", path: "/home/tims/temp/go-proj", id: 1
[INFO] using project environment variables from CLI. PATH="/usr/local/go/bin"
[INFO] fetching latest version of language server "gopls"
[INFO] downloading language server "gopls"
[INFO] starting language server process. binary path: "/home/tims/.local/share/zed/languages/gopls/gopls_0.17.1_go_1.23.4", working directory: "/home/tims/temp/go-proj", args: ["-mode=stdio"]
```

After: 

When using the desktop entry, we pass the CLI env as `None`. For the
language server, it falls back to the project directory envs. Result,
Zed finds the user-installed language server.

```sh
[INFO] attempting to start language server "gopls", path: "/home/tims/temp/go-proj", id: 1
[INFO] using project environment variables shell launched in "/home/tims/temp/go-proj". PATH="/home/tims/go/bin:/usr/local/go/bin"
[INFO] found user-installed language server for gopls. path: "/home/tims/go/bin/gopls", arguments: ["-mode=stdio"]
[INFO] starting language server process. binary path: "/home/tims/go/bin/gopls", working directory: "/home/tims/temp/go-proj", args: ["-mode=stdio"]
```

Release Notes:

- Fixed issue with project-specific env not being found via .envrc
(direnv) on Linux
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed The user has signed the Contributor License Agreement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Linux cannot install gopls when starting zed from .desktop file
4 participants